1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 // $HOME/.exrc and ./.exrc
12 // add magic to search /foo.*bar
15 // if mark[] values were line numbers rather than pointers
16 // it would be easier to change the mark when add/delete lines
17 // More intelligence in refresh()
18 // ":r !cmd" and "!cmd" to filter text through an external command
19 // An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (23 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 // Should be after libbb.h: on some systems regex.h needs sys/types.h:
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 // the CRASHME code is unmaintained, and doesn't currently build
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 // VT102 ESC sequences.
222 // See "Xterm Control Sequences"
223 // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
225 // Inverse/Normal text
226 #define ESC_BOLD_TEXT ESC"[7m"
227 #define ESC_NORM_TEXT ESC"[m"
229 #define ESC_BELL "\007"
230 // Clear-to-end-of-line
231 #define ESC_CLEAR2EOL ESC"[K"
232 // Clear-to-end-of-screen.
233 // (We use default param here.
234 // Full sequence is "ESC [ <num> J",
235 // <num> is 0/1/2 = "erase below/above/all".)
236 #define ESC_CLEAR2EOS ESC"[J"
237 // Cursor to given coordinate (1,1: top left)
238 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
239 #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
241 //// Cursor up and down
242 //#define ESC_CURSOR_UP ESC"[A"
243 //#define ESC_CURSOR_DOWN "\n"
245 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
246 // cmds modifying text[]
247 // vda: removed "aAiIs" as they switch us into insert mode
248 // and remembering input for replay after them makes no sense
249 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
255 FORWARD = 1, // code depends on "1" for array index
256 BACK = -1, // code depends on "-1" for array index
257 LIMITED = 0, // char_search() only current line
258 FULL = 1, // char_search() to the end/beginning of entire text
260 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
261 S_TO_WS = 2, // used in skip_thing() for moving "dot"
262 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
263 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
264 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
268 // vi.c expects chars to be unsigned.
269 // busybox build system provides that, but it's better
270 // to audit and fix the source
273 // many references - keep near the top of globals
274 char *text, *end; // pointers to the user data in memory
275 char *dot; // where all the action takes place
276 int text_size; // size of the allocated buffer
280 #define VI_AUTOINDENT 1
281 #define VI_SHOWMATCH 2
282 #define VI_IGNORECASE 4
283 #define VI_ERR_METHOD 8
284 #define autoindent (vi_setops & VI_AUTOINDENT)
285 #define showmatch (vi_setops & VI_SHOWMATCH )
286 #define ignorecase (vi_setops & VI_IGNORECASE)
287 // indicate error with beep or flash
288 #define err_method (vi_setops & VI_ERR_METHOD)
290 #if ENABLE_FEATURE_VI_READONLY
291 smallint readonly_mode;
292 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
293 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
294 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
296 #define SET_READONLY_FILE(flags) ((void)0)
297 #define SET_READONLY_MODE(flags) ((void)0)
298 #define UNSET_READONLY_FILE(flags) ((void)0)
301 smallint editing; // >0 while we are editing a file
302 // [code audit says "can be 0, 1 or 2 only"]
303 smallint cmd_mode; // 0=command 1=insert 2=replace
304 int modified_count; // buffer contents changed if !0
305 int last_modified_count; // = -1;
306 int save_argc; // how many file names on cmd line
307 int cmdcnt; // repetition count
308 unsigned rows, columns; // the terminal screen is this size
309 #if ENABLE_FEATURE_VI_ASK_TERMINAL
310 int get_rowcol_error;
312 int crow, ccol; // cursor is on Crow x Ccol
313 int offset; // chars scrolled off the screen to the left
314 int have_status_msg; // is default edit status needed?
315 // [don't make smallint!]
316 int last_status_cksum; // hash of current status line
317 char *current_filename;
318 char *screenbegin; // index into text[], of top line on the screen
319 char *screen; // pointer to the virtual screen buffer
320 int screensize; // and its size
322 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
323 #if ENABLE_FEATURE_VI_CRASHME
324 char last_input_char; // last char read from user
327 #if ENABLE_FEATURE_VI_DOT_CMD
328 smallint adding2q; // are we currently adding user input to q
329 int lmc_len; // length of last_modifying_cmd
330 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
332 #if ENABLE_FEATURE_VI_SEARCH
333 char *last_search_pattern; // last pattern from a '/' or '?' search
337 #if ENABLE_FEATURE_VI_YANKMARK
338 char *edit_file__cur_line;
340 int refresh__old_offset;
341 int format_edit_status__tot;
343 // a few references only
344 #if ENABLE_FEATURE_VI_YANKMARK
345 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
347 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
348 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
349 char *context_start, *context_end;
351 #if ENABLE_FEATURE_VI_USE_SIGNALS
352 sigjmp_buf restart; // int_handler() jumps to location remembered here
354 struct termios term_orig; // remember what the cooked mode was
355 #if ENABLE_FEATURE_VI_COLON
356 char *initial_cmds[3]; // currently 2 entries, NULL terminated
358 // Should be just enough to hold a key sequence,
359 // but CRASHME mode uses it as generated command buffer too
360 #if ENABLE_FEATURE_VI_CRASHME
361 char readbuffer[128];
363 char readbuffer[KEYCODE_BUFFER_SIZE];
365 #define STATUS_BUFFER_LEN 200
366 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
367 #if ENABLE_FEATURE_VI_DOT_CMD
368 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
370 char get_input_line__buf[MAX_INPUT_LEN]; // former static
372 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
374 #if ENABLE_FEATURE_VI_UNDO
375 // undo_push() operations
378 #define UNDO_INS_CHAIN 2
379 #define UNDO_DEL_CHAIN 3
380 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
381 #define UNDO_QUEUED_FLAG 4
382 #define UNDO_INS_QUEUED 4
383 #define UNDO_DEL_QUEUED 5
384 #define UNDO_USE_SPOS 32
385 #define UNDO_EMPTY 64
386 // Pass-through flags for functions that can be undone
389 #define ALLOW_UNDO_CHAIN 2
390 # if ENABLE_FEATURE_VI_UNDO_QUEUE
391 #define ALLOW_UNDO_QUEUED 3
392 char undo_queue_state;
394 char *undo_queue_spos; // Start position of queued operation
395 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
397 // If undo queuing disabled, don't invoke the missing queue logic
398 #define ALLOW_UNDO_QUEUED 1
401 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
402 int start; // Offset where the data should be restored/deleted
403 int length; // total data size
404 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
405 char undo_text[1]; // text that was deleted (if deletion)
407 #endif /* ENABLE_FEATURE_VI_UNDO */
409 #define G (*ptr_to_globals)
410 #define text (G.text )
411 #define text_size (G.text_size )
416 #define vi_setops (G.vi_setops )
417 #define editing (G.editing )
418 #define cmd_mode (G.cmd_mode )
419 #define modified_count (G.modified_count )
420 #define last_modified_count (G.last_modified_count)
421 #define save_argc (G.save_argc )
422 #define cmdcnt (G.cmdcnt )
423 #define rows (G.rows )
424 #define columns (G.columns )
425 #define crow (G.crow )
426 #define ccol (G.ccol )
427 #define offset (G.offset )
428 #define status_buffer (G.status_buffer )
429 #define have_status_msg (G.have_status_msg )
430 #define last_status_cksum (G.last_status_cksum )
431 #define current_filename (G.current_filename )
432 #define screen (G.screen )
433 #define screensize (G.screensize )
434 #define screenbegin (G.screenbegin )
435 #define tabstop (G.tabstop )
436 #define last_forward_char (G.last_forward_char )
437 #if ENABLE_FEATURE_VI_CRASHME
438 #define last_input_char (G.last_input_char )
440 #if ENABLE_FEATURE_VI_READONLY
441 #define readonly_mode (G.readonly_mode )
443 #define readonly_mode 0
445 #define adding2q (G.adding2q )
446 #define lmc_len (G.lmc_len )
448 #define ioq_start (G.ioq_start )
449 #define last_search_pattern (G.last_search_pattern)
451 #define edit_file__cur_line (G.edit_file__cur_line)
452 #define refresh__old_offset (G.refresh__old_offset)
453 #define format_edit_status__tot (G.format_edit_status__tot)
455 #define YDreg (G.YDreg )
456 //#define Ureg (G.Ureg )
457 #define mark (G.mark )
458 #define context_start (G.context_start )
459 #define context_end (G.context_end )
460 #define restart (G.restart )
461 #define term_orig (G.term_orig )
462 #define initial_cmds (G.initial_cmds )
463 #define readbuffer (G.readbuffer )
464 #define scr_out_buf (G.scr_out_buf )
465 #define last_modifying_cmd (G.last_modifying_cmd )
466 #define get_input_line__buf (G.get_input_line__buf)
468 #if ENABLE_FEATURE_VI_UNDO
469 #define undo_stack_tail (G.undo_stack_tail )
470 # if ENABLE_FEATURE_VI_UNDO_QUEUE
471 #define undo_queue_state (G.undo_queue_state)
472 #define undo_q (G.undo_q )
473 #define undo_queue (G.undo_queue )
474 #define undo_queue_spos (G.undo_queue_spos )
478 #define INIT_G() do { \
479 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
480 last_modified_count = -1; \
481 /* "" but has space for 2 chars: */ \
482 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
485 #if ENABLE_FEATURE_VI_CRASHME
486 static int crashme = 0;
489 static void show_status_line(void); // put a message on the bottom line
490 static void status_line_bold(const char *, ...);
492 static void show_help(void)
494 puts("These features are available:"
495 #if ENABLE_FEATURE_VI_SEARCH
496 "\n\tPattern searches with / and ?"
498 #if ENABLE_FEATURE_VI_DOT_CMD
499 "\n\tLast command repeat with ."
501 #if ENABLE_FEATURE_VI_YANKMARK
502 "\n\tLine marking with 'x"
503 "\n\tNamed buffers with \"x"
505 #if ENABLE_FEATURE_VI_READONLY
506 //not implemented: "\n\tReadonly if vi is called as \"view\""
507 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
509 #if ENABLE_FEATURE_VI_SET
510 "\n\tSome colon mode commands with :"
512 #if ENABLE_FEATURE_VI_SETOPTS
513 "\n\tSettable options with \":set\""
515 #if ENABLE_FEATURE_VI_USE_SIGNALS
516 "\n\tSignal catching- ^C"
517 "\n\tJob suspend and resume with ^Z"
519 #if ENABLE_FEATURE_VI_WIN_RESIZE
520 "\n\tAdapt to window re-sizes"
525 static void write1(const char *out)
530 #if ENABLE_FEATURE_VI_WIN_RESIZE
531 static int query_screen_dimensions(void)
533 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
534 if (rows > MAX_SCR_ROWS)
536 if (columns > MAX_SCR_COLS)
537 columns = MAX_SCR_COLS;
541 static ALWAYS_INLINE int query_screen_dimensions(void)
547 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
548 static int mysleep(int hund)
550 struct pollfd pfd[1];
555 pfd[0].fd = STDIN_FILENO;
556 pfd[0].events = POLLIN;
557 return safe_poll(pfd, 1, hund*10) > 0;
560 //----- Set terminal attributes --------------------------------
561 static void rawmode(void)
563 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
564 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
567 static void cookmode(void)
570 tcsetattr_stdin_TCSANOW(&term_orig);
573 //----- Terminal Drawing ---------------------------------------
574 // The terminal is made up of 'rows' line of 'columns' columns.
575 // classically this would be 24 x 80.
576 // screen coordinates
582 // 23,0 ... 23,79 <- status line
584 //----- Move the cursor to row x col (count from 0, not 1) -------
585 static void place_cursor(int row, int col)
587 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
589 if (row < 0) row = 0;
590 if (row >= rows) row = rows - 1;
591 if (col < 0) col = 0;
592 if (col >= columns) col = columns - 1;
594 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
598 //----- Erase from cursor to end of line -----------------------
599 static void clear_to_eol(void)
601 write1(ESC_CLEAR2EOL);
604 static void go_bottom_and_clear_to_eol(void)
606 place_cursor(rows - 1, 0);
610 //----- Start standout mode ------------------------------------
611 static void standout_start(void)
613 write1(ESC_BOLD_TEXT);
616 //----- End standout mode --------------------------------------
617 static void standout_end(void)
619 write1(ESC_NORM_TEXT);
622 //----- Text Movement Routines ---------------------------------
623 static char *begin_line(char *p) // return pointer to first char cur line
626 p = memrchr(text, '\n', p - text);
634 static char *end_line(char *p) // return pointer to NL of cur line
637 p = memchr(p, '\n', end - p - 1);
644 static char *dollar_line(char *p) // return pointer to just before NL line
647 // Try to stay off of the Newline
648 if (*p == '\n' && (p - begin_line(p)) > 0)
653 static char *prev_line(char *p) // return pointer first char prev line
655 p = begin_line(p); // goto beginning of cur line
656 if (p > text && p[-1] == '\n')
657 p--; // step to prev line
658 p = begin_line(p); // goto beginning of prev line
662 static char *next_line(char *p) // return pointer first char next line
665 if (p < end - 1 && *p == '\n')
666 p++; // step to next line
670 //----- Text Information Routines ------------------------------
671 static char *end_screen(void)
676 // find new bottom line
678 for (cnt = 0; cnt < rows - 2; cnt++)
684 // count line from start to stop
685 static int count_lines(char *start, char *stop)
690 if (stop < start) { // start and stop are backwards- reverse them
696 stop = end_line(stop);
697 while (start <= stop && start <= end - 1) {
698 start = end_line(start);
706 static char *find_line(int li) // find beginning of line #li
710 for (q = text; li > 1; li--) {
716 static int next_tabstop(int col)
718 return col + ((tabstop - 1) - (col % tabstop));
721 //----- Erase the Screen[] memory ------------------------------
722 static void screen_erase(void)
724 memset(screen, ' ', screensize); // clear new screen
727 //----- Synchronize the cursor to Dot --------------------------
728 static NOINLINE void sync_cursor(char *d, int *row, int *col)
730 char *beg_cur; // begin and end of "d" line
734 beg_cur = begin_line(d); // first char of cur line
736 if (beg_cur < screenbegin) {
737 // "d" is before top line on screen
738 // how many lines do we have to move
739 cnt = count_lines(beg_cur, screenbegin);
741 screenbegin = beg_cur;
742 if (cnt > (rows - 1) / 2) {
743 // we moved too many lines. put "dot" in middle of screen
744 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
745 screenbegin = prev_line(screenbegin);
749 char *end_scr; // begin and end of screen
750 end_scr = end_screen(); // last char of screen
751 if (beg_cur > end_scr) {
752 // "d" is after bottom line on screen
753 // how many lines do we have to move
754 cnt = count_lines(end_scr, beg_cur);
755 if (cnt > (rows - 1) / 2)
756 goto sc1; // too many lines
757 for (ro = 0; ro < cnt - 1; ro++) {
758 // move screen begin the same amount
759 screenbegin = next_line(screenbegin);
760 // now, move the end of screen
761 end_scr = next_line(end_scr);
762 end_scr = end_line(end_scr);
766 // "d" is on screen- find out which row
768 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
774 // find out what col "d" is on
776 while (tp < d) { // drive "co" to correct column
777 if (*tp == '\n') //vda || *tp == '\0')
780 // handle tabs like real vi
781 if (d == tp && cmd_mode) {
784 co = next_tabstop(co);
785 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
786 co++; // display as ^X, use 2 columns
792 // "co" is the column where "dot" is.
793 // The screen has "columns" columns.
794 // The currently displayed columns are 0+offset -- columns+ofset
795 // |-------------------------------------------------------------|
797 // offset | |------- columns ----------------|
799 // If "co" is already in this range then we do not have to adjust offset
800 // but, we do have to subtract the "offset" bias from "co".
801 // If "co" is outside this range then we have to change "offset".
802 // If the first char of a line is a tab the cursor will try to stay
803 // in column 7, but we have to set offset to 0.
805 if (co < 0 + offset) {
808 if (co >= columns + offset) {
809 offset = co - columns + 1;
811 // if the first char of the line is a tab, and "dot" is sitting on it
812 // force offset to 0.
813 if (d == beg_cur && *d == '\t') {
822 //----- Format a text[] line into a buffer ---------------------
823 static char* format_line(char *src /*, int li*/)
828 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
830 c = '~'; // char in col 0 in non-existent lines is '~'
832 while (co < columns + tabstop) {
833 // have we gone past the end?
838 if ((c & 0x80) && !Isprint(c)) {
841 if (c < ' ' || c == 0x7f) {
845 while ((co % tabstop) != (tabstop - 1)) {
853 c += '@'; // Ctrl-X -> 'X'
858 // discard scrolled-off-to-the-left portion,
859 // in tabstop-sized pieces
860 if (ofs >= tabstop && co >= tabstop) {
861 memmove(dest, dest + tabstop, co);
868 // check "short line, gigantic offset" case
871 // discard last scrolled off part
874 // fill the rest with spaces
876 memset(&dest[co], ' ', columns - co);
880 //----- Refresh the changed screen lines -----------------------
881 // Copy the source line from text[] into the buffer and note
882 // if the current screenline is different from the new buffer.
883 // If they differ then that line needs redrawing on the terminal.
885 static void refresh(int full_screen)
887 #define old_offset refresh__old_offset
890 char *tp, *sp; // pointer into text[] and screen[]
892 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
893 unsigned c = columns, r = rows;
894 query_screen_dimensions();
895 #if ENABLE_FEATURE_VI_USE_SIGNALS
896 full_screen |= (c - columns) | (r - rows);
898 if (c != columns || r != rows) {
900 // update screen memory since SIGWINCH won't have done it
901 new_screen(rows, columns);
905 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
906 tp = screenbegin; // index into text[] of top line
908 // compare text[] to screen[] and mark screen[] lines that need updating
909 for (li = 0; li < rows - 1; li++) {
910 int cs, ce; // column start & end
912 // format current text line
913 out_buf = format_line(tp /*, li*/);
915 // skip to the end of the current text[] line
917 char *t = memchr(tp, '\n', end - tp);
922 // see if there are any changes between virtual screen and out_buf
923 changed = FALSE; // assume no change
926 sp = &screen[li * columns]; // start of screen line
928 // force re-draw of every single column from 0 - columns-1
931 // compare newly formatted buffer with virtual screen
932 // look forward for first difference between buf and screen
933 for (; cs <= ce; cs++) {
934 if (out_buf[cs] != sp[cs]) {
935 changed = TRUE; // mark for redraw
940 // look backward for last difference between out_buf and screen
941 for (; ce >= cs; ce--) {
942 if (out_buf[ce] != sp[ce]) {
943 changed = TRUE; // mark for redraw
947 // now, cs is index of first diff, and ce is index of last diff
949 // if horz offset has changed, force a redraw
950 if (offset != old_offset) {
955 // make a sanity check of columns indexes
957 if (ce > columns - 1) ce = columns - 1;
958 if (cs > ce) { cs = 0; ce = columns - 1; }
959 // is there a change between virtual screen and out_buf
961 // copy changed part of buffer to virtual screen
962 memcpy(sp+cs, out_buf+cs, ce-cs+1);
963 place_cursor(li, cs);
964 // write line out to terminal
965 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
969 place_cursor(crow, ccol);
975 //----- Force refresh of all Lines -----------------------------
976 static void redraw(int full_screen)
978 // cursor to top,left; clear to the end of screen
979 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
980 screen_erase(); // erase the internal screen buffer
981 last_status_cksum = 0; // force status update
982 refresh(full_screen); // this will redraw the entire display
986 //----- Flash the screen --------------------------------------
987 static void flash(int h)
996 static void indicate_error(void)
998 #if ENABLE_FEATURE_VI_CRASHME
1009 //----- IO Routines --------------------------------------------
1010 static int readit(void) // read (maybe cursor) key from stdin
1016 // Wait for input. TIMEOUT = -1 makes read_key wait even
1017 // on nonblocking stdin.
1018 // Note: read_key sets errno to 0 on success.
1020 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1021 if (c == -1) { // EOF/error
1022 if (errno == EAGAIN) // paranoia
1024 go_bottom_and_clear_to_eol();
1025 cookmode(); // terminal to "cooked"
1026 bb_error_msg_and_die("can't read user input");
1031 #if ENABLE_FEATURE_VI_DOT_CMD
1032 static int get_one_char(void)
1037 // we are not adding to the q.
1038 // but, we may be reading from a saved q.
1039 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1040 // when done - "ioq_start" is reset instead).
1041 if (ioq_start != NULL) {
1042 // there is a queue to get chars from.
1043 // careful with correct sign expansion!
1044 c = (unsigned char)*ioq++;
1054 // we are adding STDIN chars to q.
1056 if (lmc_len >= MAX_INPUT_LEN - 1) {
1057 status_line_bold("last_modifying_cmd overrun");
1059 last_modifying_cmd[lmc_len++] = c;
1064 # define get_one_char() readit()
1067 // Get input line (uses "status line" area)
1068 static char *get_input_line(const char *prompt)
1070 // char [MAX_INPUT_LEN]
1071 #define buf get_input_line__buf
1076 strcpy(buf, prompt);
1077 last_status_cksum = 0; // force status update
1078 go_bottom_and_clear_to_eol();
1079 write1(prompt); // write out the :, /, or ? prompt
1082 while (i < MAX_INPUT_LEN) {
1084 if (c == '\n' || c == '\r' || c == 27)
1085 break; // this is end of input
1086 if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
1087 // user wants to erase prev char
1089 write1("\b \b"); // erase char on screen
1090 if (i <= 0) // user backs up before b-o-l, exit
1092 } else if (c > 0 && c < 256) { // exclude Unicode
1093 // (TODO: need to handle Unicode)
1104 static void Hit_Return(void)
1109 write1("[Hit return to continue]");
1111 while ((c = get_one_char()) != '\n' && c != '\r')
1113 redraw(TRUE); // force redraw all
1116 //----- Draw the status line at bottom of the screen -------------
1117 // show file status on status line
1118 static int format_edit_status(void)
1120 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1122 #define tot format_edit_status__tot
1124 int cur, percent, ret, trunc_at;
1126 // modified_count is now a counter rather than a flag. this
1127 // helps reduce the amount of line counting we need to do.
1128 // (this will cause a mis-reporting of modified status
1129 // once every MAXINT editing operations.)
1131 // it would be nice to do a similar optimization here -- if
1132 // we haven't done a motion that could have changed which line
1133 // we're on, then we shouldn't have to do this count_lines()
1134 cur = count_lines(text, dot);
1136 // count_lines() is expensive.
1137 // Call it only if something was changed since last time
1139 if (modified_count != last_modified_count) {
1140 tot = cur + count_lines(dot, end - 1) - 1;
1141 last_modified_count = modified_count;
1144 // current line percent
1145 // ------------- ~~ ----------
1148 percent = (100 * cur) / tot;
1154 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1155 columns : STATUS_BUFFER_LEN-1;
1157 ret = snprintf(status_buffer, trunc_at+1,
1158 #if ENABLE_FEATURE_VI_READONLY
1159 "%c %s%s%s %d/%d %d%%",
1161 "%c %s%s %d/%d %d%%",
1163 cmd_mode_indicator[cmd_mode & 3],
1164 (current_filename != NULL ? current_filename : "No file"),
1165 #if ENABLE_FEATURE_VI_READONLY
1166 (readonly_mode ? " [Readonly]" : ""),
1168 (modified_count ? " [Modified]" : ""),
1171 if (ret >= 0 && ret < trunc_at)
1172 return ret; // it all fit
1174 return trunc_at; // had to truncate
1178 static int bufsum(char *buf, int count)
1181 char *e = buf + count;
1183 sum += (unsigned char) *buf++;
1187 static void show_status_line(void)
1189 int cnt = 0, cksum = 0;
1191 // either we already have an error or status message, or we
1193 if (!have_status_msg) {
1194 cnt = format_edit_status();
1195 cksum = bufsum(status_buffer, cnt);
1197 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1198 last_status_cksum = cksum; // remember if we have seen this line
1199 go_bottom_and_clear_to_eol();
1200 write1(status_buffer);
1201 if (have_status_msg) {
1202 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1204 have_status_msg = 0;
1207 have_status_msg = 0;
1209 place_cursor(crow, ccol); // put cursor back in correct place
1214 //----- format the status buffer, the bottom line of screen ------
1215 static void status_line(const char *format, ...)
1219 va_start(args, format);
1220 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1223 have_status_msg = 1;
1225 static void status_line_bold(const char *format, ...)
1229 va_start(args, format);
1230 strcpy(status_buffer, ESC_BOLD_TEXT);
1231 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1232 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1235 strcat(status_buffer, ESC_NORM_TEXT);
1238 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
1240 static void status_line_bold_errno(const char *fn)
1242 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1245 // copy s to buf, convert unprintable
1246 static void print_literal(char *buf, const char *s)
1260 c_is_no_print = (c & 0x80) && !Isprint(c);
1261 if (c_is_no_print) {
1262 strcpy(d, ESC_NORM_TEXT);
1263 d += sizeof(ESC_NORM_TEXT)-1;
1266 if (c < ' ' || c == 0x7f) {
1274 if (c_is_no_print) {
1275 strcpy(d, ESC_BOLD_TEXT);
1276 d += sizeof(ESC_BOLD_TEXT)-1;
1282 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1286 static void not_implemented(const char *s)
1288 char buf[MAX_INPUT_LEN];
1289 print_literal(buf, s);
1290 status_line_bold("'%s' is not implemented", buf);
1293 //----- Block insert/delete, undo ops --------------------------
1294 #if ENABLE_FEATURE_VI_YANKMARK
1295 static char *text_yank(char *p, char *q, int dest) // copy text into a register
1298 if (cnt < 0) { // they are backwards- reverse them
1302 free(reg[dest]); // if already a yank register, free it
1303 reg[dest] = xstrndup(p, cnt + 1);
1307 static char what_reg(void)
1311 c = 'D'; // default to D-reg
1312 if (0 <= YDreg && YDreg <= 25)
1313 c = 'a' + (char) YDreg;
1321 static void check_context(char cmd)
1323 // A context is defined to be "modifying text"
1324 // Any modifying command establishes a new context.
1326 if (dot < context_start || dot > context_end) {
1327 if (strchr(modifying_cmds, cmd) != NULL) {
1328 // we are trying to modify text[]- make this the current context
1329 mark[27] = mark[26]; // move cur to prev
1330 mark[26] = dot; // move local to cur
1331 context_start = prev_line(prev_line(dot));
1332 context_end = next_line(next_line(dot));
1333 //loiter= start_loiter= now;
1338 static char *swap_context(char *p) // goto new context for '' command make this the current context
1342 // the current context is in mark[26]
1343 // the previous context is in mark[27]
1344 // only swap context if other context is valid
1345 if (text <= mark[27] && mark[27] <= end - 1) {
1349 context_start = prev_line(prev_line(prev_line(p)));
1350 context_end = next_line(next_line(next_line(p)));
1354 #endif /* FEATURE_VI_YANKMARK */
1356 #if ENABLE_FEATURE_VI_UNDO
1357 static void undo_push(char *, unsigned, unsigned char);
1360 // open a hole in text[]
1361 // might reallocate text[]! use p += text_hole_make(p, ...),
1362 // and be careful to not use pointers into potentially freed text[]!
1363 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1369 end += size; // adjust the new END
1370 if (end >= (text + text_size)) {
1372 text_size += end - (text + text_size) + 10240;
1373 new_text = xrealloc(text, text_size);
1374 bias = (new_text - text);
1375 screenbegin += bias;
1379 #if ENABLE_FEATURE_VI_YANKMARK
1382 for (i = 0; i < ARRAY_SIZE(mark); i++)
1389 memmove(p + size, p, end - size - p);
1390 memset(p, ' ', size); // clear new hole
1394 // close a hole in text[] - delete "p" through "q", inclusive
1395 // "undo" value indicates if this operation should be undo-able
1396 #if !ENABLE_FEATURE_VI_UNDO
1397 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
1399 static char *text_hole_delete(char *p, char *q, int undo)
1404 // move forwards, from beginning
1408 if (q < p) { // they are backward- swap them
1412 hole_size = q - p + 1;
1414 #if ENABLE_FEATURE_VI_UNDO
1419 undo_push(p, hole_size, UNDO_DEL);
1421 case ALLOW_UNDO_CHAIN:
1422 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1424 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1425 case ALLOW_UNDO_QUEUED:
1426 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1432 if (src < text || src > end)
1434 if (dest < text || dest >= end)
1438 goto thd_atend; // just delete the end of the buffer
1439 memmove(dest, src, cnt);
1441 end = end - hole_size; // adjust the new END
1443 dest = end - 1; // make sure dest in below end-1
1445 dest = end = text; // keep pointers valid
1450 #if ENABLE_FEATURE_VI_UNDO
1452 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1453 // Flush any queued objects to the undo stack
1454 static void undo_queue_commit(void)
1456 // Pushes the queue object onto the undo stack
1458 // Deleted character undo events grow from the end
1459 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1461 (undo_queue_state | UNDO_USE_SPOS)
1463 undo_queue_state = UNDO_EMPTY;
1468 # define undo_queue_commit() ((void)0)
1471 static void flush_undo_data(void)
1473 struct undo_object *undo_entry;
1475 while (undo_stack_tail) {
1476 undo_entry = undo_stack_tail;
1477 undo_stack_tail = undo_entry->prev;
1482 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1483 // Add to the undo stack
1484 static void undo_push(char *src, unsigned length, uint8_t u_type)
1486 struct undo_object *undo_entry;
1489 // UNDO_INS: insertion, undo will remove from buffer
1490 // UNDO_DEL: deleted text, undo will restore to buffer
1491 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1492 // The CHAIN operations are for handling multiple operations that the user
1493 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1494 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1495 // for the INS/DEL operation. The raw values should be equal to the values of
1496 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1498 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1499 // This undo queuing functionality groups multiple character typing or backspaces
1500 // into a single large undo object. This greatly reduces calls to malloc() for
1501 // single-character operations while typing and has the side benefit of letting
1502 // an undo operation remove chunks of text rather than a single character.
1504 case UNDO_EMPTY: // Just in case this ever happens...
1506 case UNDO_DEL_QUEUED:
1508 return; // Only queue single characters
1509 switch (undo_queue_state) {
1511 undo_queue_state = UNDO_DEL;
1513 undo_queue_spos = src;
1515 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1516 // If queue is full, dump it into an object
1517 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1518 undo_queue_commit();
1521 // Switch from storing inserted text to deleted text
1522 undo_queue_commit();
1523 undo_push(src, length, UNDO_DEL_QUEUED);
1527 case UNDO_INS_QUEUED:
1530 switch (undo_queue_state) {
1532 undo_queue_state = UNDO_INS;
1533 undo_queue_spos = src;
1536 undo_q++; // Don't need to save any data for insertions
1537 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1538 undo_queue_commit();
1542 // Switch from storing deleted text to inserted text
1543 undo_queue_commit();
1544 undo_push(src, length, UNDO_INS_QUEUED);
1550 // If undo queuing is disabled, ignore the queuing flag entirely
1551 u_type = u_type & ~UNDO_QUEUED_FLAG;
1554 // Allocate a new undo object
1555 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1556 // For UNDO_DEL objects, save deleted text
1557 if ((text + length) == end)
1559 // If this deletion empties text[], strip the newline. When the buffer becomes
1560 // zero-length, a newline is added back, which requires this to compensate.
1561 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1562 memcpy(undo_entry->undo_text, src, length);
1564 undo_entry = xzalloc(sizeof(*undo_entry));
1566 undo_entry->length = length;
1567 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1568 if ((u_type & UNDO_USE_SPOS) != 0) {
1569 undo_entry->start = undo_queue_spos - text; // use start position from queue
1571 undo_entry->start = src - text; // use offset from start of text buffer
1573 u_type = (u_type & ~UNDO_USE_SPOS);
1575 undo_entry->start = src - text;
1577 undo_entry->u_type = u_type;
1579 // Push it on undo stack
1580 undo_entry->prev = undo_stack_tail;
1581 undo_stack_tail = undo_entry;
1585 static void undo_push_insert(char *p, int len, int undo)
1589 undo_push(p, len, UNDO_INS);
1591 case ALLOW_UNDO_CHAIN:
1592 undo_push(p, len, UNDO_INS_CHAIN);
1594 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1595 case ALLOW_UNDO_QUEUED:
1596 undo_push(p, len, UNDO_INS_QUEUED);
1602 // Undo the last operation
1603 static void undo_pop(void)
1606 char *u_start, *u_end;
1607 struct undo_object *undo_entry;
1609 // Commit pending undo queue before popping (should be unnecessary)
1610 undo_queue_commit();
1612 undo_entry = undo_stack_tail;
1613 // Check for an empty undo stack
1615 status_line("Already at oldest change");
1619 switch (undo_entry->u_type) {
1621 case UNDO_DEL_CHAIN:
1622 // make hole and put in text that was deleted; deallocate text
1623 u_start = text + undo_entry->start;
1624 text_hole_make(u_start, undo_entry->length);
1625 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1626 status_line("Undo [%d] %s %d chars at position %d",
1627 modified_count, "restored",
1628 undo_entry->length, undo_entry->start
1632 case UNDO_INS_CHAIN:
1633 // delete what was inserted
1634 u_start = undo_entry->start + text;
1635 u_end = u_start - 1 + undo_entry->length;
1636 text_hole_delete(u_start, u_end, NO_UNDO);
1637 status_line("Undo [%d] %s %d chars at position %d",
1638 modified_count, "deleted",
1639 undo_entry->length, undo_entry->start
1644 switch (undo_entry->u_type) {
1645 // If this is the end of a chain, lower modification count and refresh display
1648 dot = (text + undo_entry->start);
1651 case UNDO_DEL_CHAIN:
1652 case UNDO_INS_CHAIN:
1656 // Deallocate the undo object we just processed
1657 undo_stack_tail = undo_entry->prev;
1660 // For chained operations, continue popping all the way down the chain.
1662 undo_pop(); // Follow the undo chain if one exists
1667 # define flush_undo_data() ((void)0)
1668 # define undo_queue_commit() ((void)0)
1669 #endif /* ENABLE_FEATURE_VI_UNDO */
1671 //----- Dot Movement Routines ----------------------------------
1672 static void dot_left(void)
1674 undo_queue_commit();
1675 if (dot > text && dot[-1] != '\n')
1679 static void dot_right(void)
1681 undo_queue_commit();
1682 if (dot < end - 1 && *dot != '\n')
1686 static void dot_begin(void)
1688 undo_queue_commit();
1689 dot = begin_line(dot); // return pointer to first char cur line
1692 static void dot_end(void)
1694 undo_queue_commit();
1695 dot = end_line(dot); // return pointer to last char cur line
1698 static char *move_to_col(char *p, int l)
1704 while (co < l && p < end) {
1705 if (*p == '\n') //vda || *p == '\0')
1708 co = next_tabstop(co);
1709 } else if (*p < ' ' || *p == 127) {
1710 co++; // display as ^X, use 2 columns
1718 static void dot_next(void)
1720 undo_queue_commit();
1721 dot = next_line(dot);
1724 static void dot_prev(void)
1726 undo_queue_commit();
1727 dot = prev_line(dot);
1730 static void dot_skip_over_ws(void)
1733 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1737 static void dot_scroll(int cnt, int dir)
1741 undo_queue_commit();
1742 for (; cnt > 0; cnt--) {
1745 // ctrl-Y scroll up one line
1746 screenbegin = prev_line(screenbegin);
1749 // ctrl-E scroll down one line
1750 screenbegin = next_line(screenbegin);
1753 // make sure "dot" stays on the screen so we dont scroll off
1754 if (dot < screenbegin)
1756 q = end_screen(); // find new bottom line
1758 dot = begin_line(q); // is dot is below bottom line?
1762 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1764 if (p >= end && end > text) {
1775 #if ENABLE_FEATURE_VI_DOT_CMD
1776 static void start_new_cmd_q(char c)
1778 // get buffer for new cmd
1779 // if there is a current cmd count put it in the buffer first
1781 lmc_len = sprintf(last_modifying_cmd, "%u%c", cmdcnt, c);
1782 } else { // just save char c onto queue
1783 last_modifying_cmd[0] = c;
1788 static void end_cmd_q(void)
1790 # if ENABLE_FEATURE_VI_YANKMARK
1791 YDreg = 26; // go back to default Yank/Delete reg
1796 # define end_cmd_q() ((void)0)
1797 #endif /* FEATURE_VI_DOT_CMD */
1799 // copy text into register, then delete text.
1800 // if dist <= 0, do not include, or go past, a NewLine
1802 #if !ENABLE_FEATURE_VI_UNDO
1803 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1805 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1809 // make sure start <= stop
1811 // they are backwards, reverse them
1817 // we cannot cross NL boundaries
1821 // dont go past a NewLine
1822 for (; p + 1 <= stop; p++) {
1824 stop = p; // "stop" just before NewLine
1830 #if ENABLE_FEATURE_VI_YANKMARK
1831 text_yank(start, stop, YDreg);
1833 if (yf == YANKDEL) {
1834 p = text_hole_delete(start, stop, undo);
1839 // might reallocate text[]!
1840 static int file_insert(const char *fn, char *p, int initial)
1844 struct stat statbuf;
1851 fd = open(fn, O_RDONLY);
1854 status_line_bold_errno(fn);
1859 if (fstat(fd, &statbuf) < 0) {
1860 status_line_bold_errno(fn);
1863 if (!S_ISREG(statbuf.st_mode)) {
1864 status_line_bold("'%s' is not a regular file", fn);
1867 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1868 p += text_hole_make(p, size);
1869 cnt = full_read(fd, p, size);
1871 status_line_bold_errno(fn);
1872 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1873 } else if (cnt < size) {
1874 // There was a partial read, shrink unused space
1875 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1876 status_line_bold("can't read '%s'", fn);
1881 #if ENABLE_FEATURE_VI_READONLY
1883 && ((access(fn, W_OK) < 0) ||
1884 // root will always have access()
1885 // so we check fileperms too
1886 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1889 SET_READONLY_FILE(readonly_mode);
1895 // find matching char of pair () [] {}
1896 // will crash if c is not one of these
1897 static char *find_pair(char *p, const char c)
1899 const char *braces = "()[]{}";
1903 dir = strchr(braces, c) - braces;
1905 match = braces[dir];
1906 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1908 // look for match, count levels of pairs (( ))
1912 if (p < text || p >= end)
1915 level++; // increase pair levels
1917 level--; // reduce pair level
1919 return p; // found matching pair
1924 #if ENABLE_FEATURE_VI_SETOPTS
1925 // show the matching char of a pair, () [] {}
1926 static void showmatching(char *p)
1930 // we found half of a pair
1931 q = find_pair(p, *p); // get loc of matching char
1933 indicate_error(); // no matching char
1935 // "q" now points to matching pair
1936 save_dot = dot; // remember where we are
1937 dot = q; // go to new loc
1938 refresh(FALSE); // let the user see it
1939 mysleep(40); // give user some time
1940 dot = save_dot; // go back to old loc
1944 #endif /* FEATURE_VI_SETOPTS */
1946 // might reallocate text[]! use p += stupid_insert(p, ...),
1947 // and be careful to not use pointers into potentially freed text[]!
1948 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1951 bias = text_hole_make(p, 1);
1957 #if !ENABLE_FEATURE_VI_UNDO
1958 #define char_insert(a,b,c) char_insert(a,b)
1960 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1962 if (c == 22) { // Is this an ctrl-V?
1963 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1964 refresh(FALSE); // show the ^
1967 #if ENABLE_FEATURE_VI_UNDO
1968 undo_push_insert(p, 1, undo);
1973 } else if (c == 27) { // Is this an ESC?
1975 undo_queue_commit();
1977 end_cmd_q(); // stop adding to q
1978 last_status_cksum = 0; // force status update
1979 if ((p[-1] != '\n') && (dot > text)) {
1982 } else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
1985 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1988 // insert a char into text[]
1990 c = '\n'; // translate \r to \n
1991 #if ENABLE_FEATURE_VI_UNDO
1992 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1994 undo_queue_commit();
1996 undo_push_insert(p, 1, undo);
2000 p += 1 + stupid_insert(p, c); // insert the char
2001 #if ENABLE_FEATURE_VI_SETOPTS
2002 if (showmatch && strchr(")]}", c) != NULL) {
2003 showmatching(p - 1);
2005 if (autoindent && c == '\n') { // auto indent the new line
2008 q = prev_line(p); // use prev line as template
2009 len = strspn(q, " \t"); // space or tab
2012 bias = text_hole_make(p, len);
2015 #if ENABLE_FEATURE_VI_UNDO
2016 undo_push_insert(p, len, undo);
2027 // read text from file or create an empty buf
2028 // will also update current_filename
2029 static int init_text_buffer(char *fn)
2033 // allocate/reallocate text buffer
2036 screenbegin = dot = end = text = xzalloc(text_size);
2038 if (fn != current_filename) {
2039 free(current_filename);
2040 current_filename = xstrdup(fn);
2042 rc = file_insert(fn, text, 1);
2044 // file doesnt exist. Start empty buf with dummy line
2045 char_insert(text, '\n', NO_UNDO);
2050 last_modified_count = -1;
2051 #if ENABLE_FEATURE_VI_YANKMARK
2053 memset(mark, 0, sizeof(mark));
2058 #if ENABLE_FEATURE_VI_YANKMARK \
2059 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2060 || ENABLE_FEATURE_VI_CRASHME
2061 // might reallocate text[]! use p += string_insert(p, ...),
2062 // and be careful to not use pointers into potentially freed text[]!
2063 # if !ENABLE_FEATURE_VI_UNDO
2064 # define string_insert(a,b,c) string_insert(a,b)
2066 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2072 #if ENABLE_FEATURE_VI_UNDO
2073 undo_push_insert(p, i, undo);
2075 bias = text_hole_make(p, i);
2078 #if ENABLE_FEATURE_VI_YANKMARK
2081 for (cnt = 0; *s != '\0'; s++) {
2085 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2092 static int file_write(char *fn, char *first, char *last)
2094 int fd, cnt, charcnt;
2097 status_line_bold("No current filename");
2100 // By popular request we do not open file with O_TRUNC,
2101 // but instead ftruncate() it _after_ successful write.
2102 // Might reduce amount of data lost on power fail etc.
2103 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2106 cnt = last - first + 1;
2107 charcnt = full_write(fd, first, cnt);
2108 ftruncate(fd, charcnt);
2109 if (charcnt == cnt) {
2111 //modified_count = FALSE;
2119 #if ENABLE_FEATURE_VI_SEARCH
2120 # if ENABLE_FEATURE_VI_REGEX_SEARCH
2121 // search for pattern starting at p
2122 static char *char_search(char *p, const char *pat, int dir_and_range)
2124 struct re_pattern_buffer preg;
2131 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2133 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2135 memset(&preg, 0, sizeof(preg));
2136 err = re_compile_pattern(pat, strlen(pat), &preg);
2138 status_line_bold("bad search pattern '%s': %s", pat, err);
2142 range = (dir_and_range & 1);
2143 q = end - 1; // if FULL
2144 if (range == LIMITED)
2146 if (dir_and_range < 0) { // BACK?
2148 if (range == LIMITED)
2152 // RANGE could be negative if we are searching backwards
2162 // search for the compiled pattern, preg, in p[]
2163 // range < 0: search backward
2164 // range > 0: search forward
2166 // re_search() < 0: not found or error
2167 // re_search() >= 0: index of found pattern
2168 // struct pattern char int int int struct reg
2169 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2170 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2174 if (dir_and_range > 0) // FORWARD?
2181 # if ENABLE_FEATURE_VI_SETOPTS
2182 static int mycmp(const char *s1, const char *s2, int len)
2185 return strncasecmp(s1, s2, len);
2187 return strncmp(s1, s2, len);
2190 # define mycmp strncmp
2192 static char *char_search(char *p, const char *pat, int dir_and_range)
2199 range = (dir_and_range & 1);
2200 if (dir_and_range > 0) { //FORWARD?
2201 stop = end - 1; // assume range is p..end-1
2202 if (range == LIMITED)
2203 stop = next_line(p); // range is to next line
2204 for (start = p; start < stop; start++) {
2205 if (mycmp(start, pat, len) == 0) {
2210 stop = text; // assume range is text..p
2211 if (range == LIMITED)
2212 stop = prev_line(p); // range is to prev line
2213 for (start = p - len; start >= stop; start--) {
2214 if (mycmp(start, pat, len) == 0) {
2219 // pattern not found
2223 #endif /* FEATURE_VI_SEARCH */
2225 //----- The Colon commands -------------------------------------
2226 #if ENABLE_FEATURE_VI_COLON
2227 static char *get_one_address(char *p, int *addr) // get colon addr, if present
2231 IF_FEATURE_VI_YANKMARK(char c;)
2232 IF_FEATURE_VI_SEARCH(char *pat;)
2234 *addr = -1; // assume no addr
2235 if (*p == '.') { // the current line
2237 q = begin_line(dot);
2238 *addr = count_lines(text, q);
2240 #if ENABLE_FEATURE_VI_YANKMARK
2241 else if (*p == '\'') { // is this a mark addr
2245 if (c >= 'a' && c <= 'z') {
2248 q = mark[(unsigned char) c];
2249 if (q != NULL) { // is mark valid
2250 *addr = count_lines(text, q);
2255 #if ENABLE_FEATURE_VI_SEARCH
2256 else if (*p == '/') { // a search pattern
2257 q = strchrnul(++p, '/');
2258 pat = xstrndup(p, q - p); // save copy of pattern
2262 q = char_search(dot, pat, (FORWARD << 1) | FULL);
2264 *addr = count_lines(text, q);
2269 else if (*p == '$') { // the last line in file
2271 q = begin_line(end - 1);
2272 *addr = count_lines(text, q);
2273 } else if (isdigit(*p)) { // specific line number
2274 sscanf(p, "%d%n", addr, &st);
2277 // unrecognized address - assume -1
2283 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
2285 //----- get the address' i.e., 1,3 'a,'b -----
2286 // get FIRST addr, if present
2288 p++; // skip over leading spaces
2289 if (*p == '%') { // alias for 1,$
2292 *e = count_lines(text, end-1);
2295 p = get_one_address(p, b);
2298 if (*p == ',') { // is there a address separator
2302 // get SECOND addr, if present
2303 p = get_one_address(p, e);
2307 p++; // skip over trailing spaces
2311 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2312 static void setops(const char *args, const char *opname, int flg_no,
2313 const char *short_opname, int opt)
2315 const char *a = args + flg_no;
2316 int l = strlen(opname) - 1; // opname have + ' '
2318 // maybe strncmp? we had tons of erroneous strncasecmp's...
2319 if (strncasecmp(a, opname, l) == 0
2320 || strncasecmp(a, short_opname, 2) == 0
2330 #endif /* FEATURE_VI_COLON */
2332 // buf must be no longer than MAX_INPUT_LEN!
2333 static void colon(char *buf)
2335 #if !ENABLE_FEATURE_VI_COLON
2336 // Simple ":cmd" handler with minimal set of commands
2345 if (strncmp(p, "quit", cnt) == 0
2346 || strncmp(p, "q!", cnt) == 0
2348 if (modified_count && p[1] != '!') {
2349 status_line_bold("No write since last change (:%s! overrides)", p);
2355 if (strncmp(p, "write", cnt) == 0
2356 || strncmp(p, "wq", cnt) == 0
2357 || strncmp(p, "wn", cnt) == 0
2358 || (p[0] == 'x' && !p[1])
2360 if (modified_count != 0 || p[0] != 'x') {
2361 cnt = file_write(current_filename, text, end - 1);
2365 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2368 last_modified_count = -1;
2369 status_line("'%s' %dL, %dC",
2371 count_lines(text, end - 1), cnt
2374 || p[1] == 'q' || p[1] == 'n'
2375 || p[1] == 'Q' || p[1] == 'N'
2382 if (strncmp(p, "file", cnt) == 0) {
2383 last_status_cksum = 0; // force status update
2386 if (sscanf(p, "%d", &cnt) > 0) {
2387 dot = find_line(cnt);
2394 char c, *buf1, *q, *r;
2395 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2398 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2402 // :3154 // if (-e line 3154) goto it else stay put
2403 // :4,33w! foo // write a portion of buffer to file "foo"
2404 // :w // write all of buffer to current file
2406 // :q! // quit- dont care about modified file
2407 // :'a,'z!sort -u // filter block through sort
2408 // :'f // goto mark "f"
2409 // :'fl // list literal the mark "f" line
2410 // :.r bar // read file "bar" into buffer before dot
2411 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2412 // :/xyz/ // goto the "xyz" line
2413 // :s/find/replace/ // substitute pattern "find" with "replace"
2414 // :!<cmd> // run <cmd> then return
2420 buf++; // move past the ':'
2424 q = text; // assume 1,$ for the range
2426 li = count_lines(text, end - 1);
2427 fn = current_filename;
2429 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2430 buf = get_address(buf, &b, &e);
2432 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2433 // remember orig command line
2437 // get the COMMAND into cmd[]
2439 while (*buf != '\0') {
2445 // get any ARGuments
2446 while (isblank(*buf))
2450 buf1 = last_char_is(cmd, '!');
2453 *buf1 = '\0'; // get rid of !
2456 // if there is only one addr, then the addr
2457 // is the line number of the single line the
2458 // user wants. So, reset the end
2459 // pointer to point at end of the "b" line
2460 q = find_line(b); // what line is #b
2465 // we were given two addrs. change the
2466 // end pointer to the addr given by user.
2467 r = find_line(e); // what line is #e
2471 // ------------ now look for the command ------------
2473 if (i == 0) { // :123CR goto line #123
2475 dot = find_line(b); // what line is #b
2479 # if ENABLE_FEATURE_ALLOW_EXEC
2480 else if (cmd[0] == '!') { // run a cmd
2482 // :!ls run the <cmd>
2483 go_bottom_and_clear_to_eol();
2485 retcode = system(orig_buf + 1); // run the cmd
2487 printf("\nshell returned %i\n\n", retcode);
2489 Hit_Return(); // let user see results
2492 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
2493 if (b < 0) { // no addr given- use defaults
2494 b = e = count_lines(text, dot);
2496 status_line("%d", b);
2497 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
2498 if (b < 0) { // no addr given- use defaults
2499 q = begin_line(dot); // assume .,. for the range
2502 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
2504 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
2507 // don't edit, if the current file has been modified
2508 if (modified_count && !useforce) {
2509 status_line_bold("No write since last change (:%s! overrides)", cmd);
2513 // the user supplied a file name
2515 } else if (current_filename && current_filename[0]) {
2516 // no user supplied name- use the current filename
2517 // fn = current_filename; was set by default
2519 // no user file name, no current name- punt
2520 status_line_bold("No current filename");
2524 size = init_text_buffer(fn);
2526 # if ENABLE_FEATURE_VI_YANKMARK
2527 if (Ureg >= 0 && Ureg < 28) {
2528 free(reg[Ureg]); // free orig line reg- for 'U'
2531 if (YDreg >= 0 && YDreg < 28) {
2532 free(reg[YDreg]); // free default yank/delete register
2536 // how many lines in text[]?
2537 li = count_lines(text, end - 1);
2538 status_line("'%s'%s"
2539 IF_FEATURE_VI_READONLY("%s")
2542 (size < 0 ? " [New file]" : ""),
2543 IF_FEATURE_VI_READONLY(
2544 ((readonly_mode) ? " [Readonly]" : ""),
2546 li, (int)(end - text)
2548 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
2549 if (b != -1 || e != -1) {
2550 status_line_bold("No address allowed on this command");
2554 // user wants a new filename
2555 free(current_filename);
2556 current_filename = xstrdup(args);
2558 // user wants file status info
2559 last_status_cksum = 0; // force status update
2561 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
2562 // print out values of all features
2563 go_bottom_and_clear_to_eol();
2568 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
2569 if (b < 0) { // no addr given- use defaults
2570 q = begin_line(dot); // assume .,. for the range
2573 go_bottom_and_clear_to_eol();
2575 for (; q <= r; q++) {
2579 c_is_no_print = (c & 0x80) && !Isprint(c);
2580 if (c_is_no_print) {
2586 } else if (c < ' ' || c == 127) {
2598 } else if (strncmp(cmd, "quit", i) == 0 // quit
2599 || strncmp(cmd, "next", i) == 0 // edit next file
2600 || strncmp(cmd, "prev", i) == 0 // edit previous file
2605 // force end of argv list
2611 // don't exit if the file been modified
2612 if (modified_count) {
2613 status_line_bold("No write since last change (:%s! overrides)", cmd);
2616 // are there other file to edit
2617 n = save_argc - optind - 1;
2618 if (*cmd == 'q' && n > 0) {
2619 status_line_bold("%d more file(s) to edit", n);
2622 if (*cmd == 'n' && n <= 0) {
2623 status_line_bold("No more files to edit");
2627 // are there previous files to edit
2629 status_line_bold("No previous files to edit");
2635 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
2640 status_line_bold("No filename given");
2643 if (b < 0) { // no addr given- use defaults
2644 q = begin_line(dot); // assume "dot"
2646 // read after current line- unless user said ":0r foo"
2649 // read after last line
2653 { // dance around potentially-reallocated text[]
2654 uintptr_t ofs = q - text;
2655 size = file_insert(fn, q, 0);
2659 goto ret; // nothing was inserted
2660 // how many lines in text[]?
2661 li = count_lines(q, q + size - 1);
2663 IF_FEATURE_VI_READONLY("%s")
2666 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2670 // if the insert is before "dot" then we need to update
2674 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
2675 if (modified_count && !useforce) {
2676 status_line_bold("No write since last change (:%s! overrides)", cmd);
2678 // reset the filenames to edit
2679 optind = -1; // start from 0th file
2682 # if ENABLE_FEATURE_VI_SET
2683 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
2684 # if ENABLE_FEATURE_VI_SETOPTS
2687 i = 0; // offset into args
2688 // only blank is regarded as args delimiter. What about tab '\t'?
2689 if (!args[0] || strcasecmp(args, "all") == 0) {
2690 // print out values of all options
2691 # if ENABLE_FEATURE_VI_SETOPTS
2698 autoindent ? "" : "no",
2699 err_method ? "" : "no",
2700 ignorecase ? "" : "no",
2701 showmatch ? "" : "no",
2707 # if ENABLE_FEATURE_VI_SETOPTS
2710 if (strncmp(argp, "no", 2) == 0)
2711 i = 2; // ":set noautoindent"
2712 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2713 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
2714 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2715 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2716 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2718 sscanf(argp + i+8, "%u", &t);
2719 if (t > 0 && t <= MAX_TABSTOP)
2722 argp = skip_non_whitespace(argp);
2723 argp = skip_whitespace(argp);
2725 # endif /* FEATURE_VI_SETOPTS */
2726 # endif /* FEATURE_VI_SET */
2728 # if ENABLE_FEATURE_VI_SEARCH
2729 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
2730 char *F, *R, *flags;
2731 size_t len_F, len_R;
2732 int gflag; // global replace flag
2733 # if ENABLE_FEATURE_VI_UNDO
2734 int dont_chain_first_item = ALLOW_UNDO;
2737 // F points to the "find" pattern
2738 // R points to the "replace" pattern
2739 // replace the cmd line delimiters "/" with NULs
2740 c = orig_buf[1]; // what is the delimiter
2741 F = orig_buf + 2; // start of "find"
2742 R = strchr(F, c); // middle delimiter
2746 *R++ = '\0'; // terminate "find"
2747 flags = strchr(R, c);
2751 *flags++ = '\0'; // terminate "replace"
2755 if (b < 0) { // maybe :s/foo/bar/
2756 q = begin_line(dot); // start with cur line
2757 b = count_lines(text, q); // cur line number
2760 e = b; // maybe :.s/foo/bar/
2762 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2763 char *ls = q; // orig line start
2766 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
2769 // we found the "find" pattern - delete it
2770 // For undo support, the first item should not be chained
2771 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2772 # if ENABLE_FEATURE_VI_UNDO
2773 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2775 // insert the "replace" patern
2776 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2779 //q += bias; - recalculated anyway
2780 // check for "global" :s/foo/bar/g
2782 if ((found + len_R) < end_line(ls)) {
2784 goto vc4; // don't let q move past cur line
2790 # endif /* FEATURE_VI_SEARCH */
2791 } else if (strncmp(cmd, "version", i) == 0) { // show software version
2792 status_line(BB_VER);
2793 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2794 || strncmp(cmd, "wq", i) == 0
2795 || strncmp(cmd, "wn", i) == 0
2796 || (cmd[0] == 'x' && !cmd[1])
2799 //int forced = FALSE;
2801 // is there a file name to write to?
2805 # if ENABLE_FEATURE_VI_READONLY
2806 if (readonly_mode && !useforce) {
2807 status_line_bold("'%s' is read only", fn);
2812 // if "fn" is not write-able, chmod u+w
2813 // sprintf(syscmd, "chmod u+w %s", fn);
2817 if (modified_count != 0 || cmd[0] != 'x') {
2819 l = file_write(fn, q, r);
2824 //if (useforce && forced) {
2826 // sprintf(syscmd, "chmod u-w %s", fn);
2832 status_line_bold_errno(fn);
2834 // how many lines written
2835 li = count_lines(q, q + l - 1);
2836 status_line("'%s' %dL, %dC", fn, li, l);
2838 if (q == text && q + l == end) {
2840 last_modified_count = -1;
2843 || cmd[1] == 'q' || cmd[1] == 'n'
2844 || cmd[1] == 'Q' || cmd[1] == 'N'
2850 # if ENABLE_FEATURE_VI_YANKMARK
2851 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
2852 if (b < 0) { // no addr given- use defaults
2853 q = begin_line(dot); // assume .,. for the range
2856 text_yank(q, r, YDreg);
2857 li = count_lines(q, r);
2858 status_line("Yank %d lines (%d chars) into [%c]",
2859 li, strlen(reg[YDreg]), what_reg());
2863 not_implemented(cmd);
2866 dot = bound_dot(dot); // make sure "dot" is valid
2868 # if ENABLE_FEATURE_VI_SEARCH
2870 status_line(":s expression missing delimiters");
2872 #endif /* FEATURE_VI_COLON */
2875 //----- Char Routines --------------------------------------------
2876 // Chars that are part of a word-
2877 // 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2878 // Chars that are Not part of a word (stoppers)
2879 // !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2880 // Chars that are WhiteSpace
2881 // TAB NEWLINE VT FF RETURN SPACE
2882 // DO NOT COUNT NEWLINE AS WHITESPACE
2884 static char *new_screen(int ro, int co)
2889 screensize = ro * co + 8;
2890 screen = xmalloc(screensize);
2891 // initialize the new screen. assume this will be a empty file.
2893 // non-existent text[] lines start with a tilde (~).
2894 for (li = 1; li < ro - 1; li++) {
2895 screen[(li * co) + 0] = '~';
2900 static int st_test(char *p, int type, int dir, char *tested)
2910 if (type == S_BEFORE_WS) {
2912 test = (!isspace(c) || c == '\n');
2914 if (type == S_TO_WS) {
2916 test = (!isspace(c) || c == '\n');
2918 if (type == S_OVER_WS) {
2922 if (type == S_END_PUNCT) {
2926 if (type == S_END_ALNUM) {
2928 test = (isalnum(c) || c == '_');
2934 static char *skip_thing(char *p, int linecnt, int dir, int type)
2938 while (st_test(p, type, dir, &c)) {
2939 // make sure we limit search to correct number of lines
2940 if (c == '\n' && --linecnt < 1)
2942 if (dir >= 0 && p >= end - 1)
2944 if (dir < 0 && p <= text)
2946 p += dir; // move to next char
2951 #if ENABLE_FEATURE_VI_USE_SIGNALS
2952 static void winch_handler(int sig UNUSED_PARAM)
2954 int save_errno = errno;
2955 // FIXME: do it in main loop!!!
2956 signal(SIGWINCH, winch_handler);
2957 query_screen_dimensions();
2958 new_screen(rows, columns); // get memory for virtual screen
2959 redraw(TRUE); // re-draw the screen
2962 static void tstp_handler(int sig UNUSED_PARAM)
2964 int save_errno = errno;
2966 // ioctl inside cookmode() was seen to generate SIGTTOU,
2967 // stopping us too early. Prevent that:
2968 signal(SIGTTOU, SIG_IGN);
2970 go_bottom_and_clear_to_eol();
2971 cookmode(); // terminal to "cooked"
2974 //signal(SIGTSTP, SIG_DFL);
2976 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2977 //signal(SIGTSTP, tstp_handler);
2979 // we have been "continued" with SIGCONT, restore screen and termios
2980 rawmode(); // terminal to "raw"
2981 last_status_cksum = 0; // force status update
2982 redraw(TRUE); // re-draw the screen
2986 static void int_handler(int sig)
2988 signal(SIGINT, int_handler);
2989 siglongjmp(restart, sig);
2991 #endif /* FEATURE_VI_USE_SIGNALS */
2993 static void do_cmd(int c);
2995 static int find_range(char **start, char **stop, char c)
2997 char *save_dot, *p, *q, *t;
2998 int cnt, multiline = 0;
3003 if (strchr("cdy><", c)) {
3004 // these cmds operate on whole lines
3005 p = q = begin_line(p);
3006 for (cnt = 1; cnt < cmdcnt; cnt++) {
3010 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3011 // These cmds operate on char positions
3012 do_cmd(c); // execute movement cmd
3014 } else if (strchr("wW", c)) {
3015 do_cmd(c); // execute movement cmd
3016 // if we are at the next word's first char
3017 // step back one char
3018 // but check the possibilities when it is true
3019 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3020 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3021 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3022 dot--; // move back off of next word
3023 if (dot > text && *dot == '\n')
3024 dot--; // stay off NL
3026 } else if (strchr("H-k{", c)) {
3027 // these operate on multi-lines backwards
3028 q = end_line(dot); // find NL
3029 do_cmd(c); // execute movement cmd
3032 } else if (strchr("L+j}\r\n", c)) {
3033 // these operate on multi-lines forwards
3034 p = begin_line(dot);
3035 do_cmd(c); // execute movement cmd
3036 dot_end(); // find NL
3039 // nothing -- this causes any other values of c to
3040 // represent the one-character range under the
3041 // cursor. this is correct for ' ' and 'l', but
3042 // perhaps no others.
3051 // backward char movements don't include start position
3052 if (q > p && strchr("^0bBh\b\177", c)) q--;
3055 for (t = p; t <= q; t++) {
3068 //---------------------------------------------------------------------
3069 //----- the Ascii Chart -----------------------------------------------
3070 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3071 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3072 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3073 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3074 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3075 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3076 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3077 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3078 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3079 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3080 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3081 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3082 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3083 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3084 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3085 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3086 //---------------------------------------------------------------------
3088 //----- Execute a Vi Command -----------------------------------
3089 static void do_cmd(int c)
3091 char *p, *q, *save_dot;
3097 // c1 = c; // quiet the compiler
3098 // cnt = yf = 0; // quiet the compiler
3099 // p = q = save_dot = buf; // quiet the compiler
3100 memset(buf, '\0', sizeof(buf));
3104 // if this is a cursor key, skip these checks
3112 case KEYCODE_PAGEUP:
3113 case KEYCODE_PAGEDOWN:
3114 case KEYCODE_DELETE:
3118 if (cmd_mode == 2) {
3119 // flip-flop Insert/Replace mode
3120 if (c == KEYCODE_INSERT)
3122 // we are 'R'eplacing the current *dot with new char
3124 // don't Replace past E-o-l
3125 cmd_mode = 1; // convert to insert
3126 undo_queue_commit();
3128 if (1 <= c || Isprint(c)) {
3130 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3131 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3136 if (cmd_mode == 1) {
3137 // hitting "Insert" twice means "R" replace mode
3138 if (c == KEYCODE_INSERT) goto dc5;
3139 // insert the char c at "dot"
3140 if (1 <= c || Isprint(c)) {
3141 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3156 #if ENABLE_FEATURE_VI_CRASHME
3157 case 0x14: // dc4 ctrl-T
3158 crashme = (crashme == 0) ? 1 : 0;
3188 default: // unrecognized command
3191 not_implemented(buf);
3192 end_cmd_q(); // stop adding to q
3193 case 0x00: // nul- ignore
3195 case 2: // ctrl-B scroll up full screen
3196 case KEYCODE_PAGEUP: // Cursor Key Page Up
3197 dot_scroll(rows - 2, -1);
3199 case 4: // ctrl-D scroll down half screen
3200 dot_scroll((rows - 2) / 2, 1);
3202 case 5: // ctrl-E scroll down one line
3205 case 6: // ctrl-F scroll down full screen
3206 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3207 dot_scroll(rows - 2, 1);
3209 case 7: // ctrl-G show current status
3210 last_status_cksum = 0; // force status update
3212 case 'h': // h- move left
3213 case KEYCODE_LEFT: // cursor key Left
3214 case 8: // ctrl-H- move left (This may be ERASE char)
3215 case 0x7f: // DEL- move left (This may be ERASE char)
3218 } while (--cmdcnt > 0);
3220 case 10: // Newline ^J
3221 case 'j': // j- goto next line, same col
3222 case KEYCODE_DOWN: // cursor key Down
3224 dot_next(); // go to next B-o-l
3225 // try stay in same col
3226 dot = move_to_col(dot, ccol + offset);
3227 } while (--cmdcnt > 0);
3229 case 12: // ctrl-L force redraw whole screen
3230 case 18: // ctrl-R force redraw
3231 redraw(TRUE); // this will redraw the entire display
3233 case 13: // Carriage Return ^M
3234 case '+': // +- goto next line
3238 } while (--cmdcnt > 0);
3240 case 21: // ctrl-U scroll up half screen
3241 dot_scroll((rows - 2) / 2, -1);
3243 case 25: // ctrl-Y scroll up one line
3249 cmd_mode = 0; // stop inserting
3250 undo_queue_commit();
3252 last_status_cksum = 0; // force status update
3254 case ' ': // move right
3255 case 'l': // move right
3256 case KEYCODE_RIGHT: // Cursor Key Right
3259 } while (--cmdcnt > 0);
3261 #if ENABLE_FEATURE_VI_YANKMARK
3262 case '"': // "- name a register to use for Delete/Yank
3263 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3264 if ((unsigned)c1 <= 25) { // a-z?
3270 case '\'': // '- goto a specific mark
3271 c1 = (get_one_char() | 0x20);
3272 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3276 if (text <= q && q < end) {
3278 dot_begin(); // go to B-o-l
3281 } else if (c1 == '\'') { // goto previous context
3282 dot = swap_context(dot); // swap current and previous context
3283 dot_begin(); // go to B-o-l
3289 case 'm': // m- Mark a line
3290 // this is really stupid. If there are any inserts or deletes
3291 // between text[0] and dot then this mark will not point to the
3292 // correct location! It could be off by many lines!
3293 // Well..., at least its quick and dirty.
3294 c1 = (get_one_char() | 0x20) - 'a';
3295 if ((unsigned)c1 <= 25) { // a-z?
3296 // remember the line
3302 case 'P': // P- Put register before
3303 case 'p': // p- put register after
3306 status_line_bold("Nothing in register %c", what_reg());
3309 // are we putting whole lines or strings
3310 if (strchr(p, '\n') != NULL) {
3312 dot_begin(); // putting lines- Put above
3315 // are we putting after very last line?
3316 if (end_line(dot) == (end - 1)) {
3317 dot = end; // force dot to end of text[]
3319 dot_next(); // next line, then put before
3324 dot_right(); // move to right, can move to NL
3326 string_insert(dot, p, ALLOW_UNDO); // insert the string
3327 end_cmd_q(); // stop adding to q
3329 case 'U': // U- Undo; replace current line with original version
3330 if (reg[Ureg] != NULL) {
3331 p = begin_line(dot);
3333 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3334 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3339 #endif /* FEATURE_VI_YANKMARK */
3340 #if ENABLE_FEATURE_VI_UNDO
3341 case 'u': // u- undo last operation
3345 case '$': // $- goto end of line
3346 case KEYCODE_END: // Cursor Key End
3348 dot = end_line(dot);
3354 case '%': // %- find matching char of pair () [] {}
3355 for (q = dot; q < end && *q != '\n'; q++) {
3356 if (strchr("()[]{}", *q) != NULL) {
3357 // we found half of a pair
3358 p = find_pair(q, *q);
3370 case 'f': // f- forward to a user specified char
3371 last_forward_char = get_one_char(); // get the search char
3373 // dont separate these two commands. 'f' depends on ';'
3375 //**** fall through to ... ';'
3376 case ';': // ;- look at rest of line for last forward char
3378 if (last_forward_char == 0)
3381 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3384 if (*q == last_forward_char)
3386 } while (--cmdcnt > 0);
3388 case ',': // repeat latest 'f' in opposite direction
3389 if (last_forward_char == 0)
3393 while (q >= text && *q != '\n' && *q != last_forward_char) {
3396 if (q >= text && *q == last_forward_char)
3398 } while (--cmdcnt > 0);
3401 case '-': // -- goto prev line
3405 } while (--cmdcnt > 0);
3407 #if ENABLE_FEATURE_VI_DOT_CMD
3408 case '.': // .- repeat the last modifying command
3409 // Stuff the last_modifying_cmd back into stdin
3410 // and let it be re-executed.
3412 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
3416 #if ENABLE_FEATURE_VI_SEARCH
3417 case '?': // /- search for a pattern
3418 case '/': // /- search for a pattern
3421 q = get_input_line(buf); // get input line- use "status line"
3422 if (q[0] && !q[1]) {
3423 if (last_search_pattern[0])
3424 last_search_pattern[0] = c;
3425 goto dc3; // if no pat re-use old pat
3427 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3428 // there is a new pat
3429 free(last_search_pattern);
3430 last_search_pattern = xstrdup(q);
3431 goto dc3; // now find the pattern
3433 // user changed mind and erased the "/"- do nothing
3435 case 'N': // N- backward search for last pattern
3436 dir = BACK; // assume BACKWARD search
3438 if (last_search_pattern[0] == '?') {
3442 goto dc4; // now search for pattern
3444 case 'n': // n- repeat search for last pattern
3445 // search rest of text[] starting at next char
3446 // if search fails return orignal "p" not the "p+1" address
3450 dir = FORWARD; // assume FORWARD search
3452 if (last_search_pattern[0] == '?') {
3457 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3459 dot = q; // good search, update "dot"
3463 // no pattern found between "dot" and "end"- continue at top
3468 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3469 if (q != NULL) { // found something
3470 dot = q; // found new pattern- goto it
3471 msg = "search hit BOTTOM, continuing at TOP";
3473 msg = "search hit TOP, continuing at BOTTOM";
3476 msg = "Pattern not found";
3480 status_line_bold("%s", msg);
3481 } while (--cmdcnt > 0);
3483 case '{': // {- move backward paragraph
3484 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3485 if (q != NULL) { // found blank line
3486 dot = next_line(q); // move to next blank line
3489 case '}': // }- move forward paragraph
3490 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3491 if (q != NULL) { // found blank line
3492 dot = next_line(q); // move to next blank line
3495 #endif /* FEATURE_VI_SEARCH */
3496 case '0': // 0- goto beginning of line
3506 if (c == '0' && cmdcnt < 1) {
3507 dot_begin(); // this was a standalone zero
3509 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3512 case ':': // :- the colon mode commands
3513 p = get_input_line(":"); // get input line- use "status line"
3514 colon(p); // execute the command
3516 case '<': // <- Left shift something
3517 case '>': // >- Right shift something
3518 cnt = count_lines(text, dot); // remember what line we are on
3519 c1 = get_one_char(); // get the type of thing to delete
3520 find_range(&p, &q, c1);
3521 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3524 i = count_lines(p, q); // # of lines we are shifting
3525 for ( ; i > 0; i--, p = next_line(p)) {
3527 // shift left- remove tab or 8 spaces
3529 // shrink buffer 1 char
3530 text_hole_delete(p, p, NO_UNDO);
3531 } else if (*p == ' ') {
3532 // we should be calculating columns, not just SPACE
3533 for (j = 0; *p == ' ' && j < tabstop; j++) {
3534 text_hole_delete(p, p, NO_UNDO);
3537 } else if (c == '>') {
3538 // shift right -- add tab or 8 spaces
3539 char_insert(p, '\t', ALLOW_UNDO);
3542 dot = find_line(cnt); // what line were we on
3544 end_cmd_q(); // stop adding to q
3546 case 'A': // A- append at e-o-l
3547 dot_end(); // go to e-o-l
3548 //**** fall through to ... 'a'
3549 case 'a': // a- append after current char
3554 case 'B': // B- back a blank-delimited Word
3555 case 'E': // E- end of a blank-delimited word
3556 case 'W': // W- forward a blank-delimited word
3561 if (c == 'W' || isspace(dot[dir])) {
3562 dot = skip_thing(dot, 1, dir, S_TO_WS);
3563 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3566 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3567 } while (--cmdcnt > 0);
3569 case 'C': // C- Change to e-o-l
3570 case 'D': // D- delete to e-o-l
3572 dot = dollar_line(dot); // move to before NL
3573 // copy text into a register and delete
3574 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3576 goto dc_i; // start inserting
3577 #if ENABLE_FEATURE_VI_DOT_CMD
3579 end_cmd_q(); // stop adding to q
3582 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3583 c1 = get_one_char();
3586 // c1 < 0 if the key was special. Try "g<up-arrow>"
3587 // TODO: if Unicode?
3588 buf[1] = (c1 >= 0 ? c1 : '*');
3590 not_implemented(buf);
3596 case 'G': // G- goto to a line number (default= E-O-F)
3597 dot = end - 1; // assume E-O-F
3599 dot = find_line(cmdcnt); // what line is #cmdcnt
3603 case 'H': // H- goto top line on screen
3605 if (cmdcnt > (rows - 1)) {
3606 cmdcnt = (rows - 1);
3613 case 'I': // I- insert before first non-blank
3616 //**** fall through to ... 'i'
3617 case 'i': // i- insert before current char
3618 case KEYCODE_INSERT: // Cursor Key Insert
3620 cmd_mode = 1; // start inserting
3621 undo_queue_commit(); // commit queue when cmd_mode changes
3623 case 'J': // J- join current and next lines together
3625 dot_end(); // move to NL
3626 if (dot < end - 1) { // make sure not last char in text[]
3627 #if ENABLE_FEATURE_VI_UNDO
3628 undo_push(dot, 1, UNDO_DEL);
3629 *dot++ = ' '; // replace NL with space
3630 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3635 while (isblank(*dot)) { // delete leading WS
3636 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3639 } while (--cmdcnt > 0);
3640 end_cmd_q(); // stop adding to q
3642 case 'L': // L- goto bottom line on screen
3644 if (cmdcnt > (rows - 1)) {
3645 cmdcnt = (rows - 1);
3653 case 'M': // M- goto middle line on screen
3655 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3656 dot = next_line(dot);
3658 case 'O': // O- open a empty line above
3660 p = begin_line(dot);
3661 if (p[-1] == '\n') {
3663 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3665 dot = char_insert(dot, '\n', ALLOW_UNDO);
3668 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3673 case 'R': // R- continuous Replace char
3676 undo_queue_commit();
3678 case KEYCODE_DELETE:
3680 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3682 case 'X': // X- delete char before dot
3683 case 'x': // x- delete the current char
3684 case 's': // s- substitute the current char
3689 if (dot[dir] != '\n') {
3691 dot--; // delete prev char
3692 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3694 } while (--cmdcnt > 0);
3695 end_cmd_q(); // stop adding to q
3697 goto dc_i; // start inserting
3699 case 'Z': // Z- if modified, {write}; exit
3700 // ZZ means to save file (if necessary), then exit
3701 c1 = get_one_char();
3706 if (modified_count) {
3707 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3708 status_line_bold("'%s' is read only", current_filename);
3711 cnt = file_write(current_filename, text, end - 1);
3714 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3715 } else if (cnt == (end - 1 - text + 1)) {
3722 case '^': // ^- move to first non-blank on line
3726 case 'b': // b- back a word
3727 case 'e': // e- end of word
3732 if ((dot + dir) < text || (dot + dir) > end - 1)
3735 if (isspace(*dot)) {
3736 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3738 if (isalnum(*dot) || *dot == '_') {
3739 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3740 } else if (ispunct(*dot)) {
3741 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3743 } while (--cmdcnt > 0);
3745 case 'c': // c- change something
3746 case 'd': // d- delete something
3747 #if ENABLE_FEATURE_VI_YANKMARK
3748 case 'y': // y- yank something
3749 case 'Y': // Y- Yank a line
3752 int yf, ml, whole = 0;
3753 yf = YANKDEL; // assume either "c" or "d"
3754 #if ENABLE_FEATURE_VI_YANKMARK
3755 if (c == 'y' || c == 'Y')
3760 c1 = get_one_char(); // get the type of thing to delete
3761 // determine range, and whether it spans lines
3762 ml = find_range(&p, &q, c1);
3764 if (c1 == 27) { // ESC- user changed mind and wants out
3765 c = c1 = 27; // Escape- do nothing
3766 } else if (strchr("wW", c1)) {
3768 // don't include trailing WS as part of word
3769 while (isblank(*q)) {
3770 if (q <= text || q[-1] == '\n')
3775 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3776 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3777 // partial line copy text into a register and delete
3778 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3779 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3780 // whole line copy text into a register and delete
3781 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3784 // could not recognize object
3785 c = c1 = 27; // error-
3791 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3792 // on the last line of file don't move to prev line
3793 if (whole && dot != (end-1)) {
3796 } else if (c == 'd') {
3802 // if CHANGING, not deleting, start inserting after the delete
3804 strcpy(buf, "Change");
3805 goto dc_i; // start inserting
3808 strcpy(buf, "Delete");
3810 #if ENABLE_FEATURE_VI_YANKMARK
3811 if (c == 'y' || c == 'Y') {
3812 strcpy(buf, "Yank");
3816 for (cnt = 0; p <= q; p++) {
3820 status_line("%s %u lines (%u chars) using [%c]",
3821 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3823 end_cmd_q(); // stop adding to q
3827 case 'k': // k- goto prev line, same col
3828 case KEYCODE_UP: // cursor key Up
3831 dot = move_to_col(dot, ccol + offset); // try stay in same col
3832 } while (--cmdcnt > 0);
3834 case 'r': // r- replace the current char with user input
3835 c1 = get_one_char(); // get the replacement char
3837 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3838 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3841 end_cmd_q(); // stop adding to q
3843 case 't': // t- move to char prior to next x
3844 last_forward_char = get_one_char();
3846 if (*dot == last_forward_char)
3848 last_forward_char = 0;
3850 case 'w': // w- forward a word
3852 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3853 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3854 } else if (ispunct(*dot)) { // we are on PUNCT
3855 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3858 dot++; // move over word
3859 if (isspace(*dot)) {
3860 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3862 } while (--cmdcnt > 0);
3865 c1 = get_one_char(); // get the replacement char
3868 cnt = (rows - 2) / 2; // put dot at center
3870 cnt = rows - 2; // put dot at bottom
3871 screenbegin = begin_line(dot); // start dot at top
3872 dot_scroll(cnt, -1);
3874 case '|': // |- move to column "cmdcnt"
3875 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3877 case '~': // ~- flip the case of letters a-z -> A-Z
3879 #if ENABLE_FEATURE_VI_UNDO
3880 if (islower(*dot)) {
3881 undo_push(dot, 1, UNDO_DEL);
3882 *dot = toupper(*dot);
3883 undo_push(dot, 1, UNDO_INS_CHAIN);
3884 } else if (isupper(*dot)) {
3885 undo_push(dot, 1, UNDO_DEL);
3886 *dot = tolower(*dot);
3887 undo_push(dot, 1, UNDO_INS_CHAIN);
3890 if (islower(*dot)) {
3891 *dot = toupper(*dot);
3893 } else if (isupper(*dot)) {
3894 *dot = tolower(*dot);
3899 } while (--cmdcnt > 0);
3900 end_cmd_q(); // stop adding to q
3902 //----- The Cursor and Function Keys -----------------------------
3903 case KEYCODE_HOME: // Cursor Key Home
3906 // The Fn keys could point to do_macro which could translate them
3908 case KEYCODE_FUN1: // Function Key F1
3909 case KEYCODE_FUN2: // Function Key F2
3910 case KEYCODE_FUN3: // Function Key F3
3911 case KEYCODE_FUN4: // Function Key F4
3912 case KEYCODE_FUN5: // Function Key F5
3913 case KEYCODE_FUN6: // Function Key F6
3914 case KEYCODE_FUN7: // Function Key F7
3915 case KEYCODE_FUN8: // Function Key F8
3916 case KEYCODE_FUN9: // Function Key F9
3917 case KEYCODE_FUN10: // Function Key F10
3918 case KEYCODE_FUN11: // Function Key F11
3919 case KEYCODE_FUN12: // Function Key F12
3925 // if text[] just became empty, add back an empty line
3927 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
3930 // it is OK for dot to exactly equal to end, otherwise check dot validity
3932 dot = bound_dot(dot); // make sure "dot" is valid
3934 #if ENABLE_FEATURE_VI_YANKMARK
3935 check_context(c); // update the current context
3939 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3940 cnt = dot - begin_line(dot);
3941 // Try to stay off of the Newline
3942 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3946 // NB! the CRASHME code is unmaintained, and doesn't currently build
3947 #if ENABLE_FEATURE_VI_CRASHME
3948 static int totalcmds = 0;
3949 static int Mp = 85; // Movement command Probability
3950 static int Np = 90; // Non-movement command Probability
3951 static int Dp = 96; // Delete command Probability
3952 static int Ip = 97; // Insert command Probability
3953 static int Yp = 98; // Yank command Probability
3954 static int Pp = 99; // Put command Probability
3955 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3956 static const char chars[20] = "\t012345 abcdABCD-=.$";
3957 static const char *const words[20] = {
3958 "this", "is", "a", "test",
3959 "broadcast", "the", "emergency", "of",
3960 "system", "quick", "brown", "fox",
3961 "jumped", "over", "lazy", "dogs",
3962 "back", "January", "Febuary", "March"
3964 static const char *const lines[20] = {
3965 "You should have received a copy of the GNU General Public License\n",
3966 "char c, cm, *cmd, *cmd1;\n",
3967 "generate a command by percentages\n",
3968 "Numbers may be typed as a prefix to some commands.\n",
3969 "Quit, discarding changes!\n",
3970 "Forced write, if permission originally not valid.\n",
3971 "In general, any ex or ed command (such as substitute or delete).\n",
3972 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3973 "Please get w/ me and I will go over it with you.\n",
3974 "The following is a list of scheduled, committed changes.\n",
3975 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3976 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3977 "Any question about transactions please contact Sterling Huxley.\n",
3978 "I will try to get back to you by Friday, December 31.\n",
3979 "This Change will be implemented on Friday.\n",
3980 "Let me know if you have problems accessing this;\n",
3981 "Sterling Huxley recently added you to the access list.\n",
3982 "Would you like to go to lunch?\n",
3983 "The last command will be automatically run.\n",
3984 "This is too much english for a computer geek.\n",
3986 static char *multilines[20] = {
3987 "You should have received a copy of the GNU General Public License\n",
3988 "char c, cm, *cmd, *cmd1;\n",
3989 "generate a command by percentages\n",
3990 "Numbers may be typed as a prefix to some commands.\n",
3991 "Quit, discarding changes!\n",
3992 "Forced write, if permission originally not valid.\n",
3993 "In general, any ex or ed command (such as substitute or delete).\n",
3994 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3995 "Please get w/ me and I will go over it with you.\n",
3996 "The following is a list of scheduled, committed changes.\n",
3997 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3998 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3999 "Any question about transactions please contact Sterling Huxley.\n",
4000 "I will try to get back to you by Friday, December 31.\n",
4001 "This Change will be implemented on Friday.\n",
4002 "Let me know if you have problems accessing this;\n",
4003 "Sterling Huxley recently added you to the access list.\n",
4004 "Would you like to go to lunch?\n",
4005 "The last command will be automatically run.\n",
4006 "This is too much english for a computer geek.\n",
4009 // create a random command to execute
4010 static void crash_dummy()
4012 static int sleeptime; // how long to pause between commands
4013 char c, cm, *cmd, *cmd1;
4014 int i, cnt, thing, rbi, startrbi, percent;
4016 // "dot" movement commands
4017 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4019 // is there already a command running?
4020 if (readbuffer[0] > 0)
4023 readbuffer[0] = 'X';
4025 sleeptime = 0; // how long to pause between commands
4026 memset(readbuffer, '\0', sizeof(readbuffer));
4027 // generate a command by percentages
4028 percent = (int) lrand48() % 100; // get a number from 0-99
4029 if (percent < Mp) { // Movement commands
4030 // available commands
4033 } else if (percent < Np) { // non-movement commands
4034 cmd = "mz<>\'\""; // available commands
4036 } else if (percent < Dp) { // Delete commands
4037 cmd = "dx"; // available commands
4039 } else if (percent < Ip) { // Inset commands
4040 cmd = "iIaAsrJ"; // available commands
4042 } else if (percent < Yp) { // Yank commands
4043 cmd = "yY"; // available commands
4045 } else if (percent < Pp) { // Put commands
4046 cmd = "pP"; // available commands
4049 // We do not know how to handle this command, try again
4053 // randomly pick one of the available cmds from "cmd[]"
4054 i = (int) lrand48() % strlen(cmd);
4056 if (strchr(":\024", cm))
4057 goto cd0; // dont allow colon or ctrl-T commands
4058 readbuffer[rbi++] = cm; // put cmd into input buffer
4060 // now we have the command-
4061 // there are 1, 2, and multi char commands
4062 // find out which and generate the rest of command as necessary
4063 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4064 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4065 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4066 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4068 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4070 readbuffer[rbi++] = c; // add movement to input buffer
4072 if (strchr("iIaAsc", cm)) { // multi-char commands
4074 // change some thing
4075 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4077 readbuffer[rbi++] = c; // add movement to input buffer
4079 thing = (int) lrand48() % 4; // what thing to insert
4080 cnt = (int) lrand48() % 10; // how many to insert
4081 for (i = 0; i < cnt; i++) {
4082 if (thing == 0) { // insert chars
4083 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4084 } else if (thing == 1) { // insert words
4085 strcat(readbuffer, words[(int) lrand48() % 20]);
4086 strcat(readbuffer, " ");
4087 sleeptime = 0; // how fast to type
4088 } else if (thing == 2) { // insert lines
4089 strcat(readbuffer, lines[(int) lrand48() % 20]);
4090 sleeptime = 0; // how fast to type
4091 } else { // insert multi-lines
4092 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4093 sleeptime = 0; // how fast to type
4096 strcat(readbuffer, ESC);
4098 readbuffer[0] = strlen(readbuffer + 1);
4102 mysleep(sleeptime); // sleep 1/100 sec
4105 // test to see if there are any errors
4106 static void crash_test()
4108 static time_t oldtim;
4115 strcat(msg, "end<text ");
4117 if (end > textend) {
4118 strcat(msg, "end>textend ");
4121 strcat(msg, "dot<text ");
4124 strcat(msg, "dot>end ");
4126 if (screenbegin < text) {
4127 strcat(msg, "screenbegin<text ");
4129 if (screenbegin > end - 1) {
4130 strcat(msg, "screenbegin>end-1 ");
4134 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4135 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4137 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4138 if (d[0] == '\n' || d[0] == '\r')
4143 if (tim >= (oldtim + 3)) {
4144 sprintf(status_buffer,
4145 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4146 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4152 static void edit_file(char *fn)
4154 #if ENABLE_FEATURE_VI_YANKMARK
4155 #define cur_line edit_file__cur_line
4158 #if ENABLE_FEATURE_VI_USE_SIGNALS
4162 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4166 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4167 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4168 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4170 write1(ESC"[999;999H" ESC"[6n");
4172 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4173 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4174 uint32_t rc = (k >> 32);
4175 columns = (rc & 0x7fff);
4176 if (columns > MAX_SCR_COLS)
4177 columns = MAX_SCR_COLS;
4178 rows = ((rc >> 16) & 0x7fff);
4179 if (rows > MAX_SCR_ROWS)
4180 rows = MAX_SCR_ROWS;
4184 new_screen(rows, columns); // get memory for virtual screen
4185 init_text_buffer(fn);
4187 #if ENABLE_FEATURE_VI_YANKMARK
4188 YDreg = 26; // default Yank/Delete reg
4189 // Ureg = 27; - const // hold orig line for "U" cmd
4190 mark[26] = mark[27] = text; // init "previous context"
4193 last_forward_char = '\0';
4194 #if ENABLE_FEATURE_VI_CRASHME
4195 last_input_char = '\0';
4200 #if ENABLE_FEATURE_VI_USE_SIGNALS
4201 signal(SIGWINCH, winch_handler);
4202 signal(SIGTSTP, tstp_handler);
4203 sig = sigsetjmp(restart, 1);
4205 screenbegin = dot = text;
4207 // int_handler() can jump to "restart",
4208 // must install handler *after* initializing "restart"
4209 signal(SIGINT, int_handler);
4212 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4215 offset = 0; // no horizontal offset
4217 #if ENABLE_FEATURE_VI_DOT_CMD
4224 #if ENABLE_FEATURE_VI_COLON
4229 while ((p = initial_cmds[n]) != NULL) {
4232 p = strchr(q, '\n');
4239 free(initial_cmds[n]);
4240 initial_cmds[n] = NULL;
4245 redraw(FALSE); // dont force every col re-draw
4246 //------This is the main Vi cmd handling loop -----------------------
4247 while (editing > 0) {
4248 #if ENABLE_FEATURE_VI_CRASHME
4250 if ((end - text) > 1) {
4251 crash_dummy(); // generate a random command
4254 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
4260 c = get_one_char(); // get a cmd from user
4261 #if ENABLE_FEATURE_VI_CRASHME
4262 last_input_char = c;
4264 #if ENABLE_FEATURE_VI_YANKMARK
4265 // save a copy of the current line- for the 'U" command
4266 if (begin_line(dot) != cur_line) {
4267 cur_line = begin_line(dot);
4268 text_yank(begin_line(dot), end_line(dot), Ureg);
4271 #if ENABLE_FEATURE_VI_DOT_CMD
4272 // If c is a command that changes text[],
4273 // (re)start remembering the input for the "." command.
4275 && ioq_start == NULL
4276 && cmd_mode == 0 // command mode
4277 && c > '\0' // exclude NUL and non-ASCII chars
4278 && c < 0x7f // (Unicode and such)
4279 && strchr(modifying_cmds, c)
4284 do_cmd(c); // execute the user command
4286 // poll to see if there is input already waiting. if we are
4287 // not able to display output fast enough to keep up, skip
4288 // the display update until we catch up with input.
4289 if (!readbuffer[0] && mysleep(0) == 0) {
4290 // no input pending - so update output
4294 #if ENABLE_FEATURE_VI_CRASHME
4296 crash_test(); // test editor variables
4299 //-------------------------------------------------------------------
4301 go_bottom_and_clear_to_eol();
4306 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4307 int vi_main(int argc, char **argv)
4313 #if ENABLE_FEATURE_VI_UNDO
4314 //undo_stack_tail = NULL; - already is
4315 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4316 undo_queue_state = UNDO_EMPTY;
4317 //undo_q = 0; - already is
4321 #if ENABLE_FEATURE_VI_CRASHME
4322 srand((long) getpid());
4324 #ifdef NO_SUCH_APPLET_YET
4325 // if we aren't "vi", we are "view"
4326 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4327 SET_READONLY_MODE(readonly_mode);
4331 // autoindent is not default in vim 7.3
4332 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4333 // 1- process $HOME/.exrc file (not inplemented yet)
4334 // 2- process EXINIT variable from environment
4335 // 3- process command line args
4336 #if ENABLE_FEATURE_VI_COLON
4338 char *p = getenv("EXINIT");
4340 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4343 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4345 #if ENABLE_FEATURE_VI_CRASHME
4350 #if ENABLE_FEATURE_VI_READONLY
4351 case 'R': // Read-only flag
4352 SET_READONLY_MODE(readonly_mode);
4355 #if ENABLE_FEATURE_VI_COLON
4356 case 'c': // cmd line vi command
4358 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4370 // The argv array can be used by the ":next" and ":rewind" commands
4374 //----- This is the main file handling loop --------------
4377 // "Save cursor, use alternate screen buffer, clear screen"
4378 write1(ESC"[?1049h");
4380 edit_file(argv[optind]); // param might be NULL
4381 if (++optind >= argc)
4384 // "Use normal screen buffer, restore cursor"
4385 write1(ESC"[?1049l");
4386 //-----------------------------------------------------------