1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (23 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
241 #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
243 ///* Cursor up and down */
244 //#define ESC_CURSOR_UP ESC"[A"
245 //#define ESC_CURSOR_DOWN "\n"
247 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
248 // cmds modifying text[]
249 // vda: removed "aAiIs" as they switch us into insert mode
250 // and remembering input for replay after them makes no sense
251 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
257 FORWARD = 1, // code depends on "1" for array index
258 BACK = -1, // code depends on "-1" for array index
259 LIMITED = 0, // char_search() only current line
260 FULL = 1, // char_search() to the end/beginning of entire text
262 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
263 S_TO_WS = 2, // used in skip_thing() for moving "dot"
264 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
265 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
266 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
270 /* vi.c expects chars to be unsigned. */
271 /* busybox build system provides that, but it's better */
272 /* to audit and fix the source */
275 /* many references - keep near the top of globals */
276 char *text, *end; // pointers to the user data in memory
277 char *dot; // where all the action takes place
278 int text_size; // size of the allocated buffer
282 #define VI_AUTOINDENT 1
283 #define VI_SHOWMATCH 2
284 #define VI_IGNORECASE 4
285 #define VI_ERR_METHOD 8
286 #define autoindent (vi_setops & VI_AUTOINDENT)
287 #define showmatch (vi_setops & VI_SHOWMATCH )
288 #define ignorecase (vi_setops & VI_IGNORECASE)
289 /* indicate error with beep or flash */
290 #define err_method (vi_setops & VI_ERR_METHOD)
292 #if ENABLE_FEATURE_VI_READONLY
293 smallint readonly_mode;
294 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
295 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
296 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
298 #define SET_READONLY_FILE(flags) ((void)0)
299 #define SET_READONLY_MODE(flags) ((void)0)
300 #define UNSET_READONLY_FILE(flags) ((void)0)
303 smallint editing; // >0 while we are editing a file
304 // [code audit says "can be 0, 1 or 2 only"]
305 smallint cmd_mode; // 0=command 1=insert 2=replace
306 int modified_count; // buffer contents changed if !0
307 int last_modified_count; // = -1;
308 int save_argc; // how many file names on cmd line
309 int cmdcnt; // repetition count
310 unsigned rows, columns; // the terminal screen is this size
311 #if ENABLE_FEATURE_VI_ASK_TERMINAL
312 int get_rowcol_error;
314 int crow, ccol; // cursor is on Crow x Ccol
315 int offset; // chars scrolled off the screen to the left
316 int have_status_msg; // is default edit status needed?
317 // [don't make smallint!]
318 int last_status_cksum; // hash of current status line
319 char *current_filename;
320 char *screenbegin; // index into text[], of top line on the screen
321 char *screen; // pointer to the virtual screen buffer
322 int screensize; // and its size
324 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
325 char erase_char; // the users erase character
326 char last_input_char; // last char read from user
328 #if ENABLE_FEATURE_VI_DOT_CMD
329 smallint adding2q; // are we currently adding user input to q
330 int lmc_len; // length of last_modifying_cmd
331 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
333 #if ENABLE_FEATURE_VI_SEARCH
334 char *last_search_pattern; // last pattern from a '/' or '?' search
338 #if ENABLE_FEATURE_VI_YANKMARK
339 char *edit_file__cur_line;
341 int refresh__old_offset;
342 int format_edit_status__tot;
344 /* a few references only */
345 #if ENABLE_FEATURE_VI_YANKMARK
346 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
348 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
349 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
350 char *context_start, *context_end;
352 #if ENABLE_FEATURE_VI_USE_SIGNALS
353 sigjmp_buf restart; // int_handler() jumps to location remembered here
355 struct termios term_orig; // remember what the cooked mode was
356 #if ENABLE_FEATURE_VI_COLON
357 char *initial_cmds[3]; // currently 2 entries, NULL terminated
359 // Should be just enough to hold a key sequence,
360 // but CRASHME mode uses it as generated command buffer too
361 #if ENABLE_FEATURE_VI_CRASHME
362 char readbuffer[128];
364 char readbuffer[KEYCODE_BUFFER_SIZE];
366 #define STATUS_BUFFER_LEN 200
367 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
368 #if ENABLE_FEATURE_VI_DOT_CMD
369 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
371 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
373 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
374 #if ENABLE_FEATURE_VI_UNDO
375 // undo_push() operations
378 #define UNDO_INS_CHAIN 2
379 #define UNDO_DEL_CHAIN 3
380 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
381 #define UNDO_QUEUED_FLAG 4
382 #define UNDO_INS_QUEUED 4
383 #define UNDO_DEL_QUEUED 5
384 #define UNDO_USE_SPOS 32
385 #define UNDO_EMPTY 64
386 // Pass-through flags for functions that can be undone
389 #define ALLOW_UNDO_CHAIN 2
390 # if ENABLE_FEATURE_VI_UNDO_QUEUE
391 #define ALLOW_UNDO_QUEUED 3
392 char undo_queue_state;
394 char *undo_queue_spos; // Start position of queued operation
395 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
397 // If undo queuing disabled, don't invoke the missing queue logic
398 #define ALLOW_UNDO_QUEUED 1
402 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
403 int start; // Offset where the data should be restored/deleted
404 int length; // total data size
405 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
406 char undo_text[1]; // text that was deleted (if deletion)
408 #endif /* ENABLE_FEATURE_VI_UNDO */
410 #define G (*ptr_to_globals)
411 #define text (G.text )
412 #define text_size (G.text_size )
417 #define vi_setops (G.vi_setops )
418 #define editing (G.editing )
419 #define cmd_mode (G.cmd_mode )
420 #define modified_count (G.modified_count )
421 #define last_modified_count (G.last_modified_count)
422 #define save_argc (G.save_argc )
423 #define cmdcnt (G.cmdcnt )
424 #define rows (G.rows )
425 #define columns (G.columns )
426 #define crow (G.crow )
427 #define ccol (G.ccol )
428 #define offset (G.offset )
429 #define status_buffer (G.status_buffer )
430 #define have_status_msg (G.have_status_msg )
431 #define last_status_cksum (G.last_status_cksum )
432 #define current_filename (G.current_filename )
433 #define screen (G.screen )
434 #define screensize (G.screensize )
435 #define screenbegin (G.screenbegin )
436 #define tabstop (G.tabstop )
437 #define last_forward_char (G.last_forward_char )
438 #define erase_char (G.erase_char )
439 #define last_input_char (G.last_input_char )
440 #if ENABLE_FEATURE_VI_READONLY
441 #define readonly_mode (G.readonly_mode )
443 #define readonly_mode 0
445 #define adding2q (G.adding2q )
446 #define lmc_len (G.lmc_len )
448 #define ioq_start (G.ioq_start )
449 #define last_search_pattern (G.last_search_pattern)
451 #define edit_file__cur_line (G.edit_file__cur_line)
452 #define refresh__old_offset (G.refresh__old_offset)
453 #define format_edit_status__tot (G.format_edit_status__tot)
455 #define YDreg (G.YDreg )
456 //#define Ureg (G.Ureg )
457 #define mark (G.mark )
458 #define context_start (G.context_start )
459 #define context_end (G.context_end )
460 #define restart (G.restart )
461 #define term_orig (G.term_orig )
462 #define initial_cmds (G.initial_cmds )
463 #define readbuffer (G.readbuffer )
464 #define scr_out_buf (G.scr_out_buf )
465 #define last_modifying_cmd (G.last_modifying_cmd )
466 #define get_input_line__buf (G.get_input_line__buf)
468 #if ENABLE_FEATURE_VI_UNDO
469 #define undo_stack_tail (G.undo_stack_tail )
470 # if ENABLE_FEATURE_VI_UNDO_QUEUE
471 #define undo_queue_state (G.undo_queue_state)
472 #define undo_q (G.undo_q )
473 #define undo_queue (G.undo_queue )
474 #define undo_queue_spos (G.undo_queue_spos )
478 #define INIT_G() do { \
479 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
480 last_modified_count = -1; \
481 /* "" but has space for 2 chars: */ \
482 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
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);
565 erase_char = term_orig.c_cc[VERASE];
568 static void cookmode(void)
571 tcsetattr_stdin_TCSANOW(&term_orig);
574 //----- Terminal Drawing ---------------------------------------
575 // The terminal is made up of 'rows' line of 'columns' columns.
576 // classically this would be 24 x 80.
577 // screen coordinates
583 // 23,0 ... 23,79 <- status line
585 //----- Move the cursor to row x col (count from 0, not 1) -------
586 static void place_cursor(int row, int col)
588 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
590 if (row < 0) row = 0;
591 if (row >= rows) row = rows - 1;
592 if (col < 0) col = 0;
593 if (col >= columns) col = columns - 1;
595 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
599 //----- Erase from cursor to end of line -----------------------
600 static void clear_to_eol(void)
602 write1(ESC_CLEAR2EOL);
605 static void go_bottom_and_clear_to_eol(void)
607 place_cursor(rows - 1, 0);
611 //----- Start standout mode ------------------------------------
612 static void standout_start(void)
614 write1(ESC_BOLD_TEXT);
617 //----- End standout mode --------------------------------------
618 static void standout_end(void)
620 write1(ESC_NORM_TEXT);
623 //----- Text Movement Routines ---------------------------------
624 static char *begin_line(char *p) // return pointer to first char cur line
627 p = memrchr(text, '\n', p - text);
635 static char *end_line(char *p) // return pointer to NL of cur line
638 p = memchr(p, '\n', end - p - 1);
645 static char *dollar_line(char *p) // return pointer to just before NL line
648 // Try to stay off of the Newline
649 if (*p == '\n' && (p - begin_line(p)) > 0)
654 static char *prev_line(char *p) // return pointer first char prev line
656 p = begin_line(p); // goto beginning of cur line
657 if (p > text && p[-1] == '\n')
658 p--; // step to prev line
659 p = begin_line(p); // goto beginning of prev line
663 static char *next_line(char *p) // return pointer first char next line
666 if (p < end - 1 && *p == '\n')
667 p++; // step to next line
671 //----- Text Information Routines ------------------------------
672 static char *end_screen(void)
677 // find new bottom line
679 for (cnt = 0; cnt < rows - 2; cnt++)
685 // count line from start to stop
686 static int count_lines(char *start, char *stop)
691 if (stop < start) { // start and stop are backwards- reverse them
697 stop = end_line(stop);
698 while (start <= stop && start <= end - 1) {
699 start = end_line(start);
707 static char *find_line(int li) // find beginning of line #li
711 for (q = text; li > 1; li--) {
717 static int next_tabstop(int col)
719 return col + ((tabstop - 1) - (col % tabstop));
722 //----- Erase the Screen[] memory ------------------------------
723 static void screen_erase(void)
725 memset(screen, ' ', screensize); // clear new screen
728 //----- Synchronize the cursor to Dot --------------------------
729 static NOINLINE void sync_cursor(char *d, int *row, int *col)
731 char *beg_cur; // begin and end of "d" line
735 beg_cur = begin_line(d); // first char of cur line
737 if (beg_cur < screenbegin) {
738 // "d" is before top line on screen
739 // how many lines do we have to move
740 cnt = count_lines(beg_cur, screenbegin);
742 screenbegin = beg_cur;
743 if (cnt > (rows - 1) / 2) {
744 // we moved too many lines. put "dot" in middle of screen
745 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
746 screenbegin = prev_line(screenbegin);
750 char *end_scr; // begin and end of screen
751 end_scr = end_screen(); // last char of screen
752 if (beg_cur > end_scr) {
753 // "d" is after bottom line on screen
754 // how many lines do we have to move
755 cnt = count_lines(end_scr, beg_cur);
756 if (cnt > (rows - 1) / 2)
757 goto sc1; // too many lines
758 for (ro = 0; ro < cnt - 1; ro++) {
759 // move screen begin the same amount
760 screenbegin = next_line(screenbegin);
761 // now, move the end of screen
762 end_scr = next_line(end_scr);
763 end_scr = end_line(end_scr);
767 // "d" is on screen- find out which row
769 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
775 // find out what col "d" is on
777 while (tp < d) { // drive "co" to correct column
778 if (*tp == '\n') //vda || *tp == '\0')
781 // handle tabs like real vi
782 if (d == tp && cmd_mode) {
785 co = next_tabstop(co);
786 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
787 co++; // display as ^X, use 2 columns
793 // "co" is the column where "dot" is.
794 // The screen has "columns" columns.
795 // The currently displayed columns are 0+offset -- columns+ofset
796 // |-------------------------------------------------------------|
798 // offset | |------- columns ----------------|
800 // If "co" is already in this range then we do not have to adjust offset
801 // but, we do have to subtract the "offset" bias from "co".
802 // If "co" is outside this range then we have to change "offset".
803 // If the first char of a line is a tab the cursor will try to stay
804 // in column 7, but we have to set offset to 0.
806 if (co < 0 + offset) {
809 if (co >= columns + offset) {
810 offset = co - columns + 1;
812 // if the first char of the line is a tab, and "dot" is sitting on it
813 // force offset to 0.
814 if (d == beg_cur && *d == '\t') {
823 //----- Format a text[] line into a buffer ---------------------
824 static char* format_line(char *src /*, int li*/)
829 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
831 c = '~'; // char in col 0 in non-existent lines is '~'
833 while (co < columns + tabstop) {
834 // have we gone past the end?
839 if ((c & 0x80) && !Isprint(c)) {
842 if (c < ' ' || c == 0x7f) {
846 while ((co % tabstop) != (tabstop - 1)) {
854 c += '@'; // Ctrl-X -> 'X'
859 // discard scrolled-off-to-the-left portion,
860 // in tabstop-sized pieces
861 if (ofs >= tabstop && co >= tabstop) {
862 memmove(dest, dest + tabstop, co);
869 // check "short line, gigantic offset" case
872 // discard last scrolled off part
875 // fill the rest with spaces
877 memset(&dest[co], ' ', columns - co);
881 //----- Refresh the changed screen lines -----------------------
882 // Copy the source line from text[] into the buffer and note
883 // if the current screenline is different from the new buffer.
884 // If they differ then that line needs redrawing on the terminal.
886 static void refresh(int full_screen)
888 #define old_offset refresh__old_offset
891 char *tp, *sp; // pointer into text[] and screen[]
893 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
894 unsigned c = columns, r = rows;
895 query_screen_dimensions();
896 #if ENABLE_FEATURE_VI_USE_SIGNALS
897 full_screen |= (c - columns) | (r - rows);
899 if (c != columns || r != rows) {
901 // update screen memory since SIGWINCH won't have done it
902 new_screen(rows, columns);
906 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
907 tp = screenbegin; // index into text[] of top line
909 // compare text[] to screen[] and mark screen[] lines that need updating
910 for (li = 0; li < rows - 1; li++) {
911 int cs, ce; // column start & end
913 // format current text line
914 out_buf = format_line(tp /*, li*/);
916 // skip to the end of the current text[] line
918 char *t = memchr(tp, '\n', end - tp);
923 // see if there are any changes between virtual screen and out_buf
924 changed = FALSE; // assume no change
927 sp = &screen[li * columns]; // start of screen line
929 // force re-draw of every single column from 0 - columns-1
932 // compare newly formatted buffer with virtual screen
933 // look forward for first difference between buf and screen
934 for (; cs <= ce; cs++) {
935 if (out_buf[cs] != sp[cs]) {
936 changed = TRUE; // mark for redraw
941 // look backward for last difference between out_buf and screen
942 for (; ce >= cs; ce--) {
943 if (out_buf[ce] != sp[ce]) {
944 changed = TRUE; // mark for redraw
948 // now, cs is index of first diff, and ce is index of last diff
950 // if horz offset has changed, force a redraw
951 if (offset != old_offset) {
956 // make a sanity check of columns indexes
958 if (ce > columns - 1) ce = columns - 1;
959 if (cs > ce) { cs = 0; ce = columns - 1; }
960 // is there a change between virtual screen and out_buf
962 // copy changed part of buffer to virtual screen
963 memcpy(sp+cs, out_buf+cs, ce-cs+1);
964 place_cursor(li, cs);
965 // write line out to terminal
966 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
970 place_cursor(crow, ccol);
976 //----- Force refresh of all Lines -----------------------------
977 static void redraw(int full_screen)
979 // cursor to top,left; clear to the end of screen
980 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
981 screen_erase(); // erase the internal screen buffer
982 last_status_cksum = 0; // force status update
983 refresh(full_screen); // this will redraw the entire display
987 //----- Flash the screen --------------------------------------
988 static void flash(int h)
997 static void indicate_error(void)
999 #if ENABLE_FEATURE_VI_CRASHME
1010 //----- IO Routines --------------------------------------------
1011 static int readit(void) // read (maybe cursor) key from stdin
1017 // Wait for input. TIMEOUT = -1 makes read_key wait even
1018 // on nonblocking stdin.
1019 // Note: read_key sets errno to 0 on success.
1021 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1022 if (c == -1) { // EOF/error
1023 if (errno == EAGAIN) // paranoia
1025 go_bottom_and_clear_to_eol();
1026 cookmode(); // terminal to "cooked"
1027 bb_error_msg_and_die("can't read user input");
1032 static int get_one_char(void)
1036 #if ENABLE_FEATURE_VI_DOT_CMD
1038 // we are not adding to the q.
1039 // but, we may be reading from a q
1041 // there is no current q, read from STDIN
1042 c = readit(); // get the users input
1044 // there is a queue to get chars from first
1045 // careful with correct sign expansion!
1046 c = (unsigned char)*ioq++;
1048 // the end of the q, read from STDIN
1050 ioq_start = ioq = 0;
1051 c = readit(); // get the users input
1055 // adding STDIN chars to q
1056 c = readit(); // get the users input
1057 if (lmc_len >= MAX_INPUT_LEN - 1) {
1058 status_line_bold("last_modifying_cmd overrun");
1060 // add new char to q
1061 last_modifying_cmd[lmc_len++] = c;
1065 c = readit(); // get the users input
1066 #endif /* FEATURE_VI_DOT_CMD */
1070 // Get input line (uses "status line" area)
1071 static char *get_input_line(const char *prompt)
1073 // char [MAX_INPUT_LEN]
1074 #define buf get_input_line__buf
1079 strcpy(buf, prompt);
1080 last_status_cksum = 0; // force status update
1081 go_bottom_and_clear_to_eol();
1082 write1(prompt); // write out the :, /, or ? prompt
1085 while (i < MAX_INPUT_LEN) {
1087 if (c == '\n' || c == '\r' || c == 27)
1088 break; // this is end of input
1089 if (c == erase_char || c == 8 || c == 127) {
1090 // user wants to erase prev char
1092 write1("\b \b"); // erase char on screen
1093 if (i <= 0) // user backs up before b-o-l, exit
1095 } else if (c > 0 && c < 256) { // exclude Unicode
1096 // (TODO: need to handle Unicode)
1107 static void Hit_Return(void)
1112 write1("[Hit return to continue]");
1114 while ((c = get_one_char()) != '\n' && c != '\r')
1116 redraw(TRUE); // force redraw all
1119 //----- Draw the status line at bottom of the screen -------------
1120 // show file status on status line
1121 static int format_edit_status(void)
1123 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1125 #define tot format_edit_status__tot
1127 int cur, percent, ret, trunc_at;
1129 // modified_count is now a counter rather than a flag. this
1130 // helps reduce the amount of line counting we need to do.
1131 // (this will cause a mis-reporting of modified status
1132 // once every MAXINT editing operations.)
1134 // it would be nice to do a similar optimization here -- if
1135 // we haven't done a motion that could have changed which line
1136 // we're on, then we shouldn't have to do this count_lines()
1137 cur = count_lines(text, dot);
1139 // count_lines() is expensive.
1140 // Call it only if something was changed since last time
1142 if (modified_count != last_modified_count) {
1143 tot = cur + count_lines(dot, end - 1) - 1;
1144 last_modified_count = modified_count;
1147 // current line percent
1148 // ------------- ~~ ----------
1151 percent = (100 * cur) / tot;
1157 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1158 columns : STATUS_BUFFER_LEN-1;
1160 ret = snprintf(status_buffer, trunc_at+1,
1161 #if ENABLE_FEATURE_VI_READONLY
1162 "%c %s%s%s %d/%d %d%%",
1164 "%c %s%s %d/%d %d%%",
1166 cmd_mode_indicator[cmd_mode & 3],
1167 (current_filename != NULL ? current_filename : "No file"),
1168 #if ENABLE_FEATURE_VI_READONLY
1169 (readonly_mode ? " [Readonly]" : ""),
1171 (modified_count ? " [Modified]" : ""),
1174 if (ret >= 0 && ret < trunc_at)
1175 return ret; // it all fit
1177 return trunc_at; // had to truncate
1181 static int bufsum(char *buf, int count)
1184 char *e = buf + count;
1186 sum += (unsigned char) *buf++;
1190 static void show_status_line(void)
1192 int cnt = 0, cksum = 0;
1194 // either we already have an error or status message, or we
1196 if (!have_status_msg) {
1197 cnt = format_edit_status();
1198 cksum = bufsum(status_buffer, cnt);
1200 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1201 last_status_cksum = cksum; // remember if we have seen this line
1202 go_bottom_and_clear_to_eol();
1203 write1(status_buffer);
1204 if (have_status_msg) {
1205 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1207 have_status_msg = 0;
1210 have_status_msg = 0;
1212 place_cursor(crow, ccol); // put cursor back in correct place
1217 //----- format the status buffer, the bottom line of screen ------
1218 static void status_line(const char *format, ...)
1222 va_start(args, format);
1223 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1226 have_status_msg = 1;
1228 static void status_line_bold(const char *format, ...)
1232 va_start(args, format);
1233 strcpy(status_buffer, ESC_BOLD_TEXT);
1234 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1235 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1238 strcat(status_buffer, ESC_NORM_TEXT);
1241 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
1243 static void status_line_bold_errno(const char *fn)
1245 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1248 // copy s to buf, convert unprintable
1249 static void print_literal(char *buf, const char *s)
1263 c_is_no_print = (c & 0x80) && !Isprint(c);
1264 if (c_is_no_print) {
1265 strcpy(d, ESC_NORM_TEXT);
1266 d += sizeof(ESC_NORM_TEXT)-1;
1269 if (c < ' ' || c == 0x7f) {
1277 if (c_is_no_print) {
1278 strcpy(d, ESC_BOLD_TEXT);
1279 d += sizeof(ESC_BOLD_TEXT)-1;
1285 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1289 static void not_implemented(const char *s)
1291 char buf[MAX_INPUT_LEN];
1292 print_literal(buf, s);
1293 status_line_bold("'%s' is not implemented", buf);
1296 //----- Block insert/delete, undo ops --------------------------
1297 #if ENABLE_FEATURE_VI_YANKMARK
1298 static char *text_yank(char *p, char *q, int dest) // copy text into a register
1301 if (cnt < 0) { // they are backwards- reverse them
1305 free(reg[dest]); // if already a yank register, free it
1306 reg[dest] = xstrndup(p, cnt + 1);
1310 static char what_reg(void)
1314 c = 'D'; // default to D-reg
1315 if (0 <= YDreg && YDreg <= 25)
1316 c = 'a' + (char) YDreg;
1324 static void check_context(char cmd)
1326 // A context is defined to be "modifying text"
1327 // Any modifying command establishes a new context.
1329 if (dot < context_start || dot > context_end) {
1330 if (strchr(modifying_cmds, cmd) != NULL) {
1331 // we are trying to modify text[]- make this the current context
1332 mark[27] = mark[26]; // move cur to prev
1333 mark[26] = dot; // move local to cur
1334 context_start = prev_line(prev_line(dot));
1335 context_end = next_line(next_line(dot));
1336 //loiter= start_loiter= now;
1341 static char *swap_context(char *p) // goto new context for '' command make this the current context
1345 // the current context is in mark[26]
1346 // the previous context is in mark[27]
1347 // only swap context if other context is valid
1348 if (text <= mark[27] && mark[27] <= end - 1) {
1352 context_start = prev_line(prev_line(prev_line(p)));
1353 context_end = next_line(next_line(next_line(p)));
1357 #endif /* FEATURE_VI_YANKMARK */
1359 #if ENABLE_FEATURE_VI_UNDO
1360 static void undo_push(char *, unsigned, unsigned char);
1363 // open a hole in text[]
1364 // might reallocate text[]! use p += text_hole_make(p, ...),
1365 // and be careful to not use pointers into potentially freed text[]!
1366 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1372 end += size; // adjust the new END
1373 if (end >= (text + text_size)) {
1375 text_size += end - (text + text_size) + 10240;
1376 new_text = xrealloc(text, text_size);
1377 bias = (new_text - text);
1378 screenbegin += bias;
1382 #if ENABLE_FEATURE_VI_YANKMARK
1385 for (i = 0; i < ARRAY_SIZE(mark); i++)
1392 memmove(p + size, p, end - size - p);
1393 memset(p, ' ', size); // clear new hole
1397 // close a hole in text[] - delete "p" through "q", inclusive
1398 // "undo" value indicates if this operation should be undo-able
1399 #if !ENABLE_FEATURE_VI_UNDO
1400 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
1402 static char *text_hole_delete(char *p, char *q, int undo)
1407 // move forwards, from beginning
1411 if (q < p) { // they are backward- swap them
1415 hole_size = q - p + 1;
1417 #if ENABLE_FEATURE_VI_UNDO
1422 undo_push(p, hole_size, UNDO_DEL);
1424 case ALLOW_UNDO_CHAIN:
1425 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1427 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1428 case ALLOW_UNDO_QUEUED:
1429 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1435 if (src < text || src > end)
1437 if (dest < text || dest >= end)
1441 goto thd_atend; // just delete the end of the buffer
1442 memmove(dest, src, cnt);
1444 end = end - hole_size; // adjust the new END
1446 dest = end - 1; // make sure dest in below end-1
1448 dest = end = text; // keep pointers valid
1453 #if ENABLE_FEATURE_VI_UNDO
1455 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1456 // Flush any queued objects to the undo stack
1457 static void undo_queue_commit(void)
1459 // Pushes the queue object onto the undo stack
1461 // Deleted character undo events grow from the end
1462 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1464 (undo_queue_state | UNDO_USE_SPOS)
1466 undo_queue_state = UNDO_EMPTY;
1471 # define undo_queue_commit() ((void)0)
1474 static void flush_undo_data(void)
1476 struct undo_object *undo_entry;
1478 while (undo_stack_tail) {
1479 undo_entry = undo_stack_tail;
1480 undo_stack_tail = undo_entry->prev;
1485 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1486 // Add to the undo stack
1487 static void undo_push(char *src, unsigned length, uint8_t u_type)
1489 struct undo_object *undo_entry;
1492 // UNDO_INS: insertion, undo will remove from buffer
1493 // UNDO_DEL: deleted text, undo will restore to buffer
1494 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1495 // The CHAIN operations are for handling multiple operations that the user
1496 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1497 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1498 // for the INS/DEL operation. The raw values should be equal to the values of
1499 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1501 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1502 // This undo queuing functionality groups multiple character typing or backspaces
1503 // into a single large undo object. This greatly reduces calls to malloc() for
1504 // single-character operations while typing and has the side benefit of letting
1505 // an undo operation remove chunks of text rather than a single character.
1507 case UNDO_EMPTY: // Just in case this ever happens...
1509 case UNDO_DEL_QUEUED:
1511 return; // Only queue single characters
1512 switch (undo_queue_state) {
1514 undo_queue_state = UNDO_DEL;
1516 undo_queue_spos = src;
1518 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1519 // If queue is full, dump it into an object
1520 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1521 undo_queue_commit();
1524 // Switch from storing inserted text to deleted text
1525 undo_queue_commit();
1526 undo_push(src, length, UNDO_DEL_QUEUED);
1530 case UNDO_INS_QUEUED:
1533 switch (undo_queue_state) {
1535 undo_queue_state = UNDO_INS;
1536 undo_queue_spos = src;
1539 undo_q++; // Don't need to save any data for insertions
1540 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1541 undo_queue_commit();
1545 // Switch from storing deleted text to inserted text
1546 undo_queue_commit();
1547 undo_push(src, length, UNDO_INS_QUEUED);
1553 // If undo queuing is disabled, ignore the queuing flag entirely
1554 u_type = u_type & ~UNDO_QUEUED_FLAG;
1557 // Allocate a new undo object
1558 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1559 // For UNDO_DEL objects, save deleted text
1560 if ((text + length) == end)
1562 // If this deletion empties text[], strip the newline. When the buffer becomes
1563 // zero-length, a newline is added back, which requires this to compensate.
1564 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1565 memcpy(undo_entry->undo_text, src, length);
1567 undo_entry = xzalloc(sizeof(*undo_entry));
1569 undo_entry->length = length;
1570 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1571 if ((u_type & UNDO_USE_SPOS) != 0) {
1572 undo_entry->start = undo_queue_spos - text; // use start position from queue
1574 undo_entry->start = src - text; // use offset from start of text buffer
1576 u_type = (u_type & ~UNDO_USE_SPOS);
1578 undo_entry->start = src - text;
1580 undo_entry->u_type = u_type;
1582 // Push it on undo stack
1583 undo_entry->prev = undo_stack_tail;
1584 undo_stack_tail = undo_entry;
1588 static void undo_push_insert(char *p, int len, int undo)
1592 undo_push(p, len, UNDO_INS);
1594 case ALLOW_UNDO_CHAIN:
1595 undo_push(p, len, UNDO_INS_CHAIN);
1597 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1598 case ALLOW_UNDO_QUEUED:
1599 undo_push(p, len, UNDO_INS_QUEUED);
1605 // Undo the last operation
1606 static void undo_pop(void)
1609 char *u_start, *u_end;
1610 struct undo_object *undo_entry;
1612 // Commit pending undo queue before popping (should be unnecessary)
1613 undo_queue_commit();
1615 undo_entry = undo_stack_tail;
1616 // Check for an empty undo stack
1618 status_line("Already at oldest change");
1622 switch (undo_entry->u_type) {
1624 case UNDO_DEL_CHAIN:
1625 // make hole and put in text that was deleted; deallocate text
1626 u_start = text + undo_entry->start;
1627 text_hole_make(u_start, undo_entry->length);
1628 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1629 status_line("Undo [%d] %s %d chars at position %d",
1630 modified_count, "restored",
1631 undo_entry->length, undo_entry->start
1635 case UNDO_INS_CHAIN:
1636 // delete what was inserted
1637 u_start = undo_entry->start + text;
1638 u_end = u_start - 1 + undo_entry->length;
1639 text_hole_delete(u_start, u_end, NO_UNDO);
1640 status_line("Undo [%d] %s %d chars at position %d",
1641 modified_count, "deleted",
1642 undo_entry->length, undo_entry->start
1647 switch (undo_entry->u_type) {
1648 // If this is the end of a chain, lower modification count and refresh display
1651 dot = (text + undo_entry->start);
1654 case UNDO_DEL_CHAIN:
1655 case UNDO_INS_CHAIN:
1659 // Deallocate the undo object we just processed
1660 undo_stack_tail = undo_entry->prev;
1663 // For chained operations, continue popping all the way down the chain.
1665 undo_pop(); // Follow the undo chain if one exists
1670 # define flush_undo_data() ((void)0)
1671 # define undo_queue_commit() ((void)0)
1672 #endif /* ENABLE_FEATURE_VI_UNDO */
1674 //----- Dot Movement Routines ----------------------------------
1675 static void dot_left(void)
1677 undo_queue_commit();
1678 if (dot > text && dot[-1] != '\n')
1682 static void dot_right(void)
1684 undo_queue_commit();
1685 if (dot < end - 1 && *dot != '\n')
1689 static void dot_begin(void)
1691 undo_queue_commit();
1692 dot = begin_line(dot); // return pointer to first char cur line
1695 static void dot_end(void)
1697 undo_queue_commit();
1698 dot = end_line(dot); // return pointer to last char cur line
1701 static char *move_to_col(char *p, int l)
1707 while (co < l && p < end) {
1708 if (*p == '\n') //vda || *p == '\0')
1711 co = next_tabstop(co);
1712 } else if (*p < ' ' || *p == 127) {
1713 co++; // display as ^X, use 2 columns
1721 static void dot_next(void)
1723 undo_queue_commit();
1724 dot = next_line(dot);
1727 static void dot_prev(void)
1729 undo_queue_commit();
1730 dot = prev_line(dot);
1733 static void dot_skip_over_ws(void)
1736 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1740 static void dot_scroll(int cnt, int dir)
1744 undo_queue_commit();
1745 for (; cnt > 0; cnt--) {
1748 // ctrl-Y scroll up one line
1749 screenbegin = prev_line(screenbegin);
1752 // ctrl-E scroll down one line
1753 screenbegin = next_line(screenbegin);
1756 // make sure "dot" stays on the screen so we dont scroll off
1757 if (dot < screenbegin)
1759 q = end_screen(); // find new bottom line
1761 dot = begin_line(q); // is dot is below bottom line?
1765 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1767 if (p >= end && end > text) {
1778 #if ENABLE_FEATURE_VI_DOT_CMD
1779 static void start_new_cmd_q(char c)
1781 // get buffer for new cmd
1782 // if there is a current cmd count put it in the buffer first
1784 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1785 } else { // just save char c onto queue
1786 last_modifying_cmd[0] = c;
1791 static void end_cmd_q(void)
1793 # if ENABLE_FEATURE_VI_YANKMARK
1794 YDreg = 26; // go back to default Yank/Delete reg
1799 # define end_cmd_q() ((void)0)
1800 #endif /* FEATURE_VI_DOT_CMD */
1802 // copy text into register, then delete text.
1803 // if dist <= 0, do not include, or go past, a NewLine
1805 #if !ENABLE_FEATURE_VI_UNDO
1806 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1808 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1812 // make sure start <= stop
1814 // they are backwards, reverse them
1820 // we cannot cross NL boundaries
1824 // dont go past a NewLine
1825 for (; p + 1 <= stop; p++) {
1827 stop = p; // "stop" just before NewLine
1833 #if ENABLE_FEATURE_VI_YANKMARK
1834 text_yank(start, stop, YDreg);
1836 if (yf == YANKDEL) {
1837 p = text_hole_delete(start, stop, undo);
1842 // might reallocate text[]!
1843 static int file_insert(const char *fn, char *p, int initial)
1847 struct stat statbuf;
1854 fd = open(fn, O_RDONLY);
1857 status_line_bold_errno(fn);
1862 if (fstat(fd, &statbuf) < 0) {
1863 status_line_bold_errno(fn);
1866 if (!S_ISREG(statbuf.st_mode)) {
1867 status_line_bold("'%s' is not a regular file", fn);
1870 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1871 p += text_hole_make(p, size);
1872 cnt = full_read(fd, p, size);
1874 status_line_bold_errno(fn);
1875 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1876 } else if (cnt < size) {
1877 // There was a partial read, shrink unused space
1878 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1879 status_line_bold("can't read '%s'", fn);
1884 #if ENABLE_FEATURE_VI_READONLY
1886 && ((access(fn, W_OK) < 0) ||
1887 // root will always have access()
1888 // so we check fileperms too
1889 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1892 SET_READONLY_FILE(readonly_mode);
1898 // find matching char of pair () [] {}
1899 // will crash if c is not one of these
1900 static char *find_pair(char *p, const char c)
1902 const char *braces = "()[]{}";
1906 dir = strchr(braces, c) - braces;
1908 match = braces[dir];
1909 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1911 // look for match, count levels of pairs (( ))
1915 if (p < text || p >= end)
1918 level++; // increase pair levels
1920 level--; // reduce pair level
1922 return p; // found matching pair
1927 #if ENABLE_FEATURE_VI_SETOPTS
1928 // show the matching char of a pair, () [] {}
1929 static void showmatching(char *p)
1933 // we found half of a pair
1934 q = find_pair(p, *p); // get loc of matching char
1936 indicate_error(); // no matching char
1938 // "q" now points to matching pair
1939 save_dot = dot; // remember where we are
1940 dot = q; // go to new loc
1941 refresh(FALSE); // let the user see it
1942 mysleep(40); // give user some time
1943 dot = save_dot; // go back to old loc
1947 #endif /* FEATURE_VI_SETOPTS */
1949 // might reallocate text[]! use p += stupid_insert(p, ...),
1950 // and be careful to not use pointers into potentially freed text[]!
1951 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1954 bias = text_hole_make(p, 1);
1960 #if !ENABLE_FEATURE_VI_UNDO
1961 #define char_insert(a,b,c) char_insert(a,b)
1963 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1965 if (c == 22) { // Is this an ctrl-V?
1966 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1967 refresh(FALSE); // show the ^
1970 #if ENABLE_FEATURE_VI_UNDO
1971 undo_push_insert(p, 1, undo);
1974 #endif /* ENABLE_FEATURE_VI_UNDO */
1976 } else if (c == 27) { // Is this an ESC?
1978 undo_queue_commit();
1980 end_cmd_q(); // stop adding to q
1981 last_status_cksum = 0; // force status update
1982 if ((p[-1] != '\n') && (dot > text)) {
1985 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1988 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1991 // insert a char into text[]
1993 c = '\n'; // translate \r to \n
1994 #if ENABLE_FEATURE_VI_UNDO
1995 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1997 undo_queue_commit();
1999 undo_push_insert(p, 1, undo);
2002 #endif /* ENABLE_FEATURE_VI_UNDO */
2003 p += 1 + stupid_insert(p, c); // insert the char
2004 #if ENABLE_FEATURE_VI_SETOPTS
2005 if (showmatch && strchr(")]}", c) != NULL) {
2006 showmatching(p - 1);
2008 if (autoindent && c == '\n') { // auto indent the new line
2011 q = prev_line(p); // use prev line as template
2012 len = strspn(q, " \t"); // space or tab
2015 bias = text_hole_make(p, len);
2018 #if ENABLE_FEATURE_VI_UNDO
2019 undo_push_insert(p, len, undo);
2030 // read text from file or create an empty buf
2031 // will also update current_filename
2032 static int init_text_buffer(char *fn)
2036 // allocate/reallocate text buffer
2039 screenbegin = dot = end = text = xzalloc(text_size);
2041 if (fn != current_filename) {
2042 free(current_filename);
2043 current_filename = xstrdup(fn);
2045 rc = file_insert(fn, text, 1);
2047 // file doesnt exist. Start empty buf with dummy line
2048 char_insert(text, '\n', NO_UNDO);
2053 last_modified_count = -1;
2054 #if ENABLE_FEATURE_VI_YANKMARK
2056 memset(mark, 0, sizeof(mark));
2061 #if ENABLE_FEATURE_VI_YANKMARK \
2062 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2063 || ENABLE_FEATURE_VI_CRASHME
2064 // might reallocate text[]! use p += string_insert(p, ...),
2065 // and be careful to not use pointers into potentially freed text[]!
2066 # if !ENABLE_FEATURE_VI_UNDO
2067 # define string_insert(a,b,c) string_insert(a,b)
2069 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2075 #if ENABLE_FEATURE_VI_UNDO
2076 undo_push_insert(p, i, undo);
2078 bias = text_hole_make(p, i);
2081 #if ENABLE_FEATURE_VI_YANKMARK
2084 for (cnt = 0; *s != '\0'; s++) {
2088 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2095 static int file_write(char *fn, char *first, char *last)
2097 int fd, cnt, charcnt;
2100 status_line_bold("No current filename");
2103 // By popular request we do not open file with O_TRUNC,
2104 // but instead ftruncate() it _after_ successful write.
2105 // Might reduce amount of data lost on power fail etc.
2106 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2109 cnt = last - first + 1;
2110 charcnt = full_write(fd, first, cnt);
2111 ftruncate(fd, charcnt);
2112 if (charcnt == cnt) {
2114 //modified_count = FALSE;
2122 #if ENABLE_FEATURE_VI_SEARCH
2123 # if ENABLE_FEATURE_VI_REGEX_SEARCH
2124 // search for pattern starting at p
2125 static char *char_search(char *p, const char *pat, int dir_and_range)
2127 struct re_pattern_buffer preg;
2134 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2136 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2138 memset(&preg, 0, sizeof(preg));
2139 err = re_compile_pattern(pat, strlen(pat), &preg);
2141 status_line_bold("bad search pattern '%s': %s", pat, err);
2145 range = (dir_and_range & 1);
2146 q = end - 1; // if FULL
2147 if (range == LIMITED)
2149 if (dir_and_range < 0) { // BACK?
2151 if (range == LIMITED)
2155 // RANGE could be negative if we are searching backwards
2165 // search for the compiled pattern, preg, in p[]
2166 // range < 0: search backward
2167 // range > 0: search forward
2169 // re_search() < 0: not found or error
2170 // re_search() >= 0: index of found pattern
2171 // struct pattern char int int int struct reg
2172 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2173 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2177 if (dir_and_range > 0) // FORWARD?
2184 # if ENABLE_FEATURE_VI_SETOPTS
2185 static int mycmp(const char *s1, const char *s2, int len)
2188 return strncasecmp(s1, s2, len);
2190 return strncmp(s1, s2, len);
2193 # define mycmp strncmp
2195 static char *char_search(char *p, const char *pat, int dir_and_range)
2202 range = (dir_and_range & 1);
2203 if (dir_and_range > 0) { //FORWARD?
2204 stop = end - 1; // assume range is p..end-1
2205 if (range == LIMITED)
2206 stop = next_line(p); // range is to next line
2207 for (start = p; start < stop; start++) {
2208 if (mycmp(start, pat, len) == 0) {
2213 stop = text; // assume range is text..p
2214 if (range == LIMITED)
2215 stop = prev_line(p); // range is to prev line
2216 for (start = p - len; start >= stop; start--) {
2217 if (mycmp(start, pat, len) == 0) {
2222 // pattern not found
2226 #endif /* FEATURE_VI_SEARCH */
2228 //----- The Colon commands -------------------------------------
2229 #if ENABLE_FEATURE_VI_COLON
2230 static char *get_one_address(char *p, int *addr) // get colon addr, if present
2234 IF_FEATURE_VI_YANKMARK(char c;)
2235 IF_FEATURE_VI_SEARCH(char *pat;)
2237 *addr = -1; // assume no addr
2238 if (*p == '.') { // the current line
2240 q = begin_line(dot);
2241 *addr = count_lines(text, q);
2243 #if ENABLE_FEATURE_VI_YANKMARK
2244 else if (*p == '\'') { // is this a mark addr
2248 if (c >= 'a' && c <= 'z') {
2251 q = mark[(unsigned char) c];
2252 if (q != NULL) { // is mark valid
2253 *addr = count_lines(text, q);
2258 #if ENABLE_FEATURE_VI_SEARCH
2259 else if (*p == '/') { // a search pattern
2260 q = strchrnul(++p, '/');
2261 pat = xstrndup(p, q - p); // save copy of pattern
2265 q = char_search(dot, pat, (FORWARD << 1) | FULL);
2267 *addr = count_lines(text, q);
2272 else if (*p == '$') { // the last line in file
2274 q = begin_line(end - 1);
2275 *addr = count_lines(text, q);
2276 } else if (isdigit(*p)) { // specific line number
2277 sscanf(p, "%d%n", addr, &st);
2280 // unrecognized address - assume -1
2286 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
2288 //----- get the address' i.e., 1,3 'a,'b -----
2289 // get FIRST addr, if present
2291 p++; // skip over leading spaces
2292 if (*p == '%') { // alias for 1,$
2295 *e = count_lines(text, end-1);
2298 p = get_one_address(p, b);
2301 if (*p == ',') { // is there a address separator
2305 // get SECOND addr, if present
2306 p = get_one_address(p, e);
2310 p++; // skip over trailing spaces
2314 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2315 static void setops(const char *args, const char *opname, int flg_no,
2316 const char *short_opname, int opt)
2318 const char *a = args + flg_no;
2319 int l = strlen(opname) - 1; // opname have + ' '
2321 // maybe strncmp? we had tons of erroneous strncasecmp's...
2322 if (strncasecmp(a, opname, l) == 0
2323 || strncasecmp(a, short_opname, 2) == 0
2333 #endif /* FEATURE_VI_COLON */
2335 // buf must be no longer than MAX_INPUT_LEN!
2336 static void colon(char *buf)
2338 #if !ENABLE_FEATURE_VI_COLON
2339 // Simple ":cmd" handler with minimal set of commands
2348 if (strncmp(p, "quit", cnt) == 0
2349 || strncmp(p, "q!", cnt) == 0
2351 if (modified_count && p[1] != '!') {
2352 status_line_bold("No write since last change (:%s! overrides)", p);
2358 if (strncmp(p, "write", cnt) == 0
2359 || strncmp(p, "wq", cnt) == 0
2360 || strncmp(p, "wn", cnt) == 0
2361 || (p[0] == 'x' && !p[1])
2363 if (modified_count != 0 || p[0] != 'x') {
2364 cnt = file_write(current_filename, text, end - 1);
2368 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2371 last_modified_count = -1;
2372 status_line("'%s' %dL, %dC",
2374 count_lines(text, end - 1), cnt
2377 || p[1] == 'q' || p[1] == 'n'
2378 || p[1] == 'Q' || p[1] == 'N'
2385 if (strncmp(p, "file", cnt) == 0) {
2386 last_status_cksum = 0; // force status update
2389 if (sscanf(p, "%d", &cnt) > 0) {
2390 dot = find_line(cnt);
2397 char c, *buf1, *q, *r;
2398 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2401 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2405 // :3154 // if (-e line 3154) goto it else stay put
2406 // :4,33w! foo // write a portion of buffer to file "foo"
2407 // :w // write all of buffer to current file
2409 // :q! // quit- dont care about modified file
2410 // :'a,'z!sort -u // filter block through sort
2411 // :'f // goto mark "f"
2412 // :'fl // list literal the mark "f" line
2413 // :.r bar // read file "bar" into buffer before dot
2414 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2415 // :/xyz/ // goto the "xyz" line
2416 // :s/find/replace/ // substitute pattern "find" with "replace"
2417 // :!<cmd> // run <cmd> then return
2423 buf++; // move past the ':'
2427 q = text; // assume 1,$ for the range
2429 li = count_lines(text, end - 1);
2430 fn = current_filename;
2432 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2433 buf = get_address(buf, &b, &e);
2435 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2436 // remember orig command line
2440 // get the COMMAND into cmd[]
2442 while (*buf != '\0') {
2448 // get any ARGuments
2449 while (isblank(*buf))
2453 buf1 = last_char_is(cmd, '!');
2456 *buf1 = '\0'; // get rid of !
2459 // if there is only one addr, then the addr
2460 // is the line number of the single line the
2461 // user wants. So, reset the end
2462 // pointer to point at end of the "b" line
2463 q = find_line(b); // what line is #b
2468 // we were given two addrs. change the
2469 // end pointer to the addr given by user.
2470 r = find_line(e); // what line is #e
2474 // ------------ now look for the command ------------
2476 if (i == 0) { // :123CR goto line #123
2478 dot = find_line(b); // what line is #b
2482 # if ENABLE_FEATURE_ALLOW_EXEC
2483 else if (cmd[0] == '!') { // run a cmd
2485 // :!ls run the <cmd>
2486 go_bottom_and_clear_to_eol();
2488 retcode = system(orig_buf + 1); // run the cmd
2490 printf("\nshell returned %i\n\n", retcode);
2492 Hit_Return(); // let user see results
2495 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
2496 if (b < 0) { // no addr given- use defaults
2497 b = e = count_lines(text, dot);
2499 status_line("%d", b);
2500 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
2501 if (b < 0) { // no addr given- use defaults
2502 q = begin_line(dot); // assume .,. for the range
2505 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
2507 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
2510 // don't edit, if the current file has been modified
2511 if (modified_count && !useforce) {
2512 status_line_bold("No write since last change (:%s! overrides)", cmd);
2516 // the user supplied a file name
2518 } else if (current_filename && current_filename[0]) {
2519 // no user supplied name- use the current filename
2520 // fn = current_filename; was set by default
2522 // no user file name, no current name- punt
2523 status_line_bold("No current filename");
2527 size = init_text_buffer(fn);
2529 # if ENABLE_FEATURE_VI_YANKMARK
2530 if (Ureg >= 0 && Ureg < 28) {
2531 free(reg[Ureg]); // free orig line reg- for 'U'
2534 if (YDreg >= 0 && YDreg < 28) {
2535 free(reg[YDreg]); // free default yank/delete register
2539 // how many lines in text[]?
2540 li = count_lines(text, end - 1);
2541 status_line("'%s'%s"
2542 IF_FEATURE_VI_READONLY("%s")
2545 (size < 0 ? " [New file]" : ""),
2546 IF_FEATURE_VI_READONLY(
2547 ((readonly_mode) ? " [Readonly]" : ""),
2549 li, (int)(end - text)
2551 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
2552 if (b != -1 || e != -1) {
2553 status_line_bold("No address allowed on this command");
2557 // user wants a new filename
2558 free(current_filename);
2559 current_filename = xstrdup(args);
2561 // user wants file status info
2562 last_status_cksum = 0; // force status update
2564 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
2565 // print out values of all features
2566 go_bottom_and_clear_to_eol();
2571 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
2572 if (b < 0) { // no addr given- use defaults
2573 q = begin_line(dot); // assume .,. for the range
2576 go_bottom_and_clear_to_eol();
2578 for (; q <= r; q++) {
2582 c_is_no_print = (c & 0x80) && !Isprint(c);
2583 if (c_is_no_print) {
2589 } else if (c < ' ' || c == 127) {
2601 } else if (strncmp(cmd, "quit", i) == 0 // quit
2602 || strncmp(cmd, "next", i) == 0 // edit next file
2603 || strncmp(cmd, "prev", i) == 0 // edit previous file
2608 // force end of argv list
2614 // don't exit if the file been modified
2615 if (modified_count) {
2616 status_line_bold("No write since last change (:%s! overrides)", cmd);
2619 // are there other file to edit
2620 n = save_argc - optind - 1;
2621 if (*cmd == 'q' && n > 0) {
2622 status_line_bold("%d more file(s) to edit", n);
2625 if (*cmd == 'n' && n <= 0) {
2626 status_line_bold("No more files to edit");
2630 // are there previous files to edit
2632 status_line_bold("No previous files to edit");
2638 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
2643 status_line_bold("No filename given");
2646 if (b < 0) { // no addr given- use defaults
2647 q = begin_line(dot); // assume "dot"
2649 // read after current line- unless user said ":0r foo"
2652 // read after last line
2656 { // dance around potentially-reallocated text[]
2657 uintptr_t ofs = q - text;
2658 size = file_insert(fn, q, 0);
2662 goto ret; // nothing was inserted
2663 // how many lines in text[]?
2664 li = count_lines(q, q + size - 1);
2666 IF_FEATURE_VI_READONLY("%s")
2669 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2673 // if the insert is before "dot" then we need to update
2677 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
2678 if (modified_count && !useforce) {
2679 status_line_bold("No write since last change (:%s! overrides)", cmd);
2681 // reset the filenames to edit
2682 optind = -1; // start from 0th file
2685 # if ENABLE_FEATURE_VI_SET
2686 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
2687 # if ENABLE_FEATURE_VI_SETOPTS
2690 i = 0; // offset into args
2691 // only blank is regarded as args delimiter. What about tab '\t'?
2692 if (!args[0] || strcasecmp(args, "all") == 0) {
2693 // print out values of all options
2694 # if ENABLE_FEATURE_VI_SETOPTS
2701 autoindent ? "" : "no",
2702 err_method ? "" : "no",
2703 ignorecase ? "" : "no",
2704 showmatch ? "" : "no",
2710 # if ENABLE_FEATURE_VI_SETOPTS
2713 if (strncmp(argp, "no", 2) == 0)
2714 i = 2; // ":set noautoindent"
2715 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2716 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
2717 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2718 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2719 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2721 sscanf(argp + i+8, "%u", &t);
2722 if (t > 0 && t <= MAX_TABSTOP)
2725 argp = skip_non_whitespace(argp);
2726 argp = skip_whitespace(argp);
2728 # endif /* FEATURE_VI_SETOPTS */
2729 # endif /* FEATURE_VI_SET */
2731 # if ENABLE_FEATURE_VI_SEARCH
2732 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
2733 char *F, *R, *flags;
2734 size_t len_F, len_R;
2735 int gflag; // global replace flag
2736 # if ENABLE_FEATURE_VI_UNDO
2737 int dont_chain_first_item = ALLOW_UNDO;
2740 // F points to the "find" pattern
2741 // R points to the "replace" pattern
2742 // replace the cmd line delimiters "/" with NULs
2743 c = orig_buf[1]; // what is the delimiter
2744 F = orig_buf + 2; // start of "find"
2745 R = strchr(F, c); // middle delimiter
2749 *R++ = '\0'; // terminate "find"
2750 flags = strchr(R, c);
2754 *flags++ = '\0'; // terminate "replace"
2758 if (b < 0) { // maybe :s/foo/bar/
2759 q = begin_line(dot); // start with cur line
2760 b = count_lines(text, q); // cur line number
2763 e = b; // maybe :.s/foo/bar/
2765 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2766 char *ls = q; // orig line start
2769 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
2772 // we found the "find" pattern - delete it
2773 // For undo support, the first item should not be chained
2774 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2775 # if ENABLE_FEATURE_VI_UNDO
2776 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2778 // insert the "replace" patern
2779 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2782 /*q += bias; - recalculated anyway */
2783 // check for "global" :s/foo/bar/g
2785 if ((found + len_R) < end_line(ls)) {
2787 goto vc4; // don't let q move past cur line
2793 # endif /* FEATURE_VI_SEARCH */
2794 } else if (strncmp(cmd, "version", i) == 0) { // show software version
2795 status_line(BB_VER);
2796 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2797 || strncmp(cmd, "wq", i) == 0
2798 || strncmp(cmd, "wn", i) == 0
2799 || (cmd[0] == 'x' && !cmd[1])
2802 //int forced = FALSE;
2804 // is there a file name to write to?
2808 # if ENABLE_FEATURE_VI_READONLY
2809 if (readonly_mode && !useforce) {
2810 status_line_bold("'%s' is read only", fn);
2815 // if "fn" is not write-able, chmod u+w
2816 // sprintf(syscmd, "chmod u+w %s", fn);
2820 if (modified_count != 0 || cmd[0] != 'x') {
2822 l = file_write(fn, q, r);
2827 //if (useforce && forced) {
2829 // sprintf(syscmd, "chmod u-w %s", fn);
2835 status_line_bold_errno(fn);
2837 // how many lines written
2838 li = count_lines(q, q + l - 1);
2839 status_line("'%s' %dL, %dC", fn, li, l);
2841 if (q == text && q + l == end) {
2843 last_modified_count = -1;
2846 || cmd[1] == 'q' || cmd[1] == 'n'
2847 || cmd[1] == 'Q' || cmd[1] == 'N'
2853 # if ENABLE_FEATURE_VI_YANKMARK
2854 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
2855 if (b < 0) { // no addr given- use defaults
2856 q = begin_line(dot); // assume .,. for the range
2859 text_yank(q, r, YDreg);
2860 li = count_lines(q, r);
2861 status_line("Yank %d lines (%d chars) into [%c]",
2862 li, strlen(reg[YDreg]), what_reg());
2866 not_implemented(cmd);
2869 dot = bound_dot(dot); // make sure "dot" is valid
2871 # if ENABLE_FEATURE_VI_SEARCH
2873 status_line(":s expression missing delimiters");
2875 #endif /* FEATURE_VI_COLON */
2878 //----- Helper Utility Routines --------------------------------
2880 //----------------------------------------------------------------
2881 //----- Char Routines --------------------------------------------
2882 /* Chars that are part of a word-
2883 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2884 * Chars that are Not part of a word (stoppers)
2885 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2886 * Chars that are WhiteSpace
2887 * TAB NEWLINE VT FF RETURN SPACE
2888 * DO NOT COUNT NEWLINE AS WHITESPACE
2891 static char *new_screen(int ro, int co)
2896 screensize = ro * co + 8;
2897 screen = xmalloc(screensize);
2898 // initialize the new screen. assume this will be a empty file.
2900 // non-existent text[] lines start with a tilde (~).
2901 for (li = 1; li < ro - 1; li++) {
2902 screen[(li * co) + 0] = '~';
2907 static int st_test(char *p, int type, int dir, char *tested)
2917 if (type == S_BEFORE_WS) {
2919 test = (!isspace(c) || c == '\n');
2921 if (type == S_TO_WS) {
2923 test = (!isspace(c) || c == '\n');
2925 if (type == S_OVER_WS) {
2929 if (type == S_END_PUNCT) {
2933 if (type == S_END_ALNUM) {
2935 test = (isalnum(c) || c == '_');
2941 static char *skip_thing(char *p, int linecnt, int dir, int type)
2945 while (st_test(p, type, dir, &c)) {
2946 // make sure we limit search to correct number of lines
2947 if (c == '\n' && --linecnt < 1)
2949 if (dir >= 0 && p >= end - 1)
2951 if (dir < 0 && p <= text)
2953 p += dir; // move to next char
2958 #if ENABLE_FEATURE_VI_USE_SIGNALS
2959 static void winch_handler(int sig UNUSED_PARAM)
2961 int save_errno = errno;
2962 // FIXME: do it in main loop!!!
2963 signal(SIGWINCH, winch_handler);
2964 query_screen_dimensions();
2965 new_screen(rows, columns); // get memory for virtual screen
2966 redraw(TRUE); // re-draw the screen
2969 static void tstp_handler(int sig UNUSED_PARAM)
2971 int save_errno = errno;
2973 // ioctl inside cookmode() was seen to generate SIGTTOU,
2974 // stopping us too early. Prevent that:
2975 signal(SIGTTOU, SIG_IGN);
2977 go_bottom_and_clear_to_eol();
2978 cookmode(); // terminal to "cooked"
2981 //signal(SIGTSTP, SIG_DFL);
2983 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2984 //signal(SIGTSTP, tstp_handler);
2986 // we have been "continued" with SIGCONT, restore screen and termios
2987 rawmode(); // terminal to "raw"
2988 last_status_cksum = 0; // force status update
2989 redraw(TRUE); // re-draw the screen
2993 static void int_handler(int sig)
2995 signal(SIGINT, int_handler);
2996 siglongjmp(restart, sig);
2998 #endif /* FEATURE_VI_USE_SIGNALS */
3000 static void do_cmd(int c);
3002 static int find_range(char **start, char **stop, char c)
3004 char *save_dot, *p, *q, *t;
3005 int cnt, multiline = 0;
3010 if (strchr("cdy><", c)) {
3011 // these cmds operate on whole lines
3012 p = q = begin_line(p);
3013 for (cnt = 1; cnt < cmdcnt; cnt++) {
3017 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3018 // These cmds operate on char positions
3019 do_cmd(c); // execute movement cmd
3021 } else if (strchr("wW", c)) {
3022 do_cmd(c); // execute movement cmd
3023 // if we are at the next word's first char
3024 // step back one char
3025 // but check the possibilities when it is true
3026 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3027 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3028 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3029 dot--; // move back off of next word
3030 if (dot > text && *dot == '\n')
3031 dot--; // stay off NL
3033 } else if (strchr("H-k{", c)) {
3034 // these operate on multi-lines backwards
3035 q = end_line(dot); // find NL
3036 do_cmd(c); // execute movement cmd
3039 } else if (strchr("L+j}\r\n", c)) {
3040 // these operate on multi-lines forwards
3041 p = begin_line(dot);
3042 do_cmd(c); // execute movement cmd
3043 dot_end(); // find NL
3046 // nothing -- this causes any other values of c to
3047 // represent the one-character range under the
3048 // cursor. this is correct for ' ' and 'l', but
3049 // perhaps no others.
3058 // backward char movements don't include start position
3059 if (q > p && strchr("^0bBh\b\177", c)) q--;
3062 for (t = p; t <= q; t++) {
3075 //---------------------------------------------------------------------
3076 //----- the Ascii Chart -----------------------------------------------
3077 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3078 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3079 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3080 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3081 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3082 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3083 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3084 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3085 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3086 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3087 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3088 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3089 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3090 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3091 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3092 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3093 //---------------------------------------------------------------------
3095 //----- Execute a Vi Command -----------------------------------
3096 static void do_cmd(int c)
3098 char *p, *q, *save_dot;
3104 // c1 = c; // quiet the compiler
3105 // cnt = yf = 0; // quiet the compiler
3106 // p = q = save_dot = buf; // quiet the compiler
3107 memset(buf, '\0', sizeof(buf));
3111 // if this is a cursor key, skip these checks
3119 case KEYCODE_PAGEUP:
3120 case KEYCODE_PAGEDOWN:
3121 case KEYCODE_DELETE:
3125 if (cmd_mode == 2) {
3126 // flip-flop Insert/Replace mode
3127 if (c == KEYCODE_INSERT)
3129 // we are 'R'eplacing the current *dot with new char
3131 // don't Replace past E-o-l
3132 cmd_mode = 1; // convert to insert
3133 undo_queue_commit();
3135 if (1 <= c || Isprint(c)) {
3137 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3138 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3143 if (cmd_mode == 1) {
3144 // hitting "Insert" twice means "R" replace mode
3145 if (c == KEYCODE_INSERT) goto dc5;
3146 // insert the char c at "dot"
3147 if (1 <= c || Isprint(c)) {
3148 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3163 #if ENABLE_FEATURE_VI_CRASHME
3164 case 0x14: // dc4 ctrl-T
3165 crashme = (crashme == 0) ? 1 : 0;
3195 default: // unrecognized command
3198 not_implemented(buf);
3199 end_cmd_q(); // stop adding to q
3200 case 0x00: // nul- ignore
3202 case 2: // ctrl-B scroll up full screen
3203 case KEYCODE_PAGEUP: // Cursor Key Page Up
3204 dot_scroll(rows - 2, -1);
3206 case 4: // ctrl-D scroll down half screen
3207 dot_scroll((rows - 2) / 2, 1);
3209 case 5: // ctrl-E scroll down one line
3212 case 6: // ctrl-F scroll down full screen
3213 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3214 dot_scroll(rows - 2, 1);
3216 case 7: // ctrl-G show current status
3217 last_status_cksum = 0; // force status update
3219 case 'h': // h- move left
3220 case KEYCODE_LEFT: // cursor key Left
3221 case 8: // ctrl-H- move left (This may be ERASE char)
3222 case 0x7f: // DEL- move left (This may be ERASE char)
3225 } while (--cmdcnt > 0);
3227 case 10: // Newline ^J
3228 case 'j': // j- goto next line, same col
3229 case KEYCODE_DOWN: // cursor key Down
3231 dot_next(); // go to next B-o-l
3232 // try stay in same col
3233 dot = move_to_col(dot, ccol + offset);
3234 } while (--cmdcnt > 0);
3236 case 12: // ctrl-L force redraw whole screen
3237 case 18: // ctrl-R force redraw
3238 redraw(TRUE); // this will redraw the entire display
3240 case 13: // Carriage Return ^M
3241 case '+': // +- goto next line
3245 } while (--cmdcnt > 0);
3247 case 21: // ctrl-U scroll up half screen
3248 dot_scroll((rows - 2) / 2, -1);
3250 case 25: // ctrl-Y scroll up one line
3256 cmd_mode = 0; // stop insrting
3257 undo_queue_commit();
3259 last_status_cksum = 0; // force status update
3261 case ' ': // move right
3262 case 'l': // move right
3263 case KEYCODE_RIGHT: // Cursor Key Right
3266 } while (--cmdcnt > 0);
3268 #if ENABLE_FEATURE_VI_YANKMARK
3269 case '"': // "- name a register to use for Delete/Yank
3270 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3271 if ((unsigned)c1 <= 25) { // a-z?
3277 case '\'': // '- goto a specific mark
3278 c1 = (get_one_char() | 0x20);
3279 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3283 if (text <= q && q < end) {
3285 dot_begin(); // go to B-o-l
3288 } else if (c1 == '\'') { // goto previous context
3289 dot = swap_context(dot); // swap current and previous context
3290 dot_begin(); // go to B-o-l
3296 case 'm': // m- Mark a line
3297 // this is really stupid. If there are any inserts or deletes
3298 // between text[0] and dot then this mark will not point to the
3299 // correct location! It could be off by many lines!
3300 // Well..., at least its quick and dirty.
3301 c1 = (get_one_char() | 0x20) - 'a';
3302 if ((unsigned)c1 <= 25) { // a-z?
3303 // remember the line
3309 case 'P': // P- Put register before
3310 case 'p': // p- put register after
3313 status_line_bold("Nothing in register %c", what_reg());
3316 // are we putting whole lines or strings
3317 if (strchr(p, '\n') != NULL) {
3319 dot_begin(); // putting lines- Put above
3322 // are we putting after very last line?
3323 if (end_line(dot) == (end - 1)) {
3324 dot = end; // force dot to end of text[]
3326 dot_next(); // next line, then put before
3331 dot_right(); // move to right, can move to NL
3333 string_insert(dot, p, ALLOW_UNDO); // insert the string
3334 end_cmd_q(); // stop adding to q
3336 case 'U': // U- Undo; replace current line with original version
3337 if (reg[Ureg] != NULL) {
3338 p = begin_line(dot);
3340 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3341 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3346 #endif /* FEATURE_VI_YANKMARK */
3347 #if ENABLE_FEATURE_VI_UNDO
3348 case 'u': // u- undo last operation
3352 case '$': // $- goto end of line
3353 case KEYCODE_END: // Cursor Key End
3355 dot = end_line(dot);
3361 case '%': // %- find matching char of pair () [] {}
3362 for (q = dot; q < end && *q != '\n'; q++) {
3363 if (strchr("()[]{}", *q) != NULL) {
3364 // we found half of a pair
3365 p = find_pair(q, *q);
3377 case 'f': // f- forward to a user specified char
3378 last_forward_char = get_one_char(); // get the search char
3380 // dont separate these two commands. 'f' depends on ';'
3382 //**** fall through to ... ';'
3383 case ';': // ;- look at rest of line for last forward char
3385 if (last_forward_char == 0)
3388 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3391 if (*q == last_forward_char)
3393 } while (--cmdcnt > 0);
3395 case ',': // repeat latest 'f' in opposite direction
3396 if (last_forward_char == 0)
3400 while (q >= text && *q != '\n' && *q != last_forward_char) {
3403 if (q >= text && *q == last_forward_char)
3405 } while (--cmdcnt > 0);
3408 case '-': // -- goto prev line
3412 } while (--cmdcnt > 0);
3414 #if ENABLE_FEATURE_VI_DOT_CMD
3415 case '.': // .- repeat the last modifying command
3416 // Stuff the last_modifying_cmd back into stdin
3417 // and let it be re-executed.
3419 last_modifying_cmd[lmc_len] = 0;
3420 ioq = ioq_start = xstrdup(last_modifying_cmd);
3424 #if ENABLE_FEATURE_VI_SEARCH
3425 case '?': // /- search for a pattern
3426 case '/': // /- search for a pattern
3429 q = get_input_line(buf); // get input line- use "status line"
3430 if (q[0] && !q[1]) {
3431 if (last_search_pattern[0])
3432 last_search_pattern[0] = c;
3433 goto dc3; // if no pat re-use old pat
3435 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3436 // there is a new pat
3437 free(last_search_pattern);
3438 last_search_pattern = xstrdup(q);
3439 goto dc3; // now find the pattern
3441 // user changed mind and erased the "/"- do nothing
3443 case 'N': // N- backward search for last pattern
3444 dir = BACK; // assume BACKWARD search
3446 if (last_search_pattern[0] == '?') {
3450 goto dc4; // now search for pattern
3452 case 'n': // n- repeat search for last pattern
3453 // search rest of text[] starting at next char
3454 // if search fails return orignal "p" not the "p+1" address
3458 dir = FORWARD; // assume FORWARD search
3460 if (last_search_pattern[0] == '?') {
3465 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3467 dot = q; // good search, update "dot"
3471 // no pattern found between "dot" and "end"- continue at top
3476 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3477 if (q != NULL) { // found something
3478 dot = q; // found new pattern- goto it
3479 msg = "search hit BOTTOM, continuing at TOP";
3481 msg = "search hit TOP, continuing at BOTTOM";
3484 msg = "Pattern not found";
3488 status_line_bold("%s", msg);
3489 } while (--cmdcnt > 0);
3491 case '{': // {- move backward paragraph
3492 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3493 if (q != NULL) { // found blank line
3494 dot = next_line(q); // move to next blank line
3497 case '}': // }- move forward paragraph
3498 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3499 if (q != NULL) { // found blank line
3500 dot = next_line(q); // move to next blank line
3503 #endif /* FEATURE_VI_SEARCH */
3504 case '0': // 0- goto beginning of line
3514 if (c == '0' && cmdcnt < 1) {
3515 dot_begin(); // this was a standalone zero
3517 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3520 case ':': // :- the colon mode commands
3521 p = get_input_line(":"); // get input line- use "status line"
3522 colon(p); // execute the command
3524 case '<': // <- Left shift something
3525 case '>': // >- Right shift something
3526 cnt = count_lines(text, dot); // remember what line we are on
3527 c1 = get_one_char(); // get the type of thing to delete
3528 find_range(&p, &q, c1);
3529 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3532 i = count_lines(p, q); // # of lines we are shifting
3533 for ( ; i > 0; i--, p = next_line(p)) {
3535 // shift left- remove tab or 8 spaces
3537 // shrink buffer 1 char
3538 text_hole_delete(p, p, NO_UNDO);
3539 } else if (*p == ' ') {
3540 // we should be calculating columns, not just SPACE
3541 for (j = 0; *p == ' ' && j < tabstop; j++) {
3542 text_hole_delete(p, p, NO_UNDO);
3545 } else if (c == '>') {
3546 // shift right -- add tab or 8 spaces
3547 char_insert(p, '\t', ALLOW_UNDO);
3550 dot = find_line(cnt); // what line were we on
3552 end_cmd_q(); // stop adding to q
3554 case 'A': // A- append at e-o-l
3555 dot_end(); // go to e-o-l
3556 //**** fall through to ... 'a'
3557 case 'a': // a- append after current char
3562 case 'B': // B- back a blank-delimited Word
3563 case 'E': // E- end of a blank-delimited word
3564 case 'W': // W- forward a blank-delimited word
3569 if (c == 'W' || isspace(dot[dir])) {
3570 dot = skip_thing(dot, 1, dir, S_TO_WS);
3571 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3574 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3575 } while (--cmdcnt > 0);
3577 case 'C': // C- Change to e-o-l
3578 case 'D': // D- delete to e-o-l
3580 dot = dollar_line(dot); // move to before NL
3581 // copy text into a register and delete
3582 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3584 goto dc_i; // start inserting
3585 #if ENABLE_FEATURE_VI_DOT_CMD
3587 end_cmd_q(); // stop adding to q
3590 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3591 c1 = get_one_char();
3594 // c1 < 0 if the key was special. Try "g<up-arrow>"
3595 // TODO: if Unicode?
3596 buf[1] = (c1 >= 0 ? c1 : '*');
3598 not_implemented(buf);
3604 case 'G': // G- goto to a line number (default= E-O-F)
3605 dot = end - 1; // assume E-O-F
3607 dot = find_line(cmdcnt); // what line is #cmdcnt
3611 case 'H': // H- goto top line on screen
3613 if (cmdcnt > (rows - 1)) {
3614 cmdcnt = (rows - 1);
3621 case 'I': // I- insert before first non-blank
3624 //**** fall through to ... 'i'
3625 case 'i': // i- insert before current char
3626 case KEYCODE_INSERT: // Cursor Key Insert
3628 cmd_mode = 1; // start inserting
3629 undo_queue_commit(); // commit queue when cmd_mode changes
3631 case 'J': // J- join current and next lines together
3633 dot_end(); // move to NL
3634 if (dot < end - 1) { // make sure not last char in text[]
3635 #if ENABLE_FEATURE_VI_UNDO
3636 undo_push(dot, 1, UNDO_DEL);
3637 *dot++ = ' '; // replace NL with space
3638 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3643 while (isblank(*dot)) { // delete leading WS
3644 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3647 } while (--cmdcnt > 0);
3648 end_cmd_q(); // stop adding to q
3650 case 'L': // L- goto bottom line on screen
3652 if (cmdcnt > (rows - 1)) {
3653 cmdcnt = (rows - 1);
3661 case 'M': // M- goto middle line on screen
3663 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3664 dot = next_line(dot);
3666 case 'O': // O- open a empty line above
3668 p = begin_line(dot);
3669 if (p[-1] == '\n') {
3671 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3673 dot = char_insert(dot, '\n', ALLOW_UNDO);
3676 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3681 case 'R': // R- continuous Replace char
3684 undo_queue_commit();
3686 case KEYCODE_DELETE:
3688 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3690 case 'X': // X- delete char before dot
3691 case 'x': // x- delete the current char
3692 case 's': // s- substitute the current char
3697 if (dot[dir] != '\n') {
3699 dot--; // delete prev char
3700 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3702 } while (--cmdcnt > 0);
3703 end_cmd_q(); // stop adding to q
3705 goto dc_i; // start inserting
3707 case 'Z': // Z- if modified, {write}; exit
3708 // ZZ means to save file (if necessary), then exit
3709 c1 = get_one_char();
3714 if (modified_count) {
3715 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3716 status_line_bold("'%s' is read only", current_filename);
3719 cnt = file_write(current_filename, text, end - 1);
3722 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3723 } else if (cnt == (end - 1 - text + 1)) {
3730 case '^': // ^- move to first non-blank on line
3734 case 'b': // b- back a word
3735 case 'e': // e- end of word
3740 if ((dot + dir) < text || (dot + dir) > end - 1)
3743 if (isspace(*dot)) {
3744 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3746 if (isalnum(*dot) || *dot == '_') {
3747 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3748 } else if (ispunct(*dot)) {
3749 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3751 } while (--cmdcnt > 0);
3753 case 'c': // c- change something
3754 case 'd': // d- delete something
3755 #if ENABLE_FEATURE_VI_YANKMARK
3756 case 'y': // y- yank something
3757 case 'Y': // Y- Yank a line
3760 int yf, ml, whole = 0;
3761 yf = YANKDEL; // assume either "c" or "d"
3762 #if ENABLE_FEATURE_VI_YANKMARK
3763 if (c == 'y' || c == 'Y')
3768 c1 = get_one_char(); // get the type of thing to delete
3769 // determine range, and whether it spans lines
3770 ml = find_range(&p, &q, c1);
3772 if (c1 == 27) { // ESC- user changed mind and wants out
3773 c = c1 = 27; // Escape- do nothing
3774 } else if (strchr("wW", c1)) {
3776 // don't include trailing WS as part of word
3777 while (isblank(*q)) {
3778 if (q <= text || q[-1] == '\n')
3783 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3784 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3785 // partial line copy text into a register and delete
3786 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3787 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3788 // whole line copy text into a register and delete
3789 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3792 // could not recognize object
3793 c = c1 = 27; // error-
3799 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3800 // on the last line of file don't move to prev line
3801 if (whole && dot != (end-1)) {
3804 } else if (c == 'd') {
3810 // if CHANGING, not deleting, start inserting after the delete
3812 strcpy(buf, "Change");
3813 goto dc_i; // start inserting
3816 strcpy(buf, "Delete");
3818 #if ENABLE_FEATURE_VI_YANKMARK
3819 if (c == 'y' || c == 'Y') {
3820 strcpy(buf, "Yank");
3824 for (cnt = 0; p <= q; p++) {
3828 status_line("%s %u lines (%u chars) using [%c]",
3829 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3831 end_cmd_q(); // stop adding to q
3835 case 'k': // k- goto prev line, same col
3836 case KEYCODE_UP: // cursor key Up
3839 dot = move_to_col(dot, ccol + offset); // try stay in same col
3840 } while (--cmdcnt > 0);
3842 case 'r': // r- replace the current char with user input
3843 c1 = get_one_char(); // get the replacement char
3845 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3846 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3849 end_cmd_q(); // stop adding to q
3851 case 't': // t- move to char prior to next x
3852 last_forward_char = get_one_char();
3854 if (*dot == last_forward_char)
3856 last_forward_char = 0;
3858 case 'w': // w- forward a word
3860 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3861 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3862 } else if (ispunct(*dot)) { // we are on PUNCT
3863 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3866 dot++; // move over word
3867 if (isspace(*dot)) {
3868 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3870 } while (--cmdcnt > 0);
3873 c1 = get_one_char(); // get the replacement char
3876 cnt = (rows - 2) / 2; // put dot at center
3878 cnt = rows - 2; // put dot at bottom
3879 screenbegin = begin_line(dot); // start dot at top
3880 dot_scroll(cnt, -1);
3882 case '|': // |- move to column "cmdcnt"
3883 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3885 case '~': // ~- flip the case of letters a-z -> A-Z
3887 #if ENABLE_FEATURE_VI_UNDO
3888 if (islower(*dot)) {
3889 undo_push(dot, 1, UNDO_DEL);
3890 *dot = toupper(*dot);
3891 undo_push(dot, 1, UNDO_INS_CHAIN);
3892 } else if (isupper(*dot)) {
3893 undo_push(dot, 1, UNDO_DEL);
3894 *dot = tolower(*dot);
3895 undo_push(dot, 1, UNDO_INS_CHAIN);
3898 if (islower(*dot)) {
3899 *dot = toupper(*dot);
3901 } else if (isupper(*dot)) {
3902 *dot = tolower(*dot);
3907 } while (--cmdcnt > 0);
3908 end_cmd_q(); // stop adding to q
3910 //----- The Cursor and Function Keys -----------------------------
3911 case KEYCODE_HOME: // Cursor Key Home
3914 // The Fn keys could point to do_macro which could translate them
3916 case KEYCODE_FUN1: // Function Key F1
3917 case KEYCODE_FUN2: // Function Key F2
3918 case KEYCODE_FUN3: // Function Key F3
3919 case KEYCODE_FUN4: // Function Key F4
3920 case KEYCODE_FUN5: // Function Key F5
3921 case KEYCODE_FUN6: // Function Key F6
3922 case KEYCODE_FUN7: // Function Key F7
3923 case KEYCODE_FUN8: // Function Key F8
3924 case KEYCODE_FUN9: // Function Key F9
3925 case KEYCODE_FUN10: // Function Key F10
3926 case KEYCODE_FUN11: // Function Key F11
3927 case KEYCODE_FUN12: // Function Key F12
3933 // if text[] just became empty, add back an empty line
3935 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
3938 // it is OK for dot to exactly equal to end, otherwise check dot validity
3940 dot = bound_dot(dot); // make sure "dot" is valid
3942 #if ENABLE_FEATURE_VI_YANKMARK
3943 check_context(c); // update the current context
3947 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3948 cnt = dot - begin_line(dot);
3949 // Try to stay off of the Newline
3950 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3954 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3955 #if ENABLE_FEATURE_VI_CRASHME
3956 static int totalcmds = 0;
3957 static int Mp = 85; // Movement command Probability
3958 static int Np = 90; // Non-movement command Probability
3959 static int Dp = 96; // Delete command Probability
3960 static int Ip = 97; // Insert command Probability
3961 static int Yp = 98; // Yank command Probability
3962 static int Pp = 99; // Put command Probability
3963 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3964 static const char chars[20] = "\t012345 abcdABCD-=.$";
3965 static const char *const words[20] = {
3966 "this", "is", "a", "test",
3967 "broadcast", "the", "emergency", "of",
3968 "system", "quick", "brown", "fox",
3969 "jumped", "over", "lazy", "dogs",
3970 "back", "January", "Febuary", "March"
3972 static const char *const lines[20] = {
3973 "You should have received a copy of the GNU General Public License\n",
3974 "char c, cm, *cmd, *cmd1;\n",
3975 "generate a command by percentages\n",
3976 "Numbers may be typed as a prefix to some commands.\n",
3977 "Quit, discarding changes!\n",
3978 "Forced write, if permission originally not valid.\n",
3979 "In general, any ex or ed command (such as substitute or delete).\n",
3980 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3981 "Please get w/ me and I will go over it with you.\n",
3982 "The following is a list of scheduled, committed changes.\n",
3983 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3984 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3985 "Any question about transactions please contact Sterling Huxley.\n",
3986 "I will try to get back to you by Friday, December 31.\n",
3987 "This Change will be implemented on Friday.\n",
3988 "Let me know if you have problems accessing this;\n",
3989 "Sterling Huxley recently added you to the access list.\n",
3990 "Would you like to go to lunch?\n",
3991 "The last command will be automatically run.\n",
3992 "This is too much english for a computer geek.\n",
3994 static char *multilines[20] = {
3995 "You should have received a copy of the GNU General Public License\n",
3996 "char c, cm, *cmd, *cmd1;\n",
3997 "generate a command by percentages\n",
3998 "Numbers may be typed as a prefix to some commands.\n",
3999 "Quit, discarding changes!\n",
4000 "Forced write, if permission originally not valid.\n",
4001 "In general, any ex or ed command (such as substitute or delete).\n",
4002 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4003 "Please get w/ me and I will go over it with you.\n",
4004 "The following is a list of scheduled, committed changes.\n",
4005 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4006 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4007 "Any question about transactions please contact Sterling Huxley.\n",
4008 "I will try to get back to you by Friday, December 31.\n",
4009 "This Change will be implemented on Friday.\n",
4010 "Let me know if you have problems accessing this;\n",
4011 "Sterling Huxley recently added you to the access list.\n",
4012 "Would you like to go to lunch?\n",
4013 "The last command will be automatically run.\n",
4014 "This is too much english for a computer geek.\n",
4017 // create a random command to execute
4018 static void crash_dummy()
4020 static int sleeptime; // how long to pause between commands
4021 char c, cm, *cmd, *cmd1;
4022 int i, cnt, thing, rbi, startrbi, percent;
4024 // "dot" movement commands
4025 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4027 // is there already a command running?
4028 if (readbuffer[0] > 0)
4031 readbuffer[0] = 'X';
4033 sleeptime = 0; // how long to pause between commands
4034 memset(readbuffer, '\0', sizeof(readbuffer));
4035 // generate a command by percentages
4036 percent = (int) lrand48() % 100; // get a number from 0-99
4037 if (percent < Mp) { // Movement commands
4038 // available commands
4041 } else if (percent < Np) { // non-movement commands
4042 cmd = "mz<>\'\""; // available commands
4044 } else if (percent < Dp) { // Delete commands
4045 cmd = "dx"; // available commands
4047 } else if (percent < Ip) { // Inset commands
4048 cmd = "iIaAsrJ"; // available commands
4050 } else if (percent < Yp) { // Yank commands
4051 cmd = "yY"; // available commands
4053 } else if (percent < Pp) { // Put commands
4054 cmd = "pP"; // available commands
4057 // We do not know how to handle this command, try again
4061 // randomly pick one of the available cmds from "cmd[]"
4062 i = (int) lrand48() % strlen(cmd);
4064 if (strchr(":\024", cm))
4065 goto cd0; // dont allow colon or ctrl-T commands
4066 readbuffer[rbi++] = cm; // put cmd into input buffer
4068 // now we have the command-
4069 // there are 1, 2, and multi char commands
4070 // find out which and generate the rest of command as necessary
4071 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4072 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4073 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4074 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4076 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4078 readbuffer[rbi++] = c; // add movement to input buffer
4080 if (strchr("iIaAsc", cm)) { // multi-char commands
4082 // change some thing
4083 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4085 readbuffer[rbi++] = c; // add movement to input buffer
4087 thing = (int) lrand48() % 4; // what thing to insert
4088 cnt = (int) lrand48() % 10; // how many to insert
4089 for (i = 0; i < cnt; i++) {
4090 if (thing == 0) { // insert chars
4091 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4092 } else if (thing == 1) { // insert words
4093 strcat(readbuffer, words[(int) lrand48() % 20]);
4094 strcat(readbuffer, " ");
4095 sleeptime = 0; // how fast to type
4096 } else if (thing == 2) { // insert lines
4097 strcat(readbuffer, lines[(int) lrand48() % 20]);
4098 sleeptime = 0; // how fast to type
4099 } else { // insert multi-lines
4100 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4101 sleeptime = 0; // how fast to type
4104 strcat(readbuffer, ESC);
4106 readbuffer[0] = strlen(readbuffer + 1);
4110 mysleep(sleeptime); // sleep 1/100 sec
4113 // test to see if there are any errors
4114 static void crash_test()
4116 static time_t oldtim;
4123 strcat(msg, "end<text ");
4125 if (end > textend) {
4126 strcat(msg, "end>textend ");
4129 strcat(msg, "dot<text ");
4132 strcat(msg, "dot>end ");
4134 if (screenbegin < text) {
4135 strcat(msg, "screenbegin<text ");
4137 if (screenbegin > end - 1) {
4138 strcat(msg, "screenbegin>end-1 ");
4142 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4143 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4145 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4146 if (d[0] == '\n' || d[0] == '\r')
4151 if (tim >= (oldtim + 3)) {
4152 sprintf(status_buffer,
4153 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4154 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4160 static void edit_file(char *fn)
4162 #if ENABLE_FEATURE_VI_YANKMARK
4163 #define cur_line edit_file__cur_line
4166 #if ENABLE_FEATURE_VI_USE_SIGNALS
4170 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4174 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4175 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4176 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4178 write1(ESC"[999;999H" ESC"[6n");
4180 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4181 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4182 uint32_t rc = (k >> 32);
4183 columns = (rc & 0x7fff);
4184 if (columns > MAX_SCR_COLS)
4185 columns = MAX_SCR_COLS;
4186 rows = ((rc >> 16) & 0x7fff);
4187 if (rows > MAX_SCR_ROWS)
4188 rows = MAX_SCR_ROWS;
4192 new_screen(rows, columns); // get memory for virtual screen
4193 init_text_buffer(fn);
4195 #if ENABLE_FEATURE_VI_YANKMARK
4196 YDreg = 26; // default Yank/Delete reg
4197 // Ureg = 27; - const // hold orig line for "U" cmd
4198 mark[26] = mark[27] = text; // init "previous context"
4201 last_forward_char = last_input_char = '\0';
4205 #if ENABLE_FEATURE_VI_USE_SIGNALS
4206 signal(SIGWINCH, winch_handler);
4207 signal(SIGTSTP, tstp_handler);
4208 sig = sigsetjmp(restart, 1);
4210 screenbegin = dot = text;
4212 // int_handler() can jump to "restart",
4213 // must install handler *after* initializing "restart"
4214 signal(SIGINT, int_handler);
4217 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4220 offset = 0; // no horizontal offset
4222 #if ENABLE_FEATURE_VI_DOT_CMD
4224 ioq = ioq_start = NULL;
4229 #if ENABLE_FEATURE_VI_COLON
4234 while ((p = initial_cmds[n]) != NULL) {
4237 p = strchr(q, '\n');
4244 free(initial_cmds[n]);
4245 initial_cmds[n] = NULL;
4250 redraw(FALSE); // dont force every col re-draw
4251 //------This is the main Vi cmd handling loop -----------------------
4252 while (editing > 0) {
4253 #if ENABLE_FEATURE_VI_CRASHME
4255 if ((end - text) > 1) {
4256 crash_dummy(); // generate a random command
4259 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
4265 last_input_char = c = get_one_char(); // get a cmd from user
4266 #if ENABLE_FEATURE_VI_YANKMARK
4267 // save a copy of the current line- for the 'U" command
4268 if (begin_line(dot) != cur_line) {
4269 cur_line = begin_line(dot);
4270 text_yank(begin_line(dot), end_line(dot), Ureg);
4273 #if ENABLE_FEATURE_VI_DOT_CMD
4274 // These are commands that change text[].
4275 // Remember the input for the "." command
4276 if (!adding2q && ioq_start == NULL
4277 && cmd_mode == 0 // command mode
4278 && c > '\0' // exclude NUL and non-ASCII chars
4279 && c < 0x7f // (Unicode and such)
4280 && strchr(modifying_cmds, c)
4285 do_cmd(c); // execute the user command
4287 // poll to see if there is input already waiting. if we are
4288 // not able to display output fast enough to keep up, skip
4289 // the display update until we catch up with input.
4290 if (!readbuffer[0] && mysleep(0) == 0) {
4291 // no input pending - so update output
4295 #if ENABLE_FEATURE_VI_CRASHME
4297 crash_test(); // test editor variables
4300 //-------------------------------------------------------------------
4302 go_bottom_and_clear_to_eol();
4307 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4308 int vi_main(int argc, char **argv)
4314 #if ENABLE_FEATURE_VI_UNDO
4315 /* undo_stack_tail = NULL; - already is */
4316 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4317 undo_queue_state = UNDO_EMPTY;
4318 /* undo_q = 0; - already is */
4322 #if ENABLE_FEATURE_VI_CRASHME
4323 srand((long) getpid());
4325 #ifdef NO_SUCH_APPLET_YET
4326 // if we aren't "vi", we are "view"
4327 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4328 SET_READONLY_MODE(readonly_mode);
4332 // autoindent is not default in vim 7.3
4333 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4334 // 1- process $HOME/.exrc file (not inplemented yet)
4335 // 2- process EXINIT variable from environment
4336 // 3- process command line args
4337 #if ENABLE_FEATURE_VI_COLON
4339 char *p = getenv("EXINIT");
4341 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4344 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4346 #if ENABLE_FEATURE_VI_CRASHME
4351 #if ENABLE_FEATURE_VI_READONLY
4352 case 'R': // Read-only flag
4353 SET_READONLY_MODE(readonly_mode);
4356 #if ENABLE_FEATURE_VI_COLON
4357 case 'c': // cmd line vi command
4359 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4371 // The argv array can be used by the ":next" and ":rewind" commands
4375 //----- This is the main file handling loop --------------
4378 // "Save cursor, use alternate screen buffer, clear screen"
4379 write1(ESC"[?1049h");
4381 edit_file(argv[optind]); // param might be NULL
4382 if (++optind >= argc)
4385 // "Use normal screen buffer, restore cursor"
4386 write1(ESC"[?1049l");
4387 //-----------------------------------------------------------