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 #if ENABLE_FEATURE_VI_DOT_CMD
1033 static int get_one_char(void)
1038 // we are not adding to the q.
1039 // but, we may be reading from a saved q.
1040 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1041 // when done - "ioq_start" is reset instead).
1042 if (ioq_start != NULL) {
1043 // there is a queue to get chars from.
1044 // careful with correct sign expansion!
1045 c = (unsigned char)*ioq++;
1055 // we are adding STDIN chars to q.
1057 if (lmc_len >= MAX_INPUT_LEN - 1) {
1058 status_line_bold("last_modifying_cmd overrun");
1060 last_modifying_cmd[lmc_len++] = c;
1065 # define get_one_char() readit()
1068 // Get input line (uses "status line" area)
1069 static char *get_input_line(const char *prompt)
1071 // char [MAX_INPUT_LEN]
1072 #define buf get_input_line__buf
1077 strcpy(buf, prompt);
1078 last_status_cksum = 0; // force status update
1079 go_bottom_and_clear_to_eol();
1080 write1(prompt); // write out the :, /, or ? prompt
1083 while (i < MAX_INPUT_LEN) {
1085 if (c == '\n' || c == '\r' || c == 27)
1086 break; // this is end of input
1087 if (c == erase_char || c == 8 || c == 127) {
1088 // user wants to erase prev char
1090 write1("\b \b"); // erase char on screen
1091 if (i <= 0) // user backs up before b-o-l, exit
1093 } else if (c > 0 && c < 256) { // exclude Unicode
1094 // (TODO: need to handle Unicode)
1105 static void Hit_Return(void)
1110 write1("[Hit return to continue]");
1112 while ((c = get_one_char()) != '\n' && c != '\r')
1114 redraw(TRUE); // force redraw all
1117 //----- Draw the status line at bottom of the screen -------------
1118 // show file status on status line
1119 static int format_edit_status(void)
1121 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1123 #define tot format_edit_status__tot
1125 int cur, percent, ret, trunc_at;
1127 // modified_count is now a counter rather than a flag. this
1128 // helps reduce the amount of line counting we need to do.
1129 // (this will cause a mis-reporting of modified status
1130 // once every MAXINT editing operations.)
1132 // it would be nice to do a similar optimization here -- if
1133 // we haven't done a motion that could have changed which line
1134 // we're on, then we shouldn't have to do this count_lines()
1135 cur = count_lines(text, dot);
1137 // count_lines() is expensive.
1138 // Call it only if something was changed since last time
1140 if (modified_count != last_modified_count) {
1141 tot = cur + count_lines(dot, end - 1) - 1;
1142 last_modified_count = modified_count;
1145 // current line percent
1146 // ------------- ~~ ----------
1149 percent = (100 * cur) / tot;
1155 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1156 columns : STATUS_BUFFER_LEN-1;
1158 ret = snprintf(status_buffer, trunc_at+1,
1159 #if ENABLE_FEATURE_VI_READONLY
1160 "%c %s%s%s %d/%d %d%%",
1162 "%c %s%s %d/%d %d%%",
1164 cmd_mode_indicator[cmd_mode & 3],
1165 (current_filename != NULL ? current_filename : "No file"),
1166 #if ENABLE_FEATURE_VI_READONLY
1167 (readonly_mode ? " [Readonly]" : ""),
1169 (modified_count ? " [Modified]" : ""),
1172 if (ret >= 0 && ret < trunc_at)
1173 return ret; // it all fit
1175 return trunc_at; // had to truncate
1179 static int bufsum(char *buf, int count)
1182 char *e = buf + count;
1184 sum += (unsigned char) *buf++;
1188 static void show_status_line(void)
1190 int cnt = 0, cksum = 0;
1192 // either we already have an error or status message, or we
1194 if (!have_status_msg) {
1195 cnt = format_edit_status();
1196 cksum = bufsum(status_buffer, cnt);
1198 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1199 last_status_cksum = cksum; // remember if we have seen this line
1200 go_bottom_and_clear_to_eol();
1201 write1(status_buffer);
1202 if (have_status_msg) {
1203 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1205 have_status_msg = 0;
1208 have_status_msg = 0;
1210 place_cursor(crow, ccol); // put cursor back in correct place
1215 //----- format the status buffer, the bottom line of screen ------
1216 static void status_line(const char *format, ...)
1220 va_start(args, format);
1221 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1224 have_status_msg = 1;
1226 static void status_line_bold(const char *format, ...)
1230 va_start(args, format);
1231 strcpy(status_buffer, ESC_BOLD_TEXT);
1232 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1233 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1236 strcat(status_buffer, ESC_NORM_TEXT);
1239 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
1241 static void status_line_bold_errno(const char *fn)
1243 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1246 // copy s to buf, convert unprintable
1247 static void print_literal(char *buf, const char *s)
1261 c_is_no_print = (c & 0x80) && !Isprint(c);
1262 if (c_is_no_print) {
1263 strcpy(d, ESC_NORM_TEXT);
1264 d += sizeof(ESC_NORM_TEXT)-1;
1267 if (c < ' ' || c == 0x7f) {
1275 if (c_is_no_print) {
1276 strcpy(d, ESC_BOLD_TEXT);
1277 d += sizeof(ESC_BOLD_TEXT)-1;
1283 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1287 static void not_implemented(const char *s)
1289 char buf[MAX_INPUT_LEN];
1290 print_literal(buf, s);
1291 status_line_bold("'%s' is not implemented", buf);
1294 //----- Block insert/delete, undo ops --------------------------
1295 #if ENABLE_FEATURE_VI_YANKMARK
1296 static char *text_yank(char *p, char *q, int dest) // copy text into a register
1299 if (cnt < 0) { // they are backwards- reverse them
1303 free(reg[dest]); // if already a yank register, free it
1304 reg[dest] = xstrndup(p, cnt + 1);
1308 static char what_reg(void)
1312 c = 'D'; // default to D-reg
1313 if (0 <= YDreg && YDreg <= 25)
1314 c = 'a' + (char) YDreg;
1322 static void check_context(char cmd)
1324 // A context is defined to be "modifying text"
1325 // Any modifying command establishes a new context.
1327 if (dot < context_start || dot > context_end) {
1328 if (strchr(modifying_cmds, cmd) != NULL) {
1329 // we are trying to modify text[]- make this the current context
1330 mark[27] = mark[26]; // move cur to prev
1331 mark[26] = dot; // move local to cur
1332 context_start = prev_line(prev_line(dot));
1333 context_end = next_line(next_line(dot));
1334 //loiter= start_loiter= now;
1339 static char *swap_context(char *p) // goto new context for '' command make this the current context
1343 // the current context is in mark[26]
1344 // the previous context is in mark[27]
1345 // only swap context if other context is valid
1346 if (text <= mark[27] && mark[27] <= end - 1) {
1350 context_start = prev_line(prev_line(prev_line(p)));
1351 context_end = next_line(next_line(next_line(p)));
1355 #endif /* FEATURE_VI_YANKMARK */
1357 #if ENABLE_FEATURE_VI_UNDO
1358 static void undo_push(char *, unsigned, unsigned char);
1361 // open a hole in text[]
1362 // might reallocate text[]! use p += text_hole_make(p, ...),
1363 // and be careful to not use pointers into potentially freed text[]!
1364 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1370 end += size; // adjust the new END
1371 if (end >= (text + text_size)) {
1373 text_size += end - (text + text_size) + 10240;
1374 new_text = xrealloc(text, text_size);
1375 bias = (new_text - text);
1376 screenbegin += bias;
1380 #if ENABLE_FEATURE_VI_YANKMARK
1383 for (i = 0; i < ARRAY_SIZE(mark); i++)
1390 memmove(p + size, p, end - size - p);
1391 memset(p, ' ', size); // clear new hole
1395 // close a hole in text[] - delete "p" through "q", inclusive
1396 // "undo" value indicates if this operation should be undo-able
1397 #if !ENABLE_FEATURE_VI_UNDO
1398 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
1400 static char *text_hole_delete(char *p, char *q, int undo)
1405 // move forwards, from beginning
1409 if (q < p) { // they are backward- swap them
1413 hole_size = q - p + 1;
1415 #if ENABLE_FEATURE_VI_UNDO
1420 undo_push(p, hole_size, UNDO_DEL);
1422 case ALLOW_UNDO_CHAIN:
1423 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1425 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1426 case ALLOW_UNDO_QUEUED:
1427 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1433 if (src < text || src > end)
1435 if (dest < text || dest >= end)
1439 goto thd_atend; // just delete the end of the buffer
1440 memmove(dest, src, cnt);
1442 end = end - hole_size; // adjust the new END
1444 dest = end - 1; // make sure dest in below end-1
1446 dest = end = text; // keep pointers valid
1451 #if ENABLE_FEATURE_VI_UNDO
1453 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1454 // Flush any queued objects to the undo stack
1455 static void undo_queue_commit(void)
1457 // Pushes the queue object onto the undo stack
1459 // Deleted character undo events grow from the end
1460 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1462 (undo_queue_state | UNDO_USE_SPOS)
1464 undo_queue_state = UNDO_EMPTY;
1469 # define undo_queue_commit() ((void)0)
1472 static void flush_undo_data(void)
1474 struct undo_object *undo_entry;
1476 while (undo_stack_tail) {
1477 undo_entry = undo_stack_tail;
1478 undo_stack_tail = undo_entry->prev;
1483 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1484 // Add to the undo stack
1485 static void undo_push(char *src, unsigned length, uint8_t u_type)
1487 struct undo_object *undo_entry;
1490 // UNDO_INS: insertion, undo will remove from buffer
1491 // UNDO_DEL: deleted text, undo will restore to buffer
1492 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1493 // The CHAIN operations are for handling multiple operations that the user
1494 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1495 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1496 // for the INS/DEL operation. The raw values should be equal to the values of
1497 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1499 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1500 // This undo queuing functionality groups multiple character typing or backspaces
1501 // into a single large undo object. This greatly reduces calls to malloc() for
1502 // single-character operations while typing and has the side benefit of letting
1503 // an undo operation remove chunks of text rather than a single character.
1505 case UNDO_EMPTY: // Just in case this ever happens...
1507 case UNDO_DEL_QUEUED:
1509 return; // Only queue single characters
1510 switch (undo_queue_state) {
1512 undo_queue_state = UNDO_DEL;
1514 undo_queue_spos = src;
1516 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1517 // If queue is full, dump it into an object
1518 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1519 undo_queue_commit();
1522 // Switch from storing inserted text to deleted text
1523 undo_queue_commit();
1524 undo_push(src, length, UNDO_DEL_QUEUED);
1528 case UNDO_INS_QUEUED:
1531 switch (undo_queue_state) {
1533 undo_queue_state = UNDO_INS;
1534 undo_queue_spos = src;
1537 undo_q++; // Don't need to save any data for insertions
1538 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1539 undo_queue_commit();
1543 // Switch from storing deleted text to inserted text
1544 undo_queue_commit();
1545 undo_push(src, length, UNDO_INS_QUEUED);
1551 // If undo queuing is disabled, ignore the queuing flag entirely
1552 u_type = u_type & ~UNDO_QUEUED_FLAG;
1555 // Allocate a new undo object
1556 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1557 // For UNDO_DEL objects, save deleted text
1558 if ((text + length) == end)
1560 // If this deletion empties text[], strip the newline. When the buffer becomes
1561 // zero-length, a newline is added back, which requires this to compensate.
1562 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1563 memcpy(undo_entry->undo_text, src, length);
1565 undo_entry = xzalloc(sizeof(*undo_entry));
1567 undo_entry->length = length;
1568 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1569 if ((u_type & UNDO_USE_SPOS) != 0) {
1570 undo_entry->start = undo_queue_spos - text; // use start position from queue
1572 undo_entry->start = src - text; // use offset from start of text buffer
1574 u_type = (u_type & ~UNDO_USE_SPOS);
1576 undo_entry->start = src - text;
1578 undo_entry->u_type = u_type;
1580 // Push it on undo stack
1581 undo_entry->prev = undo_stack_tail;
1582 undo_stack_tail = undo_entry;
1586 static void undo_push_insert(char *p, int len, int undo)
1590 undo_push(p, len, UNDO_INS);
1592 case ALLOW_UNDO_CHAIN:
1593 undo_push(p, len, UNDO_INS_CHAIN);
1595 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1596 case ALLOW_UNDO_QUEUED:
1597 undo_push(p, len, UNDO_INS_QUEUED);
1603 // Undo the last operation
1604 static void undo_pop(void)
1607 char *u_start, *u_end;
1608 struct undo_object *undo_entry;
1610 // Commit pending undo queue before popping (should be unnecessary)
1611 undo_queue_commit();
1613 undo_entry = undo_stack_tail;
1614 // Check for an empty undo stack
1616 status_line("Already at oldest change");
1620 switch (undo_entry->u_type) {
1622 case UNDO_DEL_CHAIN:
1623 // make hole and put in text that was deleted; deallocate text
1624 u_start = text + undo_entry->start;
1625 text_hole_make(u_start, undo_entry->length);
1626 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1627 status_line("Undo [%d] %s %d chars at position %d",
1628 modified_count, "restored",
1629 undo_entry->length, undo_entry->start
1633 case UNDO_INS_CHAIN:
1634 // delete what was inserted
1635 u_start = undo_entry->start + text;
1636 u_end = u_start - 1 + undo_entry->length;
1637 text_hole_delete(u_start, u_end, NO_UNDO);
1638 status_line("Undo [%d] %s %d chars at position %d",
1639 modified_count, "deleted",
1640 undo_entry->length, undo_entry->start
1645 switch (undo_entry->u_type) {
1646 // If this is the end of a chain, lower modification count and refresh display
1649 dot = (text + undo_entry->start);
1652 case UNDO_DEL_CHAIN:
1653 case UNDO_INS_CHAIN:
1657 // Deallocate the undo object we just processed
1658 undo_stack_tail = undo_entry->prev;
1661 // For chained operations, continue popping all the way down the chain.
1663 undo_pop(); // Follow the undo chain if one exists
1668 # define flush_undo_data() ((void)0)
1669 # define undo_queue_commit() ((void)0)
1670 #endif /* ENABLE_FEATURE_VI_UNDO */
1672 //----- Dot Movement Routines ----------------------------------
1673 static void dot_left(void)
1675 undo_queue_commit();
1676 if (dot > text && dot[-1] != '\n')
1680 static void dot_right(void)
1682 undo_queue_commit();
1683 if (dot < end - 1 && *dot != '\n')
1687 static void dot_begin(void)
1689 undo_queue_commit();
1690 dot = begin_line(dot); // return pointer to first char cur line
1693 static void dot_end(void)
1695 undo_queue_commit();
1696 dot = end_line(dot); // return pointer to last char cur line
1699 static char *move_to_col(char *p, int l)
1705 while (co < l && p < end) {
1706 if (*p == '\n') //vda || *p == '\0')
1709 co = next_tabstop(co);
1710 } else if (*p < ' ' || *p == 127) {
1711 co++; // display as ^X, use 2 columns
1719 static void dot_next(void)
1721 undo_queue_commit();
1722 dot = next_line(dot);
1725 static void dot_prev(void)
1727 undo_queue_commit();
1728 dot = prev_line(dot);
1731 static void dot_skip_over_ws(void)
1734 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1738 static void dot_scroll(int cnt, int dir)
1742 undo_queue_commit();
1743 for (; cnt > 0; cnt--) {
1746 // ctrl-Y scroll up one line
1747 screenbegin = prev_line(screenbegin);
1750 // ctrl-E scroll down one line
1751 screenbegin = next_line(screenbegin);
1754 // make sure "dot" stays on the screen so we dont scroll off
1755 if (dot < screenbegin)
1757 q = end_screen(); // find new bottom line
1759 dot = begin_line(q); // is dot is below bottom line?
1763 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1765 if (p >= end && end > text) {
1776 #if ENABLE_FEATURE_VI_DOT_CMD
1777 static void start_new_cmd_q(char c)
1779 // get buffer for new cmd
1780 // if there is a current cmd count put it in the buffer first
1782 lmc_len = sprintf(last_modifying_cmd, "%u%c", cmdcnt, c);
1783 } else { // just save char c onto queue
1784 last_modifying_cmd[0] = c;
1789 static void end_cmd_q(void)
1791 # if ENABLE_FEATURE_VI_YANKMARK
1792 YDreg = 26; // go back to default Yank/Delete reg
1797 # define end_cmd_q() ((void)0)
1798 #endif /* FEATURE_VI_DOT_CMD */
1800 // copy text into register, then delete text.
1801 // if dist <= 0, do not include, or go past, a NewLine
1803 #if !ENABLE_FEATURE_VI_UNDO
1804 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1806 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1810 // make sure start <= stop
1812 // they are backwards, reverse them
1818 // we cannot cross NL boundaries
1822 // dont go past a NewLine
1823 for (; p + 1 <= stop; p++) {
1825 stop = p; // "stop" just before NewLine
1831 #if ENABLE_FEATURE_VI_YANKMARK
1832 text_yank(start, stop, YDreg);
1834 if (yf == YANKDEL) {
1835 p = text_hole_delete(start, stop, undo);
1840 // might reallocate text[]!
1841 static int file_insert(const char *fn, char *p, int initial)
1845 struct stat statbuf;
1852 fd = open(fn, O_RDONLY);
1855 status_line_bold_errno(fn);
1860 if (fstat(fd, &statbuf) < 0) {
1861 status_line_bold_errno(fn);
1864 if (!S_ISREG(statbuf.st_mode)) {
1865 status_line_bold("'%s' is not a regular file", fn);
1868 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1869 p += text_hole_make(p, size);
1870 cnt = full_read(fd, p, size);
1872 status_line_bold_errno(fn);
1873 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1874 } else if (cnt < size) {
1875 // There was a partial read, shrink unused space
1876 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1877 status_line_bold("can't read '%s'", fn);
1882 #if ENABLE_FEATURE_VI_READONLY
1884 && ((access(fn, W_OK) < 0) ||
1885 // root will always have access()
1886 // so we check fileperms too
1887 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1890 SET_READONLY_FILE(readonly_mode);
1896 // find matching char of pair () [] {}
1897 // will crash if c is not one of these
1898 static char *find_pair(char *p, const char c)
1900 const char *braces = "()[]{}";
1904 dir = strchr(braces, c) - braces;
1906 match = braces[dir];
1907 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1909 // look for match, count levels of pairs (( ))
1913 if (p < text || p >= end)
1916 level++; // increase pair levels
1918 level--; // reduce pair level
1920 return p; // found matching pair
1925 #if ENABLE_FEATURE_VI_SETOPTS
1926 // show the matching char of a pair, () [] {}
1927 static void showmatching(char *p)
1931 // we found half of a pair
1932 q = find_pair(p, *p); // get loc of matching char
1934 indicate_error(); // no matching char
1936 // "q" now points to matching pair
1937 save_dot = dot; // remember where we are
1938 dot = q; // go to new loc
1939 refresh(FALSE); // let the user see it
1940 mysleep(40); // give user some time
1941 dot = save_dot; // go back to old loc
1945 #endif /* FEATURE_VI_SETOPTS */
1947 // might reallocate text[]! use p += stupid_insert(p, ...),
1948 // and be careful to not use pointers into potentially freed text[]!
1949 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1952 bias = text_hole_make(p, 1);
1958 #if !ENABLE_FEATURE_VI_UNDO
1959 #define char_insert(a,b,c) char_insert(a,b)
1961 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1963 if (c == 22) { // Is this an ctrl-V?
1964 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1965 refresh(FALSE); // show the ^
1968 #if ENABLE_FEATURE_VI_UNDO
1969 undo_push_insert(p, 1, undo);
1972 #endif /* ENABLE_FEATURE_VI_UNDO */
1974 } else if (c == 27) { // Is this an ESC?
1976 undo_queue_commit();
1978 end_cmd_q(); // stop adding to q
1979 last_status_cksum = 0; // force status update
1980 if ((p[-1] != '\n') && (dot > text)) {
1983 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1986 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1989 // insert a char into text[]
1991 c = '\n'; // translate \r to \n
1992 #if ENABLE_FEATURE_VI_UNDO
1993 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1995 undo_queue_commit();
1997 undo_push_insert(p, 1, undo);
2000 #endif /* ENABLE_FEATURE_VI_UNDO */
2001 p += 1 + stupid_insert(p, c); // insert the char
2002 #if ENABLE_FEATURE_VI_SETOPTS
2003 if (showmatch && strchr(")]}", c) != NULL) {
2004 showmatching(p - 1);
2006 if (autoindent && c == '\n') { // auto indent the new line
2009 q = prev_line(p); // use prev line as template
2010 len = strspn(q, " \t"); // space or tab
2013 bias = text_hole_make(p, len);
2016 #if ENABLE_FEATURE_VI_UNDO
2017 undo_push_insert(p, len, undo);
2028 // read text from file or create an empty buf
2029 // will also update current_filename
2030 static int init_text_buffer(char *fn)
2034 // allocate/reallocate text buffer
2037 screenbegin = dot = end = text = xzalloc(text_size);
2039 if (fn != current_filename) {
2040 free(current_filename);
2041 current_filename = xstrdup(fn);
2043 rc = file_insert(fn, text, 1);
2045 // file doesnt exist. Start empty buf with dummy line
2046 char_insert(text, '\n', NO_UNDO);
2051 last_modified_count = -1;
2052 #if ENABLE_FEATURE_VI_YANKMARK
2054 memset(mark, 0, sizeof(mark));
2059 #if ENABLE_FEATURE_VI_YANKMARK \
2060 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2061 || ENABLE_FEATURE_VI_CRASHME
2062 // might reallocate text[]! use p += string_insert(p, ...),
2063 // and be careful to not use pointers into potentially freed text[]!
2064 # if !ENABLE_FEATURE_VI_UNDO
2065 # define string_insert(a,b,c) string_insert(a,b)
2067 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2073 #if ENABLE_FEATURE_VI_UNDO
2074 undo_push_insert(p, i, undo);
2076 bias = text_hole_make(p, i);
2079 #if ENABLE_FEATURE_VI_YANKMARK
2082 for (cnt = 0; *s != '\0'; s++) {
2086 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2093 static int file_write(char *fn, char *first, char *last)
2095 int fd, cnt, charcnt;
2098 status_line_bold("No current filename");
2101 // By popular request we do not open file with O_TRUNC,
2102 // but instead ftruncate() it _after_ successful write.
2103 // Might reduce amount of data lost on power fail etc.
2104 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2107 cnt = last - first + 1;
2108 charcnt = full_write(fd, first, cnt);
2109 ftruncate(fd, charcnt);
2110 if (charcnt == cnt) {
2112 //modified_count = FALSE;
2120 #if ENABLE_FEATURE_VI_SEARCH
2121 # if ENABLE_FEATURE_VI_REGEX_SEARCH
2122 // search for pattern starting at p
2123 static char *char_search(char *p, const char *pat, int dir_and_range)
2125 struct re_pattern_buffer preg;
2132 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2134 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2136 memset(&preg, 0, sizeof(preg));
2137 err = re_compile_pattern(pat, strlen(pat), &preg);
2139 status_line_bold("bad search pattern '%s': %s", pat, err);
2143 range = (dir_and_range & 1);
2144 q = end - 1; // if FULL
2145 if (range == LIMITED)
2147 if (dir_and_range < 0) { // BACK?
2149 if (range == LIMITED)
2153 // RANGE could be negative if we are searching backwards
2163 // search for the compiled pattern, preg, in p[]
2164 // range < 0: search backward
2165 // range > 0: search forward
2167 // re_search() < 0: not found or error
2168 // re_search() >= 0: index of found pattern
2169 // struct pattern char int int int struct reg
2170 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2171 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2175 if (dir_and_range > 0) // FORWARD?
2182 # if ENABLE_FEATURE_VI_SETOPTS
2183 static int mycmp(const char *s1, const char *s2, int len)
2186 return strncasecmp(s1, s2, len);
2188 return strncmp(s1, s2, len);
2191 # define mycmp strncmp
2193 static char *char_search(char *p, const char *pat, int dir_and_range)
2200 range = (dir_and_range & 1);
2201 if (dir_and_range > 0) { //FORWARD?
2202 stop = end - 1; // assume range is p..end-1
2203 if (range == LIMITED)
2204 stop = next_line(p); // range is to next line
2205 for (start = p; start < stop; start++) {
2206 if (mycmp(start, pat, len) == 0) {
2211 stop = text; // assume range is text..p
2212 if (range == LIMITED)
2213 stop = prev_line(p); // range is to prev line
2214 for (start = p - len; start >= stop; start--) {
2215 if (mycmp(start, pat, len) == 0) {
2220 // pattern not found
2224 #endif /* FEATURE_VI_SEARCH */
2226 //----- The Colon commands -------------------------------------
2227 #if ENABLE_FEATURE_VI_COLON
2228 static char *get_one_address(char *p, int *addr) // get colon addr, if present
2232 IF_FEATURE_VI_YANKMARK(char c;)
2233 IF_FEATURE_VI_SEARCH(char *pat;)
2235 *addr = -1; // assume no addr
2236 if (*p == '.') { // the current line
2238 q = begin_line(dot);
2239 *addr = count_lines(text, q);
2241 #if ENABLE_FEATURE_VI_YANKMARK
2242 else if (*p == '\'') { // is this a mark addr
2246 if (c >= 'a' && c <= 'z') {
2249 q = mark[(unsigned char) c];
2250 if (q != NULL) { // is mark valid
2251 *addr = count_lines(text, q);
2256 #if ENABLE_FEATURE_VI_SEARCH
2257 else if (*p == '/') { // a search pattern
2258 q = strchrnul(++p, '/');
2259 pat = xstrndup(p, q - p); // save copy of pattern
2263 q = char_search(dot, pat, (FORWARD << 1) | FULL);
2265 *addr = count_lines(text, q);
2270 else if (*p == '$') { // the last line in file
2272 q = begin_line(end - 1);
2273 *addr = count_lines(text, q);
2274 } else if (isdigit(*p)) { // specific line number
2275 sscanf(p, "%d%n", addr, &st);
2278 // unrecognized address - assume -1
2284 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
2286 //----- get the address' i.e., 1,3 'a,'b -----
2287 // get FIRST addr, if present
2289 p++; // skip over leading spaces
2290 if (*p == '%') { // alias for 1,$
2293 *e = count_lines(text, end-1);
2296 p = get_one_address(p, b);
2299 if (*p == ',') { // is there a address separator
2303 // get SECOND addr, if present
2304 p = get_one_address(p, e);
2308 p++; // skip over trailing spaces
2312 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2313 static void setops(const char *args, const char *opname, int flg_no,
2314 const char *short_opname, int opt)
2316 const char *a = args + flg_no;
2317 int l = strlen(opname) - 1; // opname have + ' '
2319 // maybe strncmp? we had tons of erroneous strncasecmp's...
2320 if (strncasecmp(a, opname, l) == 0
2321 || strncasecmp(a, short_opname, 2) == 0
2331 #endif /* FEATURE_VI_COLON */
2333 // buf must be no longer than MAX_INPUT_LEN!
2334 static void colon(char *buf)
2336 #if !ENABLE_FEATURE_VI_COLON
2337 // Simple ":cmd" handler with minimal set of commands
2346 if (strncmp(p, "quit", cnt) == 0
2347 || strncmp(p, "q!", cnt) == 0
2349 if (modified_count && p[1] != '!') {
2350 status_line_bold("No write since last change (:%s! overrides)", p);
2356 if (strncmp(p, "write", cnt) == 0
2357 || strncmp(p, "wq", cnt) == 0
2358 || strncmp(p, "wn", cnt) == 0
2359 || (p[0] == 'x' && !p[1])
2361 if (modified_count != 0 || p[0] != 'x') {
2362 cnt = file_write(current_filename, text, end - 1);
2366 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2369 last_modified_count = -1;
2370 status_line("'%s' %dL, %dC",
2372 count_lines(text, end - 1), cnt
2375 || p[1] == 'q' || p[1] == 'n'
2376 || p[1] == 'Q' || p[1] == 'N'
2383 if (strncmp(p, "file", cnt) == 0) {
2384 last_status_cksum = 0; // force status update
2387 if (sscanf(p, "%d", &cnt) > 0) {
2388 dot = find_line(cnt);
2395 char c, *buf1, *q, *r;
2396 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2399 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2403 // :3154 // if (-e line 3154) goto it else stay put
2404 // :4,33w! foo // write a portion of buffer to file "foo"
2405 // :w // write all of buffer to current file
2407 // :q! // quit- dont care about modified file
2408 // :'a,'z!sort -u // filter block through sort
2409 // :'f // goto mark "f"
2410 // :'fl // list literal the mark "f" line
2411 // :.r bar // read file "bar" into buffer before dot
2412 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2413 // :/xyz/ // goto the "xyz" line
2414 // :s/find/replace/ // substitute pattern "find" with "replace"
2415 // :!<cmd> // run <cmd> then return
2421 buf++; // move past the ':'
2425 q = text; // assume 1,$ for the range
2427 li = count_lines(text, end - 1);
2428 fn = current_filename;
2430 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2431 buf = get_address(buf, &b, &e);
2433 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2434 // remember orig command line
2438 // get the COMMAND into cmd[]
2440 while (*buf != '\0') {
2446 // get any ARGuments
2447 while (isblank(*buf))
2451 buf1 = last_char_is(cmd, '!');
2454 *buf1 = '\0'; // get rid of !
2457 // if there is only one addr, then the addr
2458 // is the line number of the single line the
2459 // user wants. So, reset the end
2460 // pointer to point at end of the "b" line
2461 q = find_line(b); // what line is #b
2466 // we were given two addrs. change the
2467 // end pointer to the addr given by user.
2468 r = find_line(e); // what line is #e
2472 // ------------ now look for the command ------------
2474 if (i == 0) { // :123CR goto line #123
2476 dot = find_line(b); // what line is #b
2480 # if ENABLE_FEATURE_ALLOW_EXEC
2481 else if (cmd[0] == '!') { // run a cmd
2483 // :!ls run the <cmd>
2484 go_bottom_and_clear_to_eol();
2486 retcode = system(orig_buf + 1); // run the cmd
2488 printf("\nshell returned %i\n\n", retcode);
2490 Hit_Return(); // let user see results
2493 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
2494 if (b < 0) { // no addr given- use defaults
2495 b = e = count_lines(text, dot);
2497 status_line("%d", b);
2498 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
2499 if (b < 0) { // no addr given- use defaults
2500 q = begin_line(dot); // assume .,. for the range
2503 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
2505 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
2508 // don't edit, if the current file has been modified
2509 if (modified_count && !useforce) {
2510 status_line_bold("No write since last change (:%s! overrides)", cmd);
2514 // the user supplied a file name
2516 } else if (current_filename && current_filename[0]) {
2517 // no user supplied name- use the current filename
2518 // fn = current_filename; was set by default
2520 // no user file name, no current name- punt
2521 status_line_bold("No current filename");
2525 size = init_text_buffer(fn);
2527 # if ENABLE_FEATURE_VI_YANKMARK
2528 if (Ureg >= 0 && Ureg < 28) {
2529 free(reg[Ureg]); // free orig line reg- for 'U'
2532 if (YDreg >= 0 && YDreg < 28) {
2533 free(reg[YDreg]); // free default yank/delete register
2537 // how many lines in text[]?
2538 li = count_lines(text, end - 1);
2539 status_line("'%s'%s"
2540 IF_FEATURE_VI_READONLY("%s")
2543 (size < 0 ? " [New file]" : ""),
2544 IF_FEATURE_VI_READONLY(
2545 ((readonly_mode) ? " [Readonly]" : ""),
2547 li, (int)(end - text)
2549 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
2550 if (b != -1 || e != -1) {
2551 status_line_bold("No address allowed on this command");
2555 // user wants a new filename
2556 free(current_filename);
2557 current_filename = xstrdup(args);
2559 // user wants file status info
2560 last_status_cksum = 0; // force status update
2562 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
2563 // print out values of all features
2564 go_bottom_and_clear_to_eol();
2569 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
2570 if (b < 0) { // no addr given- use defaults
2571 q = begin_line(dot); // assume .,. for the range
2574 go_bottom_and_clear_to_eol();
2576 for (; q <= r; q++) {
2580 c_is_no_print = (c & 0x80) && !Isprint(c);
2581 if (c_is_no_print) {
2587 } else if (c < ' ' || c == 127) {
2599 } else if (strncmp(cmd, "quit", i) == 0 // quit
2600 || strncmp(cmd, "next", i) == 0 // edit next file
2601 || strncmp(cmd, "prev", i) == 0 // edit previous file
2606 // force end of argv list
2612 // don't exit if the file been modified
2613 if (modified_count) {
2614 status_line_bold("No write since last change (:%s! overrides)", cmd);
2617 // are there other file to edit
2618 n = save_argc - optind - 1;
2619 if (*cmd == 'q' && n > 0) {
2620 status_line_bold("%d more file(s) to edit", n);
2623 if (*cmd == 'n' && n <= 0) {
2624 status_line_bold("No more files to edit");
2628 // are there previous files to edit
2630 status_line_bold("No previous files to edit");
2636 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
2641 status_line_bold("No filename given");
2644 if (b < 0) { // no addr given- use defaults
2645 q = begin_line(dot); // assume "dot"
2647 // read after current line- unless user said ":0r foo"
2650 // read after last line
2654 { // dance around potentially-reallocated text[]
2655 uintptr_t ofs = q - text;
2656 size = file_insert(fn, q, 0);
2660 goto ret; // nothing was inserted
2661 // how many lines in text[]?
2662 li = count_lines(q, q + size - 1);
2664 IF_FEATURE_VI_READONLY("%s")
2667 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2671 // if the insert is before "dot" then we need to update
2675 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
2676 if (modified_count && !useforce) {
2677 status_line_bold("No write since last change (:%s! overrides)", cmd);
2679 // reset the filenames to edit
2680 optind = -1; // start from 0th file
2683 # if ENABLE_FEATURE_VI_SET
2684 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
2685 # if ENABLE_FEATURE_VI_SETOPTS
2688 i = 0; // offset into args
2689 // only blank is regarded as args delimiter. What about tab '\t'?
2690 if (!args[0] || strcasecmp(args, "all") == 0) {
2691 // print out values of all options
2692 # if ENABLE_FEATURE_VI_SETOPTS
2699 autoindent ? "" : "no",
2700 err_method ? "" : "no",
2701 ignorecase ? "" : "no",
2702 showmatch ? "" : "no",
2708 # if ENABLE_FEATURE_VI_SETOPTS
2711 if (strncmp(argp, "no", 2) == 0)
2712 i = 2; // ":set noautoindent"
2713 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2714 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
2715 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2716 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2717 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2719 sscanf(argp + i+8, "%u", &t);
2720 if (t > 0 && t <= MAX_TABSTOP)
2723 argp = skip_non_whitespace(argp);
2724 argp = skip_whitespace(argp);
2726 # endif /* FEATURE_VI_SETOPTS */
2727 # endif /* FEATURE_VI_SET */
2729 # if ENABLE_FEATURE_VI_SEARCH
2730 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
2731 char *F, *R, *flags;
2732 size_t len_F, len_R;
2733 int gflag; // global replace flag
2734 # if ENABLE_FEATURE_VI_UNDO
2735 int dont_chain_first_item = ALLOW_UNDO;
2738 // F points to the "find" pattern
2739 // R points to the "replace" pattern
2740 // replace the cmd line delimiters "/" with NULs
2741 c = orig_buf[1]; // what is the delimiter
2742 F = orig_buf + 2; // start of "find"
2743 R = strchr(F, c); // middle delimiter
2747 *R++ = '\0'; // terminate "find"
2748 flags = strchr(R, c);
2752 *flags++ = '\0'; // terminate "replace"
2756 if (b < 0) { // maybe :s/foo/bar/
2757 q = begin_line(dot); // start with cur line
2758 b = count_lines(text, q); // cur line number
2761 e = b; // maybe :.s/foo/bar/
2763 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2764 char *ls = q; // orig line start
2767 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
2770 // we found the "find" pattern - delete it
2771 // For undo support, the first item should not be chained
2772 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2773 # if ENABLE_FEATURE_VI_UNDO
2774 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2776 // insert the "replace" patern
2777 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2780 /*q += bias; - recalculated anyway */
2781 // check for "global" :s/foo/bar/g
2783 if ((found + len_R) < end_line(ls)) {
2785 goto vc4; // don't let q move past cur line
2791 # endif /* FEATURE_VI_SEARCH */
2792 } else if (strncmp(cmd, "version", i) == 0) { // show software version
2793 status_line(BB_VER);
2794 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2795 || strncmp(cmd, "wq", i) == 0
2796 || strncmp(cmd, "wn", i) == 0
2797 || (cmd[0] == 'x' && !cmd[1])
2800 //int forced = FALSE;
2802 // is there a file name to write to?
2806 # if ENABLE_FEATURE_VI_READONLY
2807 if (readonly_mode && !useforce) {
2808 status_line_bold("'%s' is read only", fn);
2813 // if "fn" is not write-able, chmod u+w
2814 // sprintf(syscmd, "chmod u+w %s", fn);
2818 if (modified_count != 0 || cmd[0] != 'x') {
2820 l = file_write(fn, q, r);
2825 //if (useforce && forced) {
2827 // sprintf(syscmd, "chmod u-w %s", fn);
2833 status_line_bold_errno(fn);
2835 // how many lines written
2836 li = count_lines(q, q + l - 1);
2837 status_line("'%s' %dL, %dC", fn, li, l);
2839 if (q == text && q + l == end) {
2841 last_modified_count = -1;
2844 || cmd[1] == 'q' || cmd[1] == 'n'
2845 || cmd[1] == 'Q' || cmd[1] == 'N'
2851 # if ENABLE_FEATURE_VI_YANKMARK
2852 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
2853 if (b < 0) { // no addr given- use defaults
2854 q = begin_line(dot); // assume .,. for the range
2857 text_yank(q, r, YDreg);
2858 li = count_lines(q, r);
2859 status_line("Yank %d lines (%d chars) into [%c]",
2860 li, strlen(reg[YDreg]), what_reg());
2864 not_implemented(cmd);
2867 dot = bound_dot(dot); // make sure "dot" is valid
2869 # if ENABLE_FEATURE_VI_SEARCH
2871 status_line(":s expression missing delimiters");
2873 #endif /* FEATURE_VI_COLON */
2876 //----- Helper Utility Routines --------------------------------
2878 //----------------------------------------------------------------
2879 //----- Char Routines --------------------------------------------
2880 /* Chars that are part of a word-
2881 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2882 * Chars that are Not part of a word (stoppers)
2883 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2884 * Chars that are WhiteSpace
2885 * TAB NEWLINE VT FF RETURN SPACE
2886 * DO NOT COUNT NEWLINE AS WHITESPACE
2889 static char *new_screen(int ro, int co)
2894 screensize = ro * co + 8;
2895 screen = xmalloc(screensize);
2896 // initialize the new screen. assume this will be a empty file.
2898 // non-existent text[] lines start with a tilde (~).
2899 for (li = 1; li < ro - 1; li++) {
2900 screen[(li * co) + 0] = '~';
2905 static int st_test(char *p, int type, int dir, char *tested)
2915 if (type == S_BEFORE_WS) {
2917 test = (!isspace(c) || c == '\n');
2919 if (type == S_TO_WS) {
2921 test = (!isspace(c) || c == '\n');
2923 if (type == S_OVER_WS) {
2927 if (type == S_END_PUNCT) {
2931 if (type == S_END_ALNUM) {
2933 test = (isalnum(c) || c == '_');
2939 static char *skip_thing(char *p, int linecnt, int dir, int type)
2943 while (st_test(p, type, dir, &c)) {
2944 // make sure we limit search to correct number of lines
2945 if (c == '\n' && --linecnt < 1)
2947 if (dir >= 0 && p >= end - 1)
2949 if (dir < 0 && p <= text)
2951 p += dir; // move to next char
2956 #if ENABLE_FEATURE_VI_USE_SIGNALS
2957 static void winch_handler(int sig UNUSED_PARAM)
2959 int save_errno = errno;
2960 // FIXME: do it in main loop!!!
2961 signal(SIGWINCH, winch_handler);
2962 query_screen_dimensions();
2963 new_screen(rows, columns); // get memory for virtual screen
2964 redraw(TRUE); // re-draw the screen
2967 static void tstp_handler(int sig UNUSED_PARAM)
2969 int save_errno = errno;
2971 // ioctl inside cookmode() was seen to generate SIGTTOU,
2972 // stopping us too early. Prevent that:
2973 signal(SIGTTOU, SIG_IGN);
2975 go_bottom_and_clear_to_eol();
2976 cookmode(); // terminal to "cooked"
2979 //signal(SIGTSTP, SIG_DFL);
2981 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2982 //signal(SIGTSTP, tstp_handler);
2984 // we have been "continued" with SIGCONT, restore screen and termios
2985 rawmode(); // terminal to "raw"
2986 last_status_cksum = 0; // force status update
2987 redraw(TRUE); // re-draw the screen
2991 static void int_handler(int sig)
2993 signal(SIGINT, int_handler);
2994 siglongjmp(restart, sig);
2996 #endif /* FEATURE_VI_USE_SIGNALS */
2998 static void do_cmd(int c);
3000 static int find_range(char **start, char **stop, char c)
3002 char *save_dot, *p, *q, *t;
3003 int cnt, multiline = 0;
3008 if (strchr("cdy><", c)) {
3009 // these cmds operate on whole lines
3010 p = q = begin_line(p);
3011 for (cnt = 1; cnt < cmdcnt; cnt++) {
3015 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3016 // These cmds operate on char positions
3017 do_cmd(c); // execute movement cmd
3019 } else if (strchr("wW", c)) {
3020 do_cmd(c); // execute movement cmd
3021 // if we are at the next word's first char
3022 // step back one char
3023 // but check the possibilities when it is true
3024 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3025 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3026 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3027 dot--; // move back off of next word
3028 if (dot > text && *dot == '\n')
3029 dot--; // stay off NL
3031 } else if (strchr("H-k{", c)) {
3032 // these operate on multi-lines backwards
3033 q = end_line(dot); // find NL
3034 do_cmd(c); // execute movement cmd
3037 } else if (strchr("L+j}\r\n", c)) {
3038 // these operate on multi-lines forwards
3039 p = begin_line(dot);
3040 do_cmd(c); // execute movement cmd
3041 dot_end(); // find NL
3044 // nothing -- this causes any other values of c to
3045 // represent the one-character range under the
3046 // cursor. this is correct for ' ' and 'l', but
3047 // perhaps no others.
3056 // backward char movements don't include start position
3057 if (q > p && strchr("^0bBh\b\177", c)) q--;
3060 for (t = p; t <= q; t++) {
3073 //---------------------------------------------------------------------
3074 //----- the Ascii Chart -----------------------------------------------
3075 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3076 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3077 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3078 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3079 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3080 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3081 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3082 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3083 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3084 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3085 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3086 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3087 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3088 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3089 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3090 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3091 //---------------------------------------------------------------------
3093 //----- Execute a Vi Command -----------------------------------
3094 static void do_cmd(int c)
3096 char *p, *q, *save_dot;
3102 // c1 = c; // quiet the compiler
3103 // cnt = yf = 0; // quiet the compiler
3104 // p = q = save_dot = buf; // quiet the compiler
3105 memset(buf, '\0', sizeof(buf));
3109 // if this is a cursor key, skip these checks
3117 case KEYCODE_PAGEUP:
3118 case KEYCODE_PAGEDOWN:
3119 case KEYCODE_DELETE:
3123 if (cmd_mode == 2) {
3124 // flip-flop Insert/Replace mode
3125 if (c == KEYCODE_INSERT)
3127 // we are 'R'eplacing the current *dot with new char
3129 // don't Replace past E-o-l
3130 cmd_mode = 1; // convert to insert
3131 undo_queue_commit();
3133 if (1 <= c || Isprint(c)) {
3135 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3136 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3141 if (cmd_mode == 1) {
3142 // hitting "Insert" twice means "R" replace mode
3143 if (c == KEYCODE_INSERT) goto dc5;
3144 // insert the char c at "dot"
3145 if (1 <= c || Isprint(c)) {
3146 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3161 #if ENABLE_FEATURE_VI_CRASHME
3162 case 0x14: // dc4 ctrl-T
3163 crashme = (crashme == 0) ? 1 : 0;
3193 default: // unrecognized command
3196 not_implemented(buf);
3197 end_cmd_q(); // stop adding to q
3198 case 0x00: // nul- ignore
3200 case 2: // ctrl-B scroll up full screen
3201 case KEYCODE_PAGEUP: // Cursor Key Page Up
3202 dot_scroll(rows - 2, -1);
3204 case 4: // ctrl-D scroll down half screen
3205 dot_scroll((rows - 2) / 2, 1);
3207 case 5: // ctrl-E scroll down one line
3210 case 6: // ctrl-F scroll down full screen
3211 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3212 dot_scroll(rows - 2, 1);
3214 case 7: // ctrl-G show current status
3215 last_status_cksum = 0; // force status update
3217 case 'h': // h- move left
3218 case KEYCODE_LEFT: // cursor key Left
3219 case 8: // ctrl-H- move left (This may be ERASE char)
3220 case 0x7f: // DEL- move left (This may be ERASE char)
3223 } while (--cmdcnt > 0);
3225 case 10: // Newline ^J
3226 case 'j': // j- goto next line, same col
3227 case KEYCODE_DOWN: // cursor key Down
3229 dot_next(); // go to next B-o-l
3230 // try stay in same col
3231 dot = move_to_col(dot, ccol + offset);
3232 } while (--cmdcnt > 0);
3234 case 12: // ctrl-L force redraw whole screen
3235 case 18: // ctrl-R force redraw
3236 redraw(TRUE); // this will redraw the entire display
3238 case 13: // Carriage Return ^M
3239 case '+': // +- goto next line
3243 } while (--cmdcnt > 0);
3245 case 21: // ctrl-U scroll up half screen
3246 dot_scroll((rows - 2) / 2, -1);
3248 case 25: // ctrl-Y scroll up one line
3254 cmd_mode = 0; // stop insrting
3255 undo_queue_commit();
3257 last_status_cksum = 0; // force status update
3259 case ' ': // move right
3260 case 'l': // move right
3261 case KEYCODE_RIGHT: // Cursor Key Right
3264 } while (--cmdcnt > 0);
3266 #if ENABLE_FEATURE_VI_YANKMARK
3267 case '"': // "- name a register to use for Delete/Yank
3268 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3269 if ((unsigned)c1 <= 25) { // a-z?
3275 case '\'': // '- goto a specific mark
3276 c1 = (get_one_char() | 0x20);
3277 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3281 if (text <= q && q < end) {
3283 dot_begin(); // go to B-o-l
3286 } else if (c1 == '\'') { // goto previous context
3287 dot = swap_context(dot); // swap current and previous context
3288 dot_begin(); // go to B-o-l
3294 case 'm': // m- Mark a line
3295 // this is really stupid. If there are any inserts or deletes
3296 // between text[0] and dot then this mark will not point to the
3297 // correct location! It could be off by many lines!
3298 // Well..., at least its quick and dirty.
3299 c1 = (get_one_char() | 0x20) - 'a';
3300 if ((unsigned)c1 <= 25) { // a-z?
3301 // remember the line
3307 case 'P': // P- Put register before
3308 case 'p': // p- put register after
3311 status_line_bold("Nothing in register %c", what_reg());
3314 // are we putting whole lines or strings
3315 if (strchr(p, '\n') != NULL) {
3317 dot_begin(); // putting lines- Put above
3320 // are we putting after very last line?
3321 if (end_line(dot) == (end - 1)) {
3322 dot = end; // force dot to end of text[]
3324 dot_next(); // next line, then put before
3329 dot_right(); // move to right, can move to NL
3331 string_insert(dot, p, ALLOW_UNDO); // insert the string
3332 end_cmd_q(); // stop adding to q
3334 case 'U': // U- Undo; replace current line with original version
3335 if (reg[Ureg] != NULL) {
3336 p = begin_line(dot);
3338 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3339 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3344 #endif /* FEATURE_VI_YANKMARK */
3345 #if ENABLE_FEATURE_VI_UNDO
3346 case 'u': // u- undo last operation
3350 case '$': // $- goto end of line
3351 case KEYCODE_END: // Cursor Key End
3353 dot = end_line(dot);
3359 case '%': // %- find matching char of pair () [] {}
3360 for (q = dot; q < end && *q != '\n'; q++) {
3361 if (strchr("()[]{}", *q) != NULL) {
3362 // we found half of a pair
3363 p = find_pair(q, *q);
3375 case 'f': // f- forward to a user specified char
3376 last_forward_char = get_one_char(); // get the search char
3378 // dont separate these two commands. 'f' depends on ';'
3380 //**** fall through to ... ';'
3381 case ';': // ;- look at rest of line for last forward char
3383 if (last_forward_char == 0)
3386 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3389 if (*q == last_forward_char)
3391 } while (--cmdcnt > 0);
3393 case ',': // repeat latest 'f' in opposite direction
3394 if (last_forward_char == 0)
3398 while (q >= text && *q != '\n' && *q != last_forward_char) {
3401 if (q >= text && *q == last_forward_char)
3403 } while (--cmdcnt > 0);
3406 case '-': // -- goto prev line
3410 } while (--cmdcnt > 0);
3412 #if ENABLE_FEATURE_VI_DOT_CMD
3413 case '.': // .- repeat the last modifying command
3414 // Stuff the last_modifying_cmd back into stdin
3415 // and let it be re-executed.
3417 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
3421 #if ENABLE_FEATURE_VI_SEARCH
3422 case '?': // /- search for a pattern
3423 case '/': // /- search for a pattern
3426 q = get_input_line(buf); // get input line- use "status line"
3427 if (q[0] && !q[1]) {
3428 if (last_search_pattern[0])
3429 last_search_pattern[0] = c;
3430 goto dc3; // if no pat re-use old pat
3432 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3433 // there is a new pat
3434 free(last_search_pattern);
3435 last_search_pattern = xstrdup(q);
3436 goto dc3; // now find the pattern
3438 // user changed mind and erased the "/"- do nothing
3440 case 'N': // N- backward search for last pattern
3441 dir = BACK; // assume BACKWARD search
3443 if (last_search_pattern[0] == '?') {
3447 goto dc4; // now search for pattern
3449 case 'n': // n- repeat search for last pattern
3450 // search rest of text[] starting at next char
3451 // if search fails return orignal "p" not the "p+1" address
3455 dir = FORWARD; // assume FORWARD search
3457 if (last_search_pattern[0] == '?') {
3462 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3464 dot = q; // good search, update "dot"
3468 // no pattern found between "dot" and "end"- continue at top
3473 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3474 if (q != NULL) { // found something
3475 dot = q; // found new pattern- goto it
3476 msg = "search hit BOTTOM, continuing at TOP";
3478 msg = "search hit TOP, continuing at BOTTOM";
3481 msg = "Pattern not found";
3485 status_line_bold("%s", msg);
3486 } while (--cmdcnt > 0);
3488 case '{': // {- move backward paragraph
3489 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3490 if (q != NULL) { // found blank line
3491 dot = next_line(q); // move to next blank line
3494 case '}': // }- move forward paragraph
3495 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3496 if (q != NULL) { // found blank line
3497 dot = next_line(q); // move to next blank line
3500 #endif /* FEATURE_VI_SEARCH */
3501 case '0': // 0- goto beginning of line
3511 if (c == '0' && cmdcnt < 1) {
3512 dot_begin(); // this was a standalone zero
3514 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3517 case ':': // :- the colon mode commands
3518 p = get_input_line(":"); // get input line- use "status line"
3519 colon(p); // execute the command
3521 case '<': // <- Left shift something
3522 case '>': // >- Right shift something
3523 cnt = count_lines(text, dot); // remember what line we are on
3524 c1 = get_one_char(); // get the type of thing to delete
3525 find_range(&p, &q, c1);
3526 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3529 i = count_lines(p, q); // # of lines we are shifting
3530 for ( ; i > 0; i--, p = next_line(p)) {
3532 // shift left- remove tab or 8 spaces
3534 // shrink buffer 1 char
3535 text_hole_delete(p, p, NO_UNDO);
3536 } else if (*p == ' ') {
3537 // we should be calculating columns, not just SPACE
3538 for (j = 0; *p == ' ' && j < tabstop; j++) {
3539 text_hole_delete(p, p, NO_UNDO);
3542 } else if (c == '>') {
3543 // shift right -- add tab or 8 spaces
3544 char_insert(p, '\t', ALLOW_UNDO);
3547 dot = find_line(cnt); // what line were we on
3549 end_cmd_q(); // stop adding to q
3551 case 'A': // A- append at e-o-l
3552 dot_end(); // go to e-o-l
3553 //**** fall through to ... 'a'
3554 case 'a': // a- append after current char
3559 case 'B': // B- back a blank-delimited Word
3560 case 'E': // E- end of a blank-delimited word
3561 case 'W': // W- forward a blank-delimited word
3566 if (c == 'W' || isspace(dot[dir])) {
3567 dot = skip_thing(dot, 1, dir, S_TO_WS);
3568 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3571 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3572 } while (--cmdcnt > 0);
3574 case 'C': // C- Change to e-o-l
3575 case 'D': // D- delete to e-o-l
3577 dot = dollar_line(dot); // move to before NL
3578 // copy text into a register and delete
3579 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3581 goto dc_i; // start inserting
3582 #if ENABLE_FEATURE_VI_DOT_CMD
3584 end_cmd_q(); // stop adding to q
3587 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3588 c1 = get_one_char();
3591 // c1 < 0 if the key was special. Try "g<up-arrow>"
3592 // TODO: if Unicode?
3593 buf[1] = (c1 >= 0 ? c1 : '*');
3595 not_implemented(buf);
3601 case 'G': // G- goto to a line number (default= E-O-F)
3602 dot = end - 1; // assume E-O-F
3604 dot = find_line(cmdcnt); // what line is #cmdcnt
3608 case 'H': // H- goto top line on screen
3610 if (cmdcnt > (rows - 1)) {
3611 cmdcnt = (rows - 1);
3618 case 'I': // I- insert before first non-blank
3621 //**** fall through to ... 'i'
3622 case 'i': // i- insert before current char
3623 case KEYCODE_INSERT: // Cursor Key Insert
3625 cmd_mode = 1; // start inserting
3626 undo_queue_commit(); // commit queue when cmd_mode changes
3628 case 'J': // J- join current and next lines together
3630 dot_end(); // move to NL
3631 if (dot < end - 1) { // make sure not last char in text[]
3632 #if ENABLE_FEATURE_VI_UNDO
3633 undo_push(dot, 1, UNDO_DEL);
3634 *dot++ = ' '; // replace NL with space
3635 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3640 while (isblank(*dot)) { // delete leading WS
3641 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3644 } while (--cmdcnt > 0);
3645 end_cmd_q(); // stop adding to q
3647 case 'L': // L- goto bottom line on screen
3649 if (cmdcnt > (rows - 1)) {
3650 cmdcnt = (rows - 1);
3658 case 'M': // M- goto middle line on screen
3660 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3661 dot = next_line(dot);
3663 case 'O': // O- open a empty line above
3665 p = begin_line(dot);
3666 if (p[-1] == '\n') {
3668 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3670 dot = char_insert(dot, '\n', ALLOW_UNDO);
3673 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3678 case 'R': // R- continuous Replace char
3681 undo_queue_commit();
3683 case KEYCODE_DELETE:
3685 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3687 case 'X': // X- delete char before dot
3688 case 'x': // x- delete the current char
3689 case 's': // s- substitute the current char
3694 if (dot[dir] != '\n') {
3696 dot--; // delete prev char
3697 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3699 } while (--cmdcnt > 0);
3700 end_cmd_q(); // stop adding to q
3702 goto dc_i; // start inserting
3704 case 'Z': // Z- if modified, {write}; exit
3705 // ZZ means to save file (if necessary), then exit
3706 c1 = get_one_char();
3711 if (modified_count) {
3712 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3713 status_line_bold("'%s' is read only", current_filename);
3716 cnt = file_write(current_filename, text, end - 1);
3719 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3720 } else if (cnt == (end - 1 - text + 1)) {
3727 case '^': // ^- move to first non-blank on line
3731 case 'b': // b- back a word
3732 case 'e': // e- end of word
3737 if ((dot + dir) < text || (dot + dir) > end - 1)
3740 if (isspace(*dot)) {
3741 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3743 if (isalnum(*dot) || *dot == '_') {
3744 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3745 } else if (ispunct(*dot)) {
3746 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3748 } while (--cmdcnt > 0);
3750 case 'c': // c- change something
3751 case 'd': // d- delete something
3752 #if ENABLE_FEATURE_VI_YANKMARK
3753 case 'y': // y- yank something
3754 case 'Y': // Y- Yank a line
3757 int yf, ml, whole = 0;
3758 yf = YANKDEL; // assume either "c" or "d"
3759 #if ENABLE_FEATURE_VI_YANKMARK
3760 if (c == 'y' || c == 'Y')
3765 c1 = get_one_char(); // get the type of thing to delete
3766 // determine range, and whether it spans lines
3767 ml = find_range(&p, &q, c1);
3769 if (c1 == 27) { // ESC- user changed mind and wants out
3770 c = c1 = 27; // Escape- do nothing
3771 } else if (strchr("wW", c1)) {
3773 // don't include trailing WS as part of word
3774 while (isblank(*q)) {
3775 if (q <= text || q[-1] == '\n')
3780 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3781 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3782 // partial line copy text into a register and delete
3783 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3784 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3785 // whole line copy text into a register and delete
3786 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3789 // could not recognize object
3790 c = c1 = 27; // error-
3796 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3797 // on the last line of file don't move to prev line
3798 if (whole && dot != (end-1)) {
3801 } else if (c == 'd') {
3807 // if CHANGING, not deleting, start inserting after the delete
3809 strcpy(buf, "Change");
3810 goto dc_i; // start inserting
3813 strcpy(buf, "Delete");
3815 #if ENABLE_FEATURE_VI_YANKMARK
3816 if (c == 'y' || c == 'Y') {
3817 strcpy(buf, "Yank");
3821 for (cnt = 0; p <= q; p++) {
3825 status_line("%s %u lines (%u chars) using [%c]",
3826 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3828 end_cmd_q(); // stop adding to q
3832 case 'k': // k- goto prev line, same col
3833 case KEYCODE_UP: // cursor key Up
3836 dot = move_to_col(dot, ccol + offset); // try stay in same col
3837 } while (--cmdcnt > 0);
3839 case 'r': // r- replace the current char with user input
3840 c1 = get_one_char(); // get the replacement char
3842 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3843 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3846 end_cmd_q(); // stop adding to q
3848 case 't': // t- move to char prior to next x
3849 last_forward_char = get_one_char();
3851 if (*dot == last_forward_char)
3853 last_forward_char = 0;
3855 case 'w': // w- forward a word
3857 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3858 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3859 } else if (ispunct(*dot)) { // we are on PUNCT
3860 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3863 dot++; // move over word
3864 if (isspace(*dot)) {
3865 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3867 } while (--cmdcnt > 0);
3870 c1 = get_one_char(); // get the replacement char
3873 cnt = (rows - 2) / 2; // put dot at center
3875 cnt = rows - 2; // put dot at bottom
3876 screenbegin = begin_line(dot); // start dot at top
3877 dot_scroll(cnt, -1);
3879 case '|': // |- move to column "cmdcnt"
3880 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3882 case '~': // ~- flip the case of letters a-z -> A-Z
3884 #if ENABLE_FEATURE_VI_UNDO
3885 if (islower(*dot)) {
3886 undo_push(dot, 1, UNDO_DEL);
3887 *dot = toupper(*dot);
3888 undo_push(dot, 1, UNDO_INS_CHAIN);
3889 } else if (isupper(*dot)) {
3890 undo_push(dot, 1, UNDO_DEL);
3891 *dot = tolower(*dot);
3892 undo_push(dot, 1, UNDO_INS_CHAIN);
3895 if (islower(*dot)) {
3896 *dot = toupper(*dot);
3898 } else if (isupper(*dot)) {
3899 *dot = tolower(*dot);
3904 } while (--cmdcnt > 0);
3905 end_cmd_q(); // stop adding to q
3907 //----- The Cursor and Function Keys -----------------------------
3908 case KEYCODE_HOME: // Cursor Key Home
3911 // The Fn keys could point to do_macro which could translate them
3913 case KEYCODE_FUN1: // Function Key F1
3914 case KEYCODE_FUN2: // Function Key F2
3915 case KEYCODE_FUN3: // Function Key F3
3916 case KEYCODE_FUN4: // Function Key F4
3917 case KEYCODE_FUN5: // Function Key F5
3918 case KEYCODE_FUN6: // Function Key F6
3919 case KEYCODE_FUN7: // Function Key F7
3920 case KEYCODE_FUN8: // Function Key F8
3921 case KEYCODE_FUN9: // Function Key F9
3922 case KEYCODE_FUN10: // Function Key F10
3923 case KEYCODE_FUN11: // Function Key F11
3924 case KEYCODE_FUN12: // Function Key F12
3930 // if text[] just became empty, add back an empty line
3932 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
3935 // it is OK for dot to exactly equal to end, otherwise check dot validity
3937 dot = bound_dot(dot); // make sure "dot" is valid
3939 #if ENABLE_FEATURE_VI_YANKMARK
3940 check_context(c); // update the current context
3944 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3945 cnt = dot - begin_line(dot);
3946 // Try to stay off of the Newline
3947 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3951 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3952 #if ENABLE_FEATURE_VI_CRASHME
3953 static int totalcmds = 0;
3954 static int Mp = 85; // Movement command Probability
3955 static int Np = 90; // Non-movement command Probability
3956 static int Dp = 96; // Delete command Probability
3957 static int Ip = 97; // Insert command Probability
3958 static int Yp = 98; // Yank command Probability
3959 static int Pp = 99; // Put command Probability
3960 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3961 static const char chars[20] = "\t012345 abcdABCD-=.$";
3962 static const char *const words[20] = {
3963 "this", "is", "a", "test",
3964 "broadcast", "the", "emergency", "of",
3965 "system", "quick", "brown", "fox",
3966 "jumped", "over", "lazy", "dogs",
3967 "back", "January", "Febuary", "March"
3969 static const char *const lines[20] = {
3970 "You should have received a copy of the GNU General Public License\n",
3971 "char c, cm, *cmd, *cmd1;\n",
3972 "generate a command by percentages\n",
3973 "Numbers may be typed as a prefix to some commands.\n",
3974 "Quit, discarding changes!\n",
3975 "Forced write, if permission originally not valid.\n",
3976 "In general, any ex or ed command (such as substitute or delete).\n",
3977 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3978 "Please get w/ me and I will go over it with you.\n",
3979 "The following is a list of scheduled, committed changes.\n",
3980 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3981 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3982 "Any question about transactions please contact Sterling Huxley.\n",
3983 "I will try to get back to you by Friday, December 31.\n",
3984 "This Change will be implemented on Friday.\n",
3985 "Let me know if you have problems accessing this;\n",
3986 "Sterling Huxley recently added you to the access list.\n",
3987 "Would you like to go to lunch?\n",
3988 "The last command will be automatically run.\n",
3989 "This is too much english for a computer geek.\n",
3991 static char *multilines[20] = {
3992 "You should have received a copy of the GNU General Public License\n",
3993 "char c, cm, *cmd, *cmd1;\n",
3994 "generate a command by percentages\n",
3995 "Numbers may be typed as a prefix to some commands.\n",
3996 "Quit, discarding changes!\n",
3997 "Forced write, if permission originally not valid.\n",
3998 "In general, any ex or ed command (such as substitute or delete).\n",
3999 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4000 "Please get w/ me and I will go over it with you.\n",
4001 "The following is a list of scheduled, committed changes.\n",
4002 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4003 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4004 "Any question about transactions please contact Sterling Huxley.\n",
4005 "I will try to get back to you by Friday, December 31.\n",
4006 "This Change will be implemented on Friday.\n",
4007 "Let me know if you have problems accessing this;\n",
4008 "Sterling Huxley recently added you to the access list.\n",
4009 "Would you like to go to lunch?\n",
4010 "The last command will be automatically run.\n",
4011 "This is too much english for a computer geek.\n",
4014 // create a random command to execute
4015 static void crash_dummy()
4017 static int sleeptime; // how long to pause between commands
4018 char c, cm, *cmd, *cmd1;
4019 int i, cnt, thing, rbi, startrbi, percent;
4021 // "dot" movement commands
4022 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4024 // is there already a command running?
4025 if (readbuffer[0] > 0)
4028 readbuffer[0] = 'X';
4030 sleeptime = 0; // how long to pause between commands
4031 memset(readbuffer, '\0', sizeof(readbuffer));
4032 // generate a command by percentages
4033 percent = (int) lrand48() % 100; // get a number from 0-99
4034 if (percent < Mp) { // Movement commands
4035 // available commands
4038 } else if (percent < Np) { // non-movement commands
4039 cmd = "mz<>\'\""; // available commands
4041 } else if (percent < Dp) { // Delete commands
4042 cmd = "dx"; // available commands
4044 } else if (percent < Ip) { // Inset commands
4045 cmd = "iIaAsrJ"; // available commands
4047 } else if (percent < Yp) { // Yank commands
4048 cmd = "yY"; // available commands
4050 } else if (percent < Pp) { // Put commands
4051 cmd = "pP"; // available commands
4054 // We do not know how to handle this command, try again
4058 // randomly pick one of the available cmds from "cmd[]"
4059 i = (int) lrand48() % strlen(cmd);
4061 if (strchr(":\024", cm))
4062 goto cd0; // dont allow colon or ctrl-T commands
4063 readbuffer[rbi++] = cm; // put cmd into input buffer
4065 // now we have the command-
4066 // there are 1, 2, and multi char commands
4067 // find out which and generate the rest of command as necessary
4068 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4069 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4070 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4071 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4073 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4075 readbuffer[rbi++] = c; // add movement to input buffer
4077 if (strchr("iIaAsc", cm)) { // multi-char commands
4079 // change some thing
4080 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4082 readbuffer[rbi++] = c; // add movement to input buffer
4084 thing = (int) lrand48() % 4; // what thing to insert
4085 cnt = (int) lrand48() % 10; // how many to insert
4086 for (i = 0; i < cnt; i++) {
4087 if (thing == 0) { // insert chars
4088 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4089 } else if (thing == 1) { // insert words
4090 strcat(readbuffer, words[(int) lrand48() % 20]);
4091 strcat(readbuffer, " ");
4092 sleeptime = 0; // how fast to type
4093 } else if (thing == 2) { // insert lines
4094 strcat(readbuffer, lines[(int) lrand48() % 20]);
4095 sleeptime = 0; // how fast to type
4096 } else { // insert multi-lines
4097 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4098 sleeptime = 0; // how fast to type
4101 strcat(readbuffer, ESC);
4103 readbuffer[0] = strlen(readbuffer + 1);
4107 mysleep(sleeptime); // sleep 1/100 sec
4110 // test to see if there are any errors
4111 static void crash_test()
4113 static time_t oldtim;
4120 strcat(msg, "end<text ");
4122 if (end > textend) {
4123 strcat(msg, "end>textend ");
4126 strcat(msg, "dot<text ");
4129 strcat(msg, "dot>end ");
4131 if (screenbegin < text) {
4132 strcat(msg, "screenbegin<text ");
4134 if (screenbegin > end - 1) {
4135 strcat(msg, "screenbegin>end-1 ");
4139 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4140 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4142 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4143 if (d[0] == '\n' || d[0] == '\r')
4148 if (tim >= (oldtim + 3)) {
4149 sprintf(status_buffer,
4150 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4151 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4157 static void edit_file(char *fn)
4159 #if ENABLE_FEATURE_VI_YANKMARK
4160 #define cur_line edit_file__cur_line
4163 #if ENABLE_FEATURE_VI_USE_SIGNALS
4167 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4171 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4172 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4173 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4175 write1(ESC"[999;999H" ESC"[6n");
4177 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4178 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4179 uint32_t rc = (k >> 32);
4180 columns = (rc & 0x7fff);
4181 if (columns > MAX_SCR_COLS)
4182 columns = MAX_SCR_COLS;
4183 rows = ((rc >> 16) & 0x7fff);
4184 if (rows > MAX_SCR_ROWS)
4185 rows = MAX_SCR_ROWS;
4189 new_screen(rows, columns); // get memory for virtual screen
4190 init_text_buffer(fn);
4192 #if ENABLE_FEATURE_VI_YANKMARK
4193 YDreg = 26; // default Yank/Delete reg
4194 // Ureg = 27; - const // hold orig line for "U" cmd
4195 mark[26] = mark[27] = text; // init "previous context"
4198 last_forward_char = last_input_char = '\0';
4202 #if ENABLE_FEATURE_VI_USE_SIGNALS
4203 signal(SIGWINCH, winch_handler);
4204 signal(SIGTSTP, tstp_handler);
4205 sig = sigsetjmp(restart, 1);
4207 screenbegin = dot = text;
4209 // int_handler() can jump to "restart",
4210 // must install handler *after* initializing "restart"
4211 signal(SIGINT, int_handler);
4214 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4217 offset = 0; // no horizontal offset
4219 #if ENABLE_FEATURE_VI_DOT_CMD
4226 #if ENABLE_FEATURE_VI_COLON
4231 while ((p = initial_cmds[n]) != NULL) {
4234 p = strchr(q, '\n');
4241 free(initial_cmds[n]);
4242 initial_cmds[n] = NULL;
4247 redraw(FALSE); // dont force every col re-draw
4248 //------This is the main Vi cmd handling loop -----------------------
4249 while (editing > 0) {
4250 #if ENABLE_FEATURE_VI_CRASHME
4252 if ((end - text) > 1) {
4253 crash_dummy(); // generate a random command
4256 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
4262 last_input_char = c = get_one_char(); // get a cmd from user
4263 #if ENABLE_FEATURE_VI_YANKMARK
4264 // save a copy of the current line- for the 'U" command
4265 if (begin_line(dot) != cur_line) {
4266 cur_line = begin_line(dot);
4267 text_yank(begin_line(dot), end_line(dot), Ureg);
4270 #if ENABLE_FEATURE_VI_DOT_CMD
4271 // These are commands that change text[].
4272 // Remember the input for the "." command
4274 && ioq_start == NULL
4275 && cmd_mode == 0 // command mode
4276 && c > '\0' // exclude NUL and non-ASCII chars
4277 && c < 0x7f // (Unicode and such)
4278 && strchr(modifying_cmds, c)
4283 do_cmd(c); // execute the user command
4285 // poll to see if there is input already waiting. if we are
4286 // not able to display output fast enough to keep up, skip
4287 // the display update until we catch up with input.
4288 if (!readbuffer[0] && mysleep(0) == 0) {
4289 // no input pending - so update output
4293 #if ENABLE_FEATURE_VI_CRASHME
4295 crash_test(); // test editor variables
4298 //-------------------------------------------------------------------
4300 go_bottom_and_clear_to_eol();
4305 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4306 int vi_main(int argc, char **argv)
4312 #if ENABLE_FEATURE_VI_UNDO
4313 /* undo_stack_tail = NULL; - already is */
4314 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4315 undo_queue_state = UNDO_EMPTY;
4316 /* undo_q = 0; - already is */
4320 #if ENABLE_FEATURE_VI_CRASHME
4321 srand((long) getpid());
4323 #ifdef NO_SUCH_APPLET_YET
4324 // if we aren't "vi", we are "view"
4325 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4326 SET_READONLY_MODE(readonly_mode);
4330 // autoindent is not default in vim 7.3
4331 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4332 // 1- process $HOME/.exrc file (not inplemented yet)
4333 // 2- process EXINIT variable from environment
4334 // 3- process command line args
4335 #if ENABLE_FEATURE_VI_COLON
4337 char *p = getenv("EXINIT");
4339 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4342 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4344 #if ENABLE_FEATURE_VI_CRASHME
4349 #if ENABLE_FEATURE_VI_READONLY
4350 case 'R': // Read-only flag
4351 SET_READONLY_MODE(readonly_mode);
4354 #if ENABLE_FEATURE_VI_COLON
4355 case 'c': // cmd line vi command
4357 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4369 // The argv array can be used by the ":next" and ":rewind" commands
4373 //----- This is the main file handling loop --------------
4376 // "Save cursor, use alternate screen buffer, clear screen"
4377 write1(ESC"[?1049h");
4379 edit_file(argv[optind]); // param might be NULL
4380 if (++optind >= argc)
4383 // "Use normal screen buffer, restore cursor"
4384 write1(ESC"[?1049l");
4385 //-----------------------------------------------------------