1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 // $HOME/.exrc and ./.exrc
12 // add magic to search /foo.*bar
15 // if mark[] values were line numbers rather than pointers
16 // it would be easier to change the mark when add/delete lines
17 // More intelligence in refresh()
18 // ":r !cmd" and "!cmd" to filter text through an external command
19 // An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (23 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 // Should be after libbb.h: on some systems regex.h needs sys/types.h:
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 // the CRASHME code is unmaintained, and doesn't currently build
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 // VT102 ESC sequences.
222 // See "Xterm Control Sequences"
223 // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
225 // Inverse/Normal text
226 #define ESC_BOLD_TEXT ESC"[7m"
227 #define ESC_NORM_TEXT ESC"[m"
229 #define ESC_BELL "\007"
230 // Clear-to-end-of-line
231 #define ESC_CLEAR2EOL ESC"[K"
232 // Clear-to-end-of-screen.
233 // (We use default param here.
234 // Full sequence is "ESC [ <num> J",
235 // <num> is 0/1/2 = "erase below/above/all".)
236 #define ESC_CLEAR2EOS ESC"[J"
237 // Cursor to given coordinate (1,1: top left)
238 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
239 #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
241 //// Cursor up and down
242 //#define ESC_CURSOR_UP ESC"[A"
243 //#define ESC_CURSOR_DOWN "\n"
245 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
246 // cmds modifying text[]
247 static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
253 FORWARD = 1, // code depends on "1" for array index
254 BACK = -1, // code depends on "-1" for array index
255 LIMITED = 0, // char_search() only current line
256 FULL = 1, // char_search() to the end/beginning of entire text
258 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
259 S_TO_WS = 2, // used in skip_thing() for moving "dot"
260 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
261 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
262 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
266 // vi.c expects chars to be unsigned.
267 // busybox build system provides that, but it's better
268 // to audit and fix the source
271 // many references - keep near the top of globals
272 char *text, *end; // pointers to the user data in memory
273 char *dot; // where all the action takes place
274 int text_size; // size of the allocated buffer
278 #define VI_AUTOINDENT 1
279 #define VI_SHOWMATCH 2
280 #define VI_IGNORECASE 4
281 #define VI_ERR_METHOD 8
282 #define autoindent (vi_setops & VI_AUTOINDENT)
283 #define showmatch (vi_setops & VI_SHOWMATCH )
284 #define ignorecase (vi_setops & VI_IGNORECASE)
285 // indicate error with beep or flash
286 #define err_method (vi_setops & VI_ERR_METHOD)
288 #if ENABLE_FEATURE_VI_READONLY
289 smallint readonly_mode;
290 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
291 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
292 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
294 #define SET_READONLY_FILE(flags) ((void)0)
295 #define SET_READONLY_MODE(flags) ((void)0)
296 #define UNSET_READONLY_FILE(flags) ((void)0)
299 smallint editing; // >0 while we are editing a file
300 // [code audit says "can be 0, 1 or 2 only"]
301 smallint cmd_mode; // 0=command 1=insert 2=replace
302 int modified_count; // buffer contents changed if !0
303 int last_modified_count; // = -1;
304 int cmdline_filecnt; // how many file names on cmd line
305 int cmdcnt; // repetition count
306 unsigned rows, columns; // the terminal screen is this size
307 #if ENABLE_FEATURE_VI_ASK_TERMINAL
308 int get_rowcol_error;
310 int crow, ccol; // cursor is on Crow x Ccol
311 int offset; // chars scrolled off the screen to the left
312 int have_status_msg; // is default edit status needed?
313 // [don't make smallint!]
314 int last_status_cksum; // hash of current status line
315 char *current_filename;
316 char *screenbegin; // index into text[], of top line on the screen
317 char *screen; // pointer to the virtual screen buffer
318 int screensize; // and its size
320 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
321 #if ENABLE_FEATURE_VI_CRASHME
322 char last_input_char; // last char read from user
325 #if ENABLE_FEATURE_VI_DOT_CMD
326 smallint adding2q; // are we currently adding user input to q
327 int lmc_len; // length of last_modifying_cmd
328 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
330 #if ENABLE_FEATURE_VI_SEARCH
331 char *last_search_pattern; // last pattern from a '/' or '?' search
335 #if ENABLE_FEATURE_VI_YANKMARK
336 char *edit_file__cur_line;
338 int refresh__old_offset;
339 int format_edit_status__tot;
341 // a few references only
342 #if ENABLE_FEATURE_VI_YANKMARK
343 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
345 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
346 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
347 char *context_start, *context_end;
349 #if ENABLE_FEATURE_VI_USE_SIGNALS
350 sigjmp_buf restart; // int_handler() jumps to location remembered here
352 struct termios term_orig; // remember what the cooked mode was
353 #if ENABLE_FEATURE_VI_COLON
354 char *initial_cmds[3]; // currently 2 entries, NULL terminated
356 // Should be just enough to hold a key sequence,
357 // but CRASHME mode uses it as generated command buffer too
358 #if ENABLE_FEATURE_VI_CRASHME
359 char readbuffer[128];
361 char readbuffer[KEYCODE_BUFFER_SIZE];
363 #define STATUS_BUFFER_LEN 200
364 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
365 #if ENABLE_FEATURE_VI_DOT_CMD
366 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
368 char get_input_line__buf[MAX_INPUT_LEN]; // former static
370 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
372 #if ENABLE_FEATURE_VI_UNDO
373 // undo_push() operations
376 #define UNDO_INS_CHAIN 2
377 #define UNDO_DEL_CHAIN 3
378 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
379 #define UNDO_QUEUED_FLAG 4
380 #define UNDO_INS_QUEUED 4
381 #define UNDO_DEL_QUEUED 5
382 #define UNDO_USE_SPOS 32
383 #define UNDO_EMPTY 64
384 // Pass-through flags for functions that can be undone
387 #define ALLOW_UNDO_CHAIN 2
388 # if ENABLE_FEATURE_VI_UNDO_QUEUE
389 #define ALLOW_UNDO_QUEUED 3
390 char undo_queue_state;
392 char *undo_queue_spos; // Start position of queued operation
393 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
395 // If undo queuing disabled, don't invoke the missing queue logic
396 #define ALLOW_UNDO_QUEUED 1
399 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
400 int start; // Offset where the data should be restored/deleted
401 int length; // total data size
402 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
403 char undo_text[1]; // text that was deleted (if deletion)
405 #endif /* ENABLE_FEATURE_VI_UNDO */
407 #define G (*ptr_to_globals)
408 #define text (G.text )
409 #define text_size (G.text_size )
414 #define vi_setops (G.vi_setops )
415 #define editing (G.editing )
416 #define cmd_mode (G.cmd_mode )
417 #define modified_count (G.modified_count )
418 #define last_modified_count (G.last_modified_count)
419 #define cmdline_filecnt (G.cmdline_filecnt )
420 #define cmdcnt (G.cmdcnt )
421 #define rows (G.rows )
422 #define columns (G.columns )
423 #define crow (G.crow )
424 #define ccol (G.ccol )
425 #define offset (G.offset )
426 #define status_buffer (G.status_buffer )
427 #define have_status_msg (G.have_status_msg )
428 #define last_status_cksum (G.last_status_cksum )
429 #define current_filename (G.current_filename )
430 #define screen (G.screen )
431 #define screensize (G.screensize )
432 #define screenbegin (G.screenbegin )
433 #define tabstop (G.tabstop )
434 #define last_forward_char (G.last_forward_char )
435 #if ENABLE_FEATURE_VI_CRASHME
436 #define last_input_char (G.last_input_char )
438 #if ENABLE_FEATURE_VI_READONLY
439 #define readonly_mode (G.readonly_mode )
441 #define readonly_mode 0
443 #define adding2q (G.adding2q )
444 #define lmc_len (G.lmc_len )
446 #define ioq_start (G.ioq_start )
447 #define last_search_pattern (G.last_search_pattern)
449 #define edit_file__cur_line (G.edit_file__cur_line)
450 #define refresh__old_offset (G.refresh__old_offset)
451 #define format_edit_status__tot (G.format_edit_status__tot)
453 #define YDreg (G.YDreg )
454 //#define Ureg (G.Ureg )
455 #define mark (G.mark )
456 #define context_start (G.context_start )
457 #define context_end (G.context_end )
458 #define restart (G.restart )
459 #define term_orig (G.term_orig )
460 #define initial_cmds (G.initial_cmds )
461 #define readbuffer (G.readbuffer )
462 #define scr_out_buf (G.scr_out_buf )
463 #define last_modifying_cmd (G.last_modifying_cmd )
464 #define get_input_line__buf (G.get_input_line__buf)
466 #if ENABLE_FEATURE_VI_UNDO
467 #define undo_stack_tail (G.undo_stack_tail )
468 # if ENABLE_FEATURE_VI_UNDO_QUEUE
469 #define undo_queue_state (G.undo_queue_state)
470 #define undo_q (G.undo_q )
471 #define undo_queue (G.undo_queue )
472 #define undo_queue_spos (G.undo_queue_spos )
476 #define INIT_G() do { \
477 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
478 last_modified_count = -1; \
479 /* "" but has space for 2 chars: */ \
480 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
483 #if ENABLE_FEATURE_VI_CRASHME
484 static int crashme = 0;
487 static void show_status_line(void); // put a message on the bottom line
488 static void status_line_bold(const char *, ...);
490 static void show_help(void)
492 puts("These features are available:"
493 #if ENABLE_FEATURE_VI_SEARCH
494 "\n\tPattern searches with / and ?"
496 #if ENABLE_FEATURE_VI_DOT_CMD
497 "\n\tLast command repeat with ."
499 #if ENABLE_FEATURE_VI_YANKMARK
500 "\n\tLine marking with 'x"
501 "\n\tNamed buffers with \"x"
503 #if ENABLE_FEATURE_VI_READONLY
504 //not implemented: "\n\tReadonly if vi is called as \"view\""
505 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
507 #if ENABLE_FEATURE_VI_SET
508 "\n\tSome colon mode commands with :"
510 #if ENABLE_FEATURE_VI_SETOPTS
511 "\n\tSettable options with \":set\""
513 #if ENABLE_FEATURE_VI_USE_SIGNALS
514 "\n\tSignal catching- ^C"
515 "\n\tJob suspend and resume with ^Z"
517 #if ENABLE_FEATURE_VI_WIN_RESIZE
518 "\n\tAdapt to window re-sizes"
523 static void write1(const char *out)
528 #if ENABLE_FEATURE_VI_WIN_RESIZE
529 static int query_screen_dimensions(void)
531 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
532 if (rows > MAX_SCR_ROWS)
534 if (columns > MAX_SCR_COLS)
535 columns = MAX_SCR_COLS;
539 static ALWAYS_INLINE int query_screen_dimensions(void)
545 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
546 static int mysleep(int hund)
548 struct pollfd pfd[1];
553 pfd[0].fd = STDIN_FILENO;
554 pfd[0].events = POLLIN;
555 return safe_poll(pfd, 1, hund*10) > 0;
558 //----- Set terminal attributes --------------------------------
559 static void rawmode(void)
561 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
562 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
565 static void cookmode(void)
568 tcsetattr_stdin_TCSANOW(&term_orig);
571 //----- Terminal Drawing ---------------------------------------
572 // The terminal is made up of 'rows' line of 'columns' columns.
573 // classically this would be 24 x 80.
574 // screen coordinates
580 // 23,0 ... 23,79 <- status line
582 //----- Move the cursor to row x col (count from 0, not 1) -------
583 static void place_cursor(int row, int col)
585 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
587 if (row < 0) row = 0;
588 if (row >= rows) row = rows - 1;
589 if (col < 0) col = 0;
590 if (col >= columns) col = columns - 1;
592 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
596 //----- Erase from cursor to end of line -----------------------
597 static void clear_to_eol(void)
599 write1(ESC_CLEAR2EOL);
602 static void go_bottom_and_clear_to_eol(void)
604 place_cursor(rows - 1, 0);
608 //----- Start standout mode ------------------------------------
609 static void standout_start(void)
611 write1(ESC_BOLD_TEXT);
614 //----- End standout mode --------------------------------------
615 static void standout_end(void)
617 write1(ESC_NORM_TEXT);
620 //----- Text Movement Routines ---------------------------------
621 static char *begin_line(char *p) // return pointer to first char cur line
624 p = memrchr(text, '\n', p - text);
632 static char *end_line(char *p) // return pointer to NL of cur line
635 p = memchr(p, '\n', end - p - 1);
642 static char *dollar_line(char *p) // return pointer to just before NL line
645 // Try to stay off of the Newline
646 if (*p == '\n' && (p - begin_line(p)) > 0)
651 static char *prev_line(char *p) // return pointer first char prev line
653 p = begin_line(p); // goto beginning of cur line
654 if (p > text && p[-1] == '\n')
655 p--; // step to prev line
656 p = begin_line(p); // goto beginning of prev line
660 static char *next_line(char *p) // return pointer first char next line
663 if (p < end - 1 && *p == '\n')
664 p++; // step to next line
668 //----- Text Information Routines ------------------------------
669 static char *end_screen(void)
674 // find new bottom line
676 for (cnt = 0; cnt < rows - 2; cnt++)
682 // count line from start to stop
683 static int count_lines(char *start, char *stop)
688 if (stop < start) { // start and stop are backwards- reverse them
694 stop = end_line(stop);
695 while (start <= stop && start <= end - 1) {
696 start = end_line(start);
704 static char *find_line(int li) // find beginning of line #li
708 for (q = text; li > 1; li--) {
714 static int next_tabstop(int col)
716 return col + ((tabstop - 1) - (col % tabstop));
719 //----- Erase the Screen[] memory ------------------------------
720 static void screen_erase(void)
722 memset(screen, ' ', screensize); // clear new screen
725 static void new_screen(int ro, int co)
730 screensize = ro * co + 8;
731 s = screen = xmalloc(screensize);
732 // initialize the new screen. assume this will be a empty file.
734 // non-existent text[] lines start with a tilde (~).
735 //screen[(1 * co) + 0] = '~';
736 //screen[(2 * co) + 0] = '~';
738 //screen[((ro-2) * co) + 0] = '~';
746 //----- Synchronize the cursor to Dot --------------------------
747 static NOINLINE void sync_cursor(char *d, int *row, int *col)
749 char *beg_cur; // begin and end of "d" line
753 beg_cur = begin_line(d); // first char of cur line
755 if (beg_cur < screenbegin) {
756 // "d" is before top line on screen
757 // how many lines do we have to move
758 cnt = count_lines(beg_cur, screenbegin);
760 screenbegin = beg_cur;
761 if (cnt > (rows - 1) / 2) {
762 // we moved too many lines. put "dot" in middle of screen
763 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
764 screenbegin = prev_line(screenbegin);
768 char *end_scr; // begin and end of screen
769 end_scr = end_screen(); // last char of screen
770 if (beg_cur > end_scr) {
771 // "d" is after bottom line on screen
772 // how many lines do we have to move
773 cnt = count_lines(end_scr, beg_cur);
774 if (cnt > (rows - 1) / 2)
775 goto sc1; // too many lines
776 for (ro = 0; ro < cnt - 1; ro++) {
777 // move screen begin the same amount
778 screenbegin = next_line(screenbegin);
779 // now, move the end of screen
780 end_scr = next_line(end_scr);
781 end_scr = end_line(end_scr);
785 // "d" is on screen- find out which row
787 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
793 // find out what col "d" is on
795 while (tp < d) { // drive "co" to correct column
796 if (*tp == '\n') //vda || *tp == '\0')
799 // handle tabs like real vi
800 if (d == tp && cmd_mode) {
803 co = next_tabstop(co);
804 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
805 co++; // display as ^X, use 2 columns
811 // "co" is the column where "dot" is.
812 // The screen has "columns" columns.
813 // The currently displayed columns are 0+offset -- columns+ofset
814 // |-------------------------------------------------------------|
816 // offset | |------- columns ----------------|
818 // If "co" is already in this range then we do not have to adjust offset
819 // but, we do have to subtract the "offset" bias from "co".
820 // If "co" is outside this range then we have to change "offset".
821 // If the first char of a line is a tab the cursor will try to stay
822 // in column 7, but we have to set offset to 0.
824 if (co < 0 + offset) {
827 if (co >= columns + offset) {
828 offset = co - columns + 1;
830 // if the first char of the line is a tab, and "dot" is sitting on it
831 // force offset to 0.
832 if (d == beg_cur && *d == '\t') {
841 //----- Format a text[] line into a buffer ---------------------
842 static char* format_line(char *src /*, int li*/)
847 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
849 c = '~'; // char in col 0 in non-existent lines is '~'
851 while (co < columns + tabstop) {
852 // have we gone past the end?
857 if ((c & 0x80) && !Isprint(c)) {
860 if (c < ' ' || c == 0x7f) {
864 while ((co % tabstop) != (tabstop - 1)) {
872 c += '@'; // Ctrl-X -> 'X'
877 // discard scrolled-off-to-the-left portion,
878 // in tabstop-sized pieces
879 if (ofs >= tabstop && co >= tabstop) {
880 memmove(dest, dest + tabstop, co);
887 // check "short line, gigantic offset" case
890 // discard last scrolled off part
893 // fill the rest with spaces
895 memset(&dest[co], ' ', columns - co);
899 //----- Refresh the changed screen lines -----------------------
900 // Copy the source line from text[] into the buffer and note
901 // if the current screenline is different from the new buffer.
902 // If they differ then that line needs redrawing on the terminal.
904 static void refresh(int full_screen)
906 #define old_offset refresh__old_offset
909 char *tp, *sp; // pointer into text[] and screen[]
911 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
912 unsigned c = columns, r = rows;
913 query_screen_dimensions();
914 #if ENABLE_FEATURE_VI_USE_SIGNALS
915 full_screen |= (c - columns) | (r - rows);
917 if (c != columns || r != rows) {
919 // update screen memory since SIGWINCH won't have done it
920 new_screen(rows, columns);
924 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
925 tp = screenbegin; // index into text[] of top line
927 // compare text[] to screen[] and mark screen[] lines that need updating
928 for (li = 0; li < rows - 1; li++) {
929 int cs, ce; // column start & end
931 // format current text line
932 out_buf = format_line(tp /*, li*/);
934 // skip to the end of the current text[] line
936 char *t = memchr(tp, '\n', end - tp);
941 // see if there are any changes between virtual screen and out_buf
942 changed = FALSE; // assume no change
945 sp = &screen[li * columns]; // start of screen line
947 // force re-draw of every single column from 0 - columns-1
950 // compare newly formatted buffer with virtual screen
951 // look forward for first difference between buf and screen
952 for (; cs <= ce; cs++) {
953 if (out_buf[cs] != sp[cs]) {
954 changed = TRUE; // mark for redraw
959 // look backward for last difference between out_buf and screen
960 for (; ce >= cs; ce--) {
961 if (out_buf[ce] != sp[ce]) {
962 changed = TRUE; // mark for redraw
966 // now, cs is index of first diff, and ce is index of last diff
968 // if horz offset has changed, force a redraw
969 if (offset != old_offset) {
974 // make a sanity check of columns indexes
976 if (ce > columns - 1) ce = columns - 1;
977 if (cs > ce) { cs = 0; ce = columns - 1; }
978 // is there a change between virtual screen and out_buf
980 // copy changed part of buffer to virtual screen
981 memcpy(sp+cs, out_buf+cs, ce-cs+1);
982 place_cursor(li, cs);
983 // write line out to terminal
984 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
988 place_cursor(crow, ccol);
994 //----- Force refresh of all Lines -----------------------------
995 static void redraw(int full_screen)
997 // cursor to top,left; clear to the end of screen
998 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
999 screen_erase(); // erase the internal screen buffer
1000 last_status_cksum = 0; // force status update
1001 refresh(full_screen); // this will redraw the entire display
1005 //----- Flash the screen --------------------------------------
1006 static void flash(int h)
1015 static void indicate_error(void)
1017 #if ENABLE_FEATURE_VI_CRASHME
1028 //----- IO Routines --------------------------------------------
1029 static int readit(void) // read (maybe cursor) key from stdin
1035 // Wait for input. TIMEOUT = -1 makes read_key wait even
1036 // on nonblocking stdin.
1037 // Note: read_key sets errno to 0 on success.
1039 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1040 if (c == -1) { // EOF/error
1041 if (errno == EAGAIN) // paranoia
1043 go_bottom_and_clear_to_eol();
1044 cookmode(); // terminal to "cooked"
1045 bb_simple_error_msg_and_die("can't read user input");
1050 #if ENABLE_FEATURE_VI_DOT_CMD
1051 static int get_one_char(void)
1056 // we are not adding to the q.
1057 // but, we may be reading from a saved q.
1058 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1059 // when done - "ioq_start" is reset instead).
1060 if (ioq_start != NULL) {
1061 // there is a queue to get chars from.
1062 // careful with correct sign expansion!
1063 c = (unsigned char)*ioq++;
1073 // we are adding STDIN chars to q.
1075 if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 1) {
1076 // last_modifying_cmd[] is too small, can't remeber the cmd
1081 last_modifying_cmd[lmc_len++] = c;
1086 # define get_one_char() readit()
1089 // Get input line (uses "status line" area)
1090 static char *get_input_line(const char *prompt)
1092 // char [MAX_INPUT_LEN]
1093 #define buf get_input_line__buf
1098 strcpy(buf, prompt);
1099 last_status_cksum = 0; // force status update
1100 go_bottom_and_clear_to_eol();
1101 write1(prompt); // write out the :, /, or ? prompt
1104 while (i < MAX_INPUT_LEN) {
1106 if (c == '\n' || c == '\r' || c == 27)
1107 break; // this is end of input
1108 if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
1109 // user wants to erase prev char
1111 write1("\b \b"); // erase char on screen
1112 if (i <= 0) // user backs up before b-o-l, exit
1114 } else if (c > 0 && c < 256) { // exclude Unicode
1115 // (TODO: need to handle Unicode)
1126 static void Hit_Return(void)
1131 write1("[Hit return to continue]");
1133 while ((c = get_one_char()) != '\n' && c != '\r')
1135 redraw(TRUE); // force redraw all
1138 //----- Draw the status line at bottom of the screen -------------
1139 // show file status on status line
1140 static int format_edit_status(void)
1142 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1144 #define tot format_edit_status__tot
1146 int cur, percent, ret, trunc_at;
1148 // modified_count is now a counter rather than a flag. this
1149 // helps reduce the amount of line counting we need to do.
1150 // (this will cause a mis-reporting of modified status
1151 // once every MAXINT editing operations.)
1153 // it would be nice to do a similar optimization here -- if
1154 // we haven't done a motion that could have changed which line
1155 // we're on, then we shouldn't have to do this count_lines()
1156 cur = count_lines(text, dot);
1158 // count_lines() is expensive.
1159 // Call it only if something was changed since last time
1161 if (modified_count != last_modified_count) {
1162 tot = cur + count_lines(dot, end - 1) - 1;
1163 last_modified_count = modified_count;
1166 // current line percent
1167 // ------------- ~~ ----------
1170 percent = (100 * cur) / tot;
1176 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1177 columns : STATUS_BUFFER_LEN-1;
1179 ret = snprintf(status_buffer, trunc_at+1,
1180 #if ENABLE_FEATURE_VI_READONLY
1181 "%c %s%s%s %d/%d %d%%",
1183 "%c %s%s %d/%d %d%%",
1185 cmd_mode_indicator[cmd_mode & 3],
1186 (current_filename != NULL ? current_filename : "No file"),
1187 #if ENABLE_FEATURE_VI_READONLY
1188 (readonly_mode ? " [Readonly]" : ""),
1190 (modified_count ? " [Modified]" : ""),
1193 if (ret >= 0 && ret < trunc_at)
1194 return ret; // it all fit
1196 return trunc_at; // had to truncate
1200 static int bufsum(char *buf, int count)
1203 char *e = buf + count;
1205 sum += (unsigned char) *buf++;
1209 static void show_status_line(void)
1211 int cnt = 0, cksum = 0;
1213 // either we already have an error or status message, or we
1215 if (!have_status_msg) {
1216 cnt = format_edit_status();
1217 cksum = bufsum(status_buffer, cnt);
1219 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1220 last_status_cksum = cksum; // remember if we have seen this line
1221 go_bottom_and_clear_to_eol();
1222 write1(status_buffer);
1223 if (have_status_msg) {
1224 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1226 have_status_msg = 0;
1229 have_status_msg = 0;
1231 place_cursor(crow, ccol); // put cursor back in correct place
1236 //----- format the status buffer, the bottom line of screen ------
1237 static void status_line(const char *format, ...)
1241 va_start(args, format);
1242 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1245 have_status_msg = 1;
1247 static void status_line_bold(const char *format, ...)
1251 va_start(args, format);
1252 strcpy(status_buffer, ESC_BOLD_TEXT);
1253 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1254 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1257 strcat(status_buffer, ESC_NORM_TEXT);
1260 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
1262 static void status_line_bold_errno(const char *fn)
1264 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1267 // copy s to buf, convert unprintable
1268 static void print_literal(char *buf, const char *s)
1282 c_is_no_print = (c & 0x80) && !Isprint(c);
1283 if (c_is_no_print) {
1284 strcpy(d, ESC_NORM_TEXT);
1285 d += sizeof(ESC_NORM_TEXT)-1;
1288 if (c < ' ' || c == 0x7f) {
1296 if (c_is_no_print) {
1297 strcpy(d, ESC_BOLD_TEXT);
1298 d += sizeof(ESC_BOLD_TEXT)-1;
1304 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1308 static void not_implemented(const char *s)
1310 char buf[MAX_INPUT_LEN];
1311 print_literal(buf, s);
1312 status_line_bold("'%s' is not implemented", buf);
1315 //----- Block insert/delete, undo ops --------------------------
1316 #if ENABLE_FEATURE_VI_YANKMARK
1317 static char *text_yank(char *p, char *q, int dest) // copy text into a register
1320 if (cnt < 0) { // they are backwards- reverse them
1324 free(reg[dest]); // if already a yank register, free it
1325 reg[dest] = xstrndup(p, cnt + 1);
1329 static char what_reg(void)
1333 c = 'D'; // default to D-reg
1335 c = 'a' + (char) YDreg;
1343 static void check_context(char cmd)
1345 // A context is defined to be "modifying text"
1346 // Any modifying command establishes a new context.
1348 if (dot < context_start || dot > context_end) {
1349 if (strchr(modifying_cmds, cmd) != NULL) {
1350 // we are trying to modify text[]- make this the current context
1351 mark[27] = mark[26]; // move cur to prev
1352 mark[26] = dot; // move local to cur
1353 context_start = prev_line(prev_line(dot));
1354 context_end = next_line(next_line(dot));
1355 //loiter= start_loiter= now;
1360 static char *swap_context(char *p) // goto new context for '' command make this the current context
1364 // the current context is in mark[26]
1365 // the previous context is in mark[27]
1366 // only swap context if other context is valid
1367 if (text <= mark[27] && mark[27] <= end - 1) {
1371 context_start = prev_line(prev_line(prev_line(p)));
1372 context_end = next_line(next_line(next_line(p)));
1376 #endif /* FEATURE_VI_YANKMARK */
1378 #if ENABLE_FEATURE_VI_UNDO
1379 static void undo_push(char *, unsigned, unsigned char);
1382 // open a hole in text[]
1383 // might reallocate text[]! use p += text_hole_make(p, ...),
1384 // and be careful to not use pointers into potentially freed text[]!
1385 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1391 end += size; // adjust the new END
1392 if (end >= (text + text_size)) {
1394 text_size += end - (text + text_size) + 10240;
1395 new_text = xrealloc(text, text_size);
1396 bias = (new_text - text);
1397 screenbegin += bias;
1401 #if ENABLE_FEATURE_VI_YANKMARK
1404 for (i = 0; i < ARRAY_SIZE(mark); i++)
1411 memmove(p + size, p, end - size - p);
1412 memset(p, ' ', size); // clear new hole
1416 // close a hole in text[] - delete "p" through "q", inclusive
1417 // "undo" value indicates if this operation should be undo-able
1418 #if !ENABLE_FEATURE_VI_UNDO
1419 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
1421 static char *text_hole_delete(char *p, char *q, int undo)
1426 // move forwards, from beginning
1430 if (q < p) { // they are backward- swap them
1434 hole_size = q - p + 1;
1436 #if ENABLE_FEATURE_VI_UNDO
1441 undo_push(p, hole_size, UNDO_DEL);
1443 case ALLOW_UNDO_CHAIN:
1444 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1446 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1447 case ALLOW_UNDO_QUEUED:
1448 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1454 if (src < text || src > end)
1456 if (dest < text || dest >= end)
1460 goto thd_atend; // just delete the end of the buffer
1461 memmove(dest, src, cnt);
1463 end = end - hole_size; // adjust the new END
1465 dest = end - 1; // make sure dest in below end-1
1467 dest = end = text; // keep pointers valid
1472 #if ENABLE_FEATURE_VI_UNDO
1474 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1475 // Flush any queued objects to the undo stack
1476 static void undo_queue_commit(void)
1478 // Pushes the queue object onto the undo stack
1480 // Deleted character undo events grow from the end
1481 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1483 (undo_queue_state | UNDO_USE_SPOS)
1485 undo_queue_state = UNDO_EMPTY;
1490 # define undo_queue_commit() ((void)0)
1493 static void flush_undo_data(void)
1495 struct undo_object *undo_entry;
1497 while (undo_stack_tail) {
1498 undo_entry = undo_stack_tail;
1499 undo_stack_tail = undo_entry->prev;
1504 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1505 // Add to the undo stack
1506 static void undo_push(char *src, unsigned length, uint8_t u_type)
1508 struct undo_object *undo_entry;
1511 // UNDO_INS: insertion, undo will remove from buffer
1512 // UNDO_DEL: deleted text, undo will restore to buffer
1513 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1514 // The CHAIN operations are for handling multiple operations that the user
1515 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1516 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1517 // for the INS/DEL operation. The raw values should be equal to the values of
1518 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1520 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1521 // This undo queuing functionality groups multiple character typing or backspaces
1522 // into a single large undo object. This greatly reduces calls to malloc() for
1523 // single-character operations while typing and has the side benefit of letting
1524 // an undo operation remove chunks of text rather than a single character.
1526 case UNDO_EMPTY: // Just in case this ever happens...
1528 case UNDO_DEL_QUEUED:
1530 return; // Only queue single characters
1531 switch (undo_queue_state) {
1533 undo_queue_state = UNDO_DEL;
1535 undo_queue_spos = src;
1537 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1538 // If queue is full, dump it into an object
1539 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1540 undo_queue_commit();
1543 // Switch from storing inserted text to deleted text
1544 undo_queue_commit();
1545 undo_push(src, length, UNDO_DEL_QUEUED);
1549 case UNDO_INS_QUEUED:
1552 switch (undo_queue_state) {
1554 undo_queue_state = UNDO_INS;
1555 undo_queue_spos = src;
1558 undo_q++; // Don't need to save any data for insertions
1559 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1560 undo_queue_commit();
1564 // Switch from storing deleted text to inserted text
1565 undo_queue_commit();
1566 undo_push(src, length, UNDO_INS_QUEUED);
1572 // If undo queuing is disabled, ignore the queuing flag entirely
1573 u_type = u_type & ~UNDO_QUEUED_FLAG;
1576 // Allocate a new undo object
1577 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1578 // For UNDO_DEL objects, save deleted text
1579 if ((text + length) == end)
1581 // If this deletion empties text[], strip the newline. When the buffer becomes
1582 // zero-length, a newline is added back, which requires this to compensate.
1583 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1584 memcpy(undo_entry->undo_text, src, length);
1586 undo_entry = xzalloc(sizeof(*undo_entry));
1588 undo_entry->length = length;
1589 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1590 if ((u_type & UNDO_USE_SPOS) != 0) {
1591 undo_entry->start = undo_queue_spos - text; // use start position from queue
1593 undo_entry->start = src - text; // use offset from start of text buffer
1595 u_type = (u_type & ~UNDO_USE_SPOS);
1597 undo_entry->start = src - text;
1599 undo_entry->u_type = u_type;
1601 // Push it on undo stack
1602 undo_entry->prev = undo_stack_tail;
1603 undo_stack_tail = undo_entry;
1607 static void undo_push_insert(char *p, int len, int undo)
1611 undo_push(p, len, UNDO_INS);
1613 case ALLOW_UNDO_CHAIN:
1614 undo_push(p, len, UNDO_INS_CHAIN);
1616 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1617 case ALLOW_UNDO_QUEUED:
1618 undo_push(p, len, UNDO_INS_QUEUED);
1624 // Undo the last operation
1625 static void undo_pop(void)
1628 char *u_start, *u_end;
1629 struct undo_object *undo_entry;
1631 // Commit pending undo queue before popping (should be unnecessary)
1632 undo_queue_commit();
1634 undo_entry = undo_stack_tail;
1635 // Check for an empty undo stack
1637 status_line("Already at oldest change");
1641 switch (undo_entry->u_type) {
1643 case UNDO_DEL_CHAIN:
1644 // make hole and put in text that was deleted; deallocate text
1645 u_start = text + undo_entry->start;
1646 text_hole_make(u_start, undo_entry->length);
1647 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1648 status_line("Undo [%d] %s %d chars at position %d",
1649 modified_count, "restored",
1650 undo_entry->length, undo_entry->start
1654 case UNDO_INS_CHAIN:
1655 // delete what was inserted
1656 u_start = undo_entry->start + text;
1657 u_end = u_start - 1 + undo_entry->length;
1658 text_hole_delete(u_start, u_end, NO_UNDO);
1659 status_line("Undo [%d] %s %d chars at position %d",
1660 modified_count, "deleted",
1661 undo_entry->length, undo_entry->start
1666 switch (undo_entry->u_type) {
1667 // If this is the end of a chain, lower modification count and refresh display
1670 dot = (text + undo_entry->start);
1673 case UNDO_DEL_CHAIN:
1674 case UNDO_INS_CHAIN:
1678 // Deallocate the undo object we just processed
1679 undo_stack_tail = undo_entry->prev;
1682 // For chained operations, continue popping all the way down the chain.
1684 undo_pop(); // Follow the undo chain if one exists
1689 # define flush_undo_data() ((void)0)
1690 # define undo_queue_commit() ((void)0)
1691 #endif /* ENABLE_FEATURE_VI_UNDO */
1693 //----- Dot Movement Routines ----------------------------------
1694 static void dot_left(void)
1696 undo_queue_commit();
1697 if (dot > text && dot[-1] != '\n')
1701 static void dot_right(void)
1703 undo_queue_commit();
1704 if (dot < end - 1 && *dot != '\n')
1708 static void dot_begin(void)
1710 undo_queue_commit();
1711 dot = begin_line(dot); // return pointer to first char cur line
1714 static void dot_end(void)
1716 undo_queue_commit();
1717 dot = end_line(dot); // return pointer to last char cur line
1720 static char *move_to_col(char *p, int l)
1726 while (co < l && p < end) {
1727 if (*p == '\n') //vda || *p == '\0')
1730 co = next_tabstop(co);
1731 } else if (*p < ' ' || *p == 127) {
1732 co++; // display as ^X, use 2 columns
1740 static void dot_next(void)
1742 undo_queue_commit();
1743 dot = next_line(dot);
1746 static void dot_prev(void)
1748 undo_queue_commit();
1749 dot = prev_line(dot);
1752 static void dot_skip_over_ws(void)
1755 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1759 static void dot_scroll(int cnt, int dir)
1763 undo_queue_commit();
1764 for (; cnt > 0; cnt--) {
1767 // ctrl-Y scroll up one line
1768 screenbegin = prev_line(screenbegin);
1771 // ctrl-E scroll down one line
1772 screenbegin = next_line(screenbegin);
1775 // make sure "dot" stays on the screen so we dont scroll off
1776 if (dot < screenbegin)
1778 q = end_screen(); // find new bottom line
1780 dot = begin_line(q); // is dot is below bottom line?
1784 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1786 if (p >= end && end > text) {
1797 #if ENABLE_FEATURE_VI_DOT_CMD
1798 static void start_new_cmd_q(char c)
1800 // get buffer for new cmd
1801 // if there is a current cmd count put it in the buffer first
1803 lmc_len = sprintf(last_modifying_cmd, "%u%c", cmdcnt, c);
1804 } else { // just save char c onto queue
1805 last_modifying_cmd[0] = c;
1810 static void end_cmd_q(void)
1812 # if ENABLE_FEATURE_VI_YANKMARK
1813 YDreg = 26; // go back to default Yank/Delete reg
1818 # define end_cmd_q() ((void)0)
1819 #endif /* FEATURE_VI_DOT_CMD */
1821 // copy text into register, then delete text.
1822 // if dist <= 0, do not include, or go past, a NewLine
1824 #if !ENABLE_FEATURE_VI_UNDO
1825 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1827 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1831 // make sure start <= stop
1833 // they are backwards, reverse them
1839 // we cannot cross NL boundaries
1843 // dont go past a NewLine
1844 for (; p + 1 <= stop; p++) {
1846 stop = p; // "stop" just before NewLine
1852 #if ENABLE_FEATURE_VI_YANKMARK
1853 text_yank(start, stop, YDreg);
1855 if (yf == YANKDEL) {
1856 p = text_hole_delete(start, stop, undo);
1861 // might reallocate text[]!
1862 static int file_insert(const char *fn, char *p, int initial)
1866 struct stat statbuf;
1873 fd = open(fn, O_RDONLY);
1876 status_line_bold_errno(fn);
1881 if (fstat(fd, &statbuf) < 0) {
1882 status_line_bold_errno(fn);
1885 if (!S_ISREG(statbuf.st_mode)) {
1886 status_line_bold("'%s' is not a regular file", fn);
1889 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1890 p += text_hole_make(p, size);
1891 cnt = full_read(fd, p, size);
1893 status_line_bold_errno(fn);
1894 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1895 } else if (cnt < size) {
1896 // There was a partial read, shrink unused space
1897 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1898 status_line_bold("can't read '%s'", fn);
1903 #if ENABLE_FEATURE_VI_READONLY
1905 && ((access(fn, W_OK) < 0) ||
1906 // root will always have access()
1907 // so we check fileperms too
1908 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1911 SET_READONLY_FILE(readonly_mode);
1917 // find matching char of pair () [] {}
1918 // will crash if c is not one of these
1919 static char *find_pair(char *p, const char c)
1921 const char *braces = "()[]{}";
1925 dir = strchr(braces, c) - braces;
1927 match = braces[dir];
1928 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1930 // look for match, count levels of pairs (( ))
1934 if (p < text || p >= end)
1937 level++; // increase pair levels
1939 level--; // reduce pair level
1941 return p; // found matching pair
1946 #if ENABLE_FEATURE_VI_SETOPTS
1947 // show the matching char of a pair, () [] {}
1948 static void showmatching(char *p)
1952 // we found half of a pair
1953 q = find_pair(p, *p); // get loc of matching char
1955 indicate_error(); // no matching char
1957 // "q" now points to matching pair
1958 save_dot = dot; // remember where we are
1959 dot = q; // go to new loc
1960 refresh(FALSE); // let the user see it
1961 mysleep(40); // give user some time
1962 dot = save_dot; // go back to old loc
1966 #endif /* FEATURE_VI_SETOPTS */
1968 // might reallocate text[]! use p += stupid_insert(p, ...),
1969 // and be careful to not use pointers into potentially freed text[]!
1970 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1973 bias = text_hole_make(p, 1);
1979 #if !ENABLE_FEATURE_VI_UNDO
1980 #define char_insert(a,b,c) char_insert(a,b)
1982 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1984 if (c == 22) { // Is this an ctrl-V?
1985 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1986 refresh(FALSE); // show the ^
1989 #if ENABLE_FEATURE_VI_UNDO
1990 undo_push_insert(p, 1, undo);
1995 } else if (c == 27) { // Is this an ESC?
1997 undo_queue_commit();
1999 end_cmd_q(); // stop adding to q
2000 last_status_cksum = 0; // force status update
2001 if ((p[-1] != '\n') && (dot > text)) {
2004 } else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
2007 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2010 // insert a char into text[]
2012 c = '\n'; // translate \r to \n
2013 #if ENABLE_FEATURE_VI_UNDO
2014 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2016 undo_queue_commit();
2018 undo_push_insert(p, 1, undo);
2022 p += 1 + stupid_insert(p, c); // insert the char
2023 #if ENABLE_FEATURE_VI_SETOPTS
2024 if (showmatch && strchr(")]}", c) != NULL) {
2025 showmatching(p - 1);
2027 if (autoindent && c == '\n') { // auto indent the new line
2030 q = prev_line(p); // use prev line as template
2031 len = strspn(q, " \t"); // space or tab
2034 bias = text_hole_make(p, len);
2037 #if ENABLE_FEATURE_VI_UNDO
2038 undo_push_insert(p, len, undo);
2049 // read text from file or create an empty buf
2050 // will also update current_filename
2051 static int init_text_buffer(char *fn)
2055 // allocate/reallocate text buffer
2058 screenbegin = dot = end = text = xzalloc(text_size);
2060 if (fn != current_filename) {
2061 free(current_filename);
2062 current_filename = xstrdup(fn);
2064 rc = file_insert(fn, text, 1);
2066 // file doesnt exist. Start empty buf with dummy line
2067 char_insert(text, '\n', NO_UNDO);
2072 last_modified_count = -1;
2073 #if ENABLE_FEATURE_VI_YANKMARK
2075 memset(mark, 0, sizeof(mark));
2080 #if ENABLE_FEATURE_VI_YANKMARK \
2081 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2082 || ENABLE_FEATURE_VI_CRASHME
2083 // might reallocate text[]! use p += string_insert(p, ...),
2084 // and be careful to not use pointers into potentially freed text[]!
2085 # if !ENABLE_FEATURE_VI_UNDO
2086 # define string_insert(a,b,c) string_insert(a,b)
2088 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2094 #if ENABLE_FEATURE_VI_UNDO
2095 undo_push_insert(p, i, undo);
2097 bias = text_hole_make(p, i);
2100 #if ENABLE_FEATURE_VI_YANKMARK
2103 for (cnt = 0; *s != '\0'; s++) {
2107 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2114 static int file_write(char *fn, char *first, char *last)
2116 int fd, cnt, charcnt;
2119 status_line_bold("No current filename");
2122 // By popular request we do not open file with O_TRUNC,
2123 // but instead ftruncate() it _after_ successful write.
2124 // Might reduce amount of data lost on power fail etc.
2125 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2128 cnt = last - first + 1;
2129 charcnt = full_write(fd, first, cnt);
2130 ftruncate(fd, charcnt);
2131 if (charcnt == cnt) {
2133 //modified_count = FALSE;
2141 #if ENABLE_FEATURE_VI_SEARCH
2142 # if ENABLE_FEATURE_VI_REGEX_SEARCH
2143 // search for pattern starting at p
2144 static char *char_search(char *p, const char *pat, int dir_and_range)
2146 struct re_pattern_buffer preg;
2153 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2155 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2157 memset(&preg, 0, sizeof(preg));
2158 err = re_compile_pattern(pat, strlen(pat), &preg);
2160 status_line_bold("bad search pattern '%s': %s", pat, err);
2164 range = (dir_and_range & 1);
2165 q = end - 1; // if FULL
2166 if (range == LIMITED)
2168 if (dir_and_range < 0) { // BACK?
2170 if (range == LIMITED)
2174 // RANGE could be negative if we are searching backwards
2184 // search for the compiled pattern, preg, in p[]
2185 // range < 0: search backward
2186 // range > 0: search forward
2188 // re_search() < 0: not found or error
2189 // re_search() >= 0: index of found pattern
2190 // struct pattern char int int int struct reg
2191 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2192 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2196 if (dir_and_range > 0) // FORWARD?
2203 # if ENABLE_FEATURE_VI_SETOPTS
2204 static int mycmp(const char *s1, const char *s2, int len)
2207 return strncasecmp(s1, s2, len);
2209 return strncmp(s1, s2, len);
2212 # define mycmp strncmp
2214 static char *char_search(char *p, const char *pat, int dir_and_range)
2221 range = (dir_and_range & 1);
2222 if (dir_and_range > 0) { //FORWARD?
2223 stop = end - 1; // assume range is p..end-1
2224 if (range == LIMITED)
2225 stop = next_line(p); // range is to next line
2226 for (start = p; start < stop; start++) {
2227 if (mycmp(start, pat, len) == 0) {
2232 stop = text; // assume range is text..p
2233 if (range == LIMITED)
2234 stop = prev_line(p); // range is to prev line
2235 for (start = p - len; start >= stop; start--) {
2236 if (mycmp(start, pat, len) == 0) {
2241 // pattern not found
2245 #endif /* FEATURE_VI_SEARCH */
2247 //----- The Colon commands -------------------------------------
2248 #if ENABLE_FEATURE_VI_COLON
2249 static char *get_one_address(char *p, int *addr) // get colon addr, if present
2253 IF_FEATURE_VI_YANKMARK(char c;)
2254 IF_FEATURE_VI_SEARCH(char *pat;)
2256 *addr = -1; // assume no addr
2257 if (*p == '.') { // the current line
2259 q = begin_line(dot);
2260 *addr = count_lines(text, q);
2262 #if ENABLE_FEATURE_VI_YANKMARK
2263 else if (*p == '\'') { // is this a mark addr
2267 if (c >= 'a' && c <= 'z') {
2270 q = mark[(unsigned char) c];
2271 if (q != NULL) { // is mark valid
2272 *addr = count_lines(text, q);
2277 #if ENABLE_FEATURE_VI_SEARCH
2278 else if (*p == '/') { // a search pattern
2279 q = strchrnul(++p, '/');
2280 pat = xstrndup(p, q - p); // save copy of pattern
2284 q = char_search(dot, pat, (FORWARD << 1) | FULL);
2286 *addr = count_lines(text, q);
2291 else if (*p == '$') { // the last line in file
2293 q = begin_line(end - 1);
2294 *addr = count_lines(text, q);
2295 } else if (isdigit(*p)) { // specific line number
2296 sscanf(p, "%d%n", addr, &st);
2299 // unrecognized address - assume -1
2305 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
2307 //----- get the address' i.e., 1,3 'a,'b -----
2308 // get FIRST addr, if present
2310 p++; // skip over leading spaces
2311 if (*p == '%') { // alias for 1,$
2314 *e = count_lines(text, end-1);
2317 p = get_one_address(p, b);
2320 if (*p == ',') { // is there a address separator
2324 // get SECOND addr, if present
2325 p = get_one_address(p, e);
2329 p++; // skip over trailing spaces
2333 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2334 static void setops(const char *args, const char *opname, int flg_no,
2335 const char *short_opname, int opt)
2337 const char *a = args + flg_no;
2338 int l = strlen(opname) - 1; // opname have + ' '
2340 // maybe strncmp? we had tons of erroneous strncasecmp's...
2341 if (strncasecmp(a, opname, l) == 0
2342 || strncasecmp(a, short_opname, 2) == 0
2352 #endif /* FEATURE_VI_COLON */
2354 // buf must be no longer than MAX_INPUT_LEN!
2355 static void colon(char *buf)
2357 #if !ENABLE_FEATURE_VI_COLON
2358 // Simple ":cmd" handler with minimal set of commands
2367 if (strncmp(p, "quit", cnt) == 0
2368 || strncmp(p, "q!", cnt) == 0
2370 if (modified_count && p[1] != '!') {
2371 status_line_bold("No write since last change (:%s! overrides)", p);
2377 if (strncmp(p, "write", cnt) == 0
2378 || strncmp(p, "wq", cnt) == 0
2379 || strncmp(p, "wn", cnt) == 0
2380 || (p[0] == 'x' && !p[1])
2382 if (modified_count != 0 || p[0] != 'x') {
2383 cnt = file_write(current_filename, text, end - 1);
2387 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2390 last_modified_count = -1;
2391 status_line("'%s' %uL, %uC",
2393 count_lines(text, end - 1), cnt
2396 || p[1] == 'q' || p[1] == 'n'
2397 || p[1] == 'Q' || p[1] == 'N'
2404 if (strncmp(p, "file", cnt) == 0) {
2405 last_status_cksum = 0; // force status update
2408 if (sscanf(p, "%d", &cnt) > 0) {
2409 dot = find_line(cnt);
2416 char c, *buf1, *q, *r;
2417 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2420 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2424 // :3154 // if (-e line 3154) goto it else stay put
2425 // :4,33w! foo // write a portion of buffer to file "foo"
2426 // :w // write all of buffer to current file
2428 // :q! // quit- dont care about modified file
2429 // :'a,'z!sort -u // filter block through sort
2430 // :'f // goto mark "f"
2431 // :'fl // list literal the mark "f" line
2432 // :.r bar // read file "bar" into buffer before dot
2433 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2434 // :/xyz/ // goto the "xyz" line
2435 // :s/find/replace/ // substitute pattern "find" with "replace"
2436 // :!<cmd> // run <cmd> then return
2442 buf++; // move past the ':'
2446 q = text; // assume 1,$ for the range
2448 li = count_lines(text, end - 1);
2449 fn = current_filename;
2451 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2452 buf = get_address(buf, &b, &e);
2454 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2455 // remember orig command line
2459 // get the COMMAND into cmd[]
2461 while (*buf != '\0') {
2467 // get any ARGuments
2468 while (isblank(*buf))
2472 buf1 = last_char_is(cmd, '!');
2475 *buf1 = '\0'; // get rid of !
2478 // if there is only one addr, then the addr
2479 // is the line number of the single line the
2480 // user wants. So, reset the end
2481 // pointer to point at end of the "b" line
2482 q = find_line(b); // what line is #b
2487 // we were given two addrs. change the
2488 // end pointer to the addr given by user.
2489 r = find_line(e); // what line is #e
2493 // ------------ now look for the command ------------
2495 if (i == 0) { // :123CR goto line #123
2497 dot = find_line(b); // what line is #b
2501 # if ENABLE_FEATURE_ALLOW_EXEC
2502 else if (cmd[0] == '!') { // run a cmd
2504 // :!ls run the <cmd>
2505 go_bottom_and_clear_to_eol();
2507 retcode = system(orig_buf + 1); // run the cmd
2509 printf("\nshell returned %i\n\n", retcode);
2511 Hit_Return(); // let user see results
2514 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
2515 if (b < 0) { // no addr given- use defaults
2516 b = e = count_lines(text, dot);
2518 status_line("%d", b);
2519 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
2520 if (b < 0) { // no addr given- use defaults
2521 q = begin_line(dot); // assume .,. for the range
2524 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
2526 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
2529 // don't edit, if the current file has been modified
2530 if (modified_count && !useforce) {
2531 status_line_bold("No write since last change (:%s! overrides)", cmd);
2535 // the user supplied a file name
2537 } else if (current_filename && current_filename[0]) {
2538 // no user supplied name- use the current filename
2539 // fn = current_filename; was set by default
2541 // no user file name, no current name- punt
2542 status_line_bold("No current filename");
2546 size = init_text_buffer(fn);
2548 # if ENABLE_FEATURE_VI_YANKMARK
2549 if (Ureg >= 0 && Ureg < 28) {
2550 free(reg[Ureg]); // free orig line reg- for 'U'
2553 /*if (YDreg < 28) - always true*/ {
2554 free(reg[YDreg]); // free default yank/delete register
2558 // how many lines in text[]?
2559 li = count_lines(text, end - 1);
2560 status_line("'%s'%s"
2561 IF_FEATURE_VI_READONLY("%s")
2564 (size < 0 ? " [New file]" : ""),
2565 IF_FEATURE_VI_READONLY(
2566 ((readonly_mode) ? " [Readonly]" : ""),
2568 li, (int)(end - text)
2570 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
2571 if (b != -1 || e != -1) {
2572 status_line_bold("No address allowed on this command");
2576 // user wants a new filename
2577 free(current_filename);
2578 current_filename = xstrdup(args);
2580 // user wants file status info
2581 last_status_cksum = 0; // force status update
2583 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
2584 // print out values of all features
2585 go_bottom_and_clear_to_eol();
2590 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
2591 if (b < 0) { // no addr given- use defaults
2592 q = begin_line(dot); // assume .,. for the range
2595 go_bottom_and_clear_to_eol();
2597 for (; q <= r; q++) {
2601 c_is_no_print = (c & 0x80) && !Isprint(c);
2602 if (c_is_no_print) {
2608 } else if (c < ' ' || c == 127) {
2620 } else if (strncmp(cmd, "quit", i) == 0 // quit
2621 || strncmp(cmd, "next", i) == 0 // edit next file
2622 || strncmp(cmd, "prev", i) == 0 // edit previous file
2627 // force end of argv list
2628 optind = cmdline_filecnt;
2633 // don't exit if the file been modified
2634 if (modified_count) {
2635 status_line_bold("No write since last change (:%s! overrides)", cmd);
2638 // are there other file to edit
2639 n = cmdline_filecnt - optind - 1;
2640 if (*cmd == 'q' && n > 0) {
2641 status_line_bold("%u more file(s) to edit", n);
2644 if (*cmd == 'n' && n <= 0) {
2645 status_line_bold("No more files to edit");
2649 // are there previous files to edit
2651 status_line_bold("No previous files to edit");
2657 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
2662 status_line_bold("No filename given");
2665 if (b < 0) { // no addr given- use defaults
2666 q = begin_line(dot); // assume "dot"
2668 // read after current line- unless user said ":0r foo"
2671 // read after last line
2675 { // dance around potentially-reallocated text[]
2676 uintptr_t ofs = q - text;
2677 size = file_insert(fn, q, 0);
2681 goto ret; // nothing was inserted
2682 // how many lines in text[]?
2683 li = count_lines(q, q + size - 1);
2685 IF_FEATURE_VI_READONLY("%s")
2688 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2692 // if the insert is before "dot" then we need to update
2696 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
2697 if (modified_count && !useforce) {
2698 status_line_bold("No write since last change (:%s! overrides)", cmd);
2700 // reset the filenames to edit
2701 optind = -1; // start from 0th file
2704 # if ENABLE_FEATURE_VI_SET
2705 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
2706 # if ENABLE_FEATURE_VI_SETOPTS
2709 i = 0; // offset into args
2710 // only blank is regarded as args delimiter. What about tab '\t'?
2711 if (!args[0] || strcasecmp(args, "all") == 0) {
2712 // print out values of all options
2713 # if ENABLE_FEATURE_VI_SETOPTS
2720 autoindent ? "" : "no",
2721 err_method ? "" : "no",
2722 ignorecase ? "" : "no",
2723 showmatch ? "" : "no",
2729 # if ENABLE_FEATURE_VI_SETOPTS
2732 if (strncmp(argp, "no", 2) == 0)
2733 i = 2; // ":set noautoindent"
2734 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2735 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
2736 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2737 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2738 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2740 sscanf(argp + i+8, "%u", &t);
2741 if (t > 0 && t <= MAX_TABSTOP)
2744 argp = skip_non_whitespace(argp);
2745 argp = skip_whitespace(argp);
2747 # endif /* FEATURE_VI_SETOPTS */
2748 # endif /* FEATURE_VI_SET */
2750 # if ENABLE_FEATURE_VI_SEARCH
2751 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
2752 char *F, *R, *flags;
2753 size_t len_F, len_R;
2754 int gflag; // global replace flag
2755 # if ENABLE_FEATURE_VI_UNDO
2756 int dont_chain_first_item = ALLOW_UNDO;
2759 // F points to the "find" pattern
2760 // R points to the "replace" pattern
2761 // replace the cmd line delimiters "/" with NULs
2762 c = orig_buf[1]; // what is the delimiter
2763 F = orig_buf + 2; // start of "find"
2764 R = strchr(F, c); // middle delimiter
2768 *R++ = '\0'; // terminate "find"
2769 flags = strchr(R, c);
2773 *flags++ = '\0'; // terminate "replace"
2777 if (b < 0) { // maybe :s/foo/bar/
2778 q = begin_line(dot); // start with cur line
2779 b = count_lines(text, q); // cur line number
2782 e = b; // maybe :.s/foo/bar/
2784 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2785 char *ls = q; // orig line start
2788 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
2791 // we found the "find" pattern - delete it
2792 // For undo support, the first item should not be chained
2793 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2794 # if ENABLE_FEATURE_VI_UNDO
2795 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2797 // insert the "replace" patern
2798 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2801 //q += bias; - recalculated anyway
2802 // check for "global" :s/foo/bar/g
2804 if ((found + len_R) < end_line(ls)) {
2806 goto vc4; // don't let q move past cur line
2812 # endif /* FEATURE_VI_SEARCH */
2813 } else if (strncmp(cmd, "version", i) == 0) { // show software version
2814 status_line(BB_VER);
2815 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2816 || strncmp(cmd, "wq", i) == 0
2817 || strncmp(cmd, "wn", i) == 0
2818 || (cmd[0] == 'x' && !cmd[1])
2821 //int forced = FALSE;
2823 // is there a file name to write to?
2827 # if ENABLE_FEATURE_VI_READONLY
2828 if (readonly_mode && !useforce) {
2829 status_line_bold("'%s' is read only", fn);
2834 // if "fn" is not write-able, chmod u+w
2835 // sprintf(syscmd, "chmod u+w %s", fn);
2839 if (modified_count != 0 || cmd[0] != 'x') {
2841 l = file_write(fn, q, r);
2846 //if (useforce && forced) {
2848 // sprintf(syscmd, "chmod u-w %s", fn);
2854 status_line_bold_errno(fn);
2856 // how many lines written
2857 li = count_lines(q, q + l - 1);
2858 status_line("'%s' %uL, %uC", fn, li, l);
2860 if (q == text && q + l == end) {
2862 last_modified_count = -1;
2865 || cmd[1] == 'q' || cmd[1] == 'n'
2866 || cmd[1] == 'Q' || cmd[1] == 'N'
2872 # if ENABLE_FEATURE_VI_YANKMARK
2873 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
2874 if (b < 0) { // no addr given- use defaults
2875 q = begin_line(dot); // assume .,. for the range
2878 text_yank(q, r, YDreg);
2879 li = count_lines(q, r);
2880 status_line("Yank %d lines (%d chars) into [%c]",
2881 li, strlen(reg[YDreg]), what_reg());
2885 not_implemented(cmd);
2888 dot = bound_dot(dot); // make sure "dot" is valid
2890 # if ENABLE_FEATURE_VI_SEARCH
2892 status_line(":s expression missing delimiters");
2894 #endif /* FEATURE_VI_COLON */
2897 //----- Char Routines --------------------------------------------
2898 // Chars that are part of a word-
2899 // 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2900 // Chars that are Not part of a word (stoppers)
2901 // !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2902 // Chars that are WhiteSpace
2903 // TAB NEWLINE VT FF RETURN SPACE
2904 // DO NOT COUNT NEWLINE AS WHITESPACE
2906 static int st_test(char *p, int type, int dir, char *tested)
2916 if (type == S_BEFORE_WS) {
2918 test = (!isspace(c) || c == '\n');
2920 if (type == S_TO_WS) {
2922 test = (!isspace(c) || c == '\n');
2924 if (type == S_OVER_WS) {
2928 if (type == S_END_PUNCT) {
2932 if (type == S_END_ALNUM) {
2934 test = (isalnum(c) || c == '_');
2940 static char *skip_thing(char *p, int linecnt, int dir, int type)
2944 while (st_test(p, type, dir, &c)) {
2945 // make sure we limit search to correct number of lines
2946 if (c == '\n' && --linecnt < 1)
2948 if (dir >= 0 && p >= end - 1)
2950 if (dir < 0 && p <= text)
2952 p += dir; // move to next char
2957 #if ENABLE_FEATURE_VI_USE_SIGNALS
2958 static void winch_handler(int sig UNUSED_PARAM)
2960 int save_errno = errno;
2961 // FIXME: do it in main loop!!!
2962 signal(SIGWINCH, winch_handler);
2963 query_screen_dimensions();
2964 new_screen(rows, columns); // get memory for virtual screen
2965 redraw(TRUE); // re-draw the screen
2968 static void tstp_handler(int sig UNUSED_PARAM)
2970 int save_errno = errno;
2972 // ioctl inside cookmode() was seen to generate SIGTTOU,
2973 // stopping us too early. Prevent that:
2974 signal(SIGTTOU, SIG_IGN);
2976 go_bottom_and_clear_to_eol();
2977 cookmode(); // terminal to "cooked"
2980 //signal(SIGTSTP, SIG_DFL);
2982 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2983 //signal(SIGTSTP, tstp_handler);
2985 // we have been "continued" with SIGCONT, restore screen and termios
2986 rawmode(); // terminal to "raw"
2987 last_status_cksum = 0; // force status update
2988 redraw(TRUE); // re-draw the screen
2992 static void int_handler(int sig)
2994 signal(SIGINT, int_handler);
2995 siglongjmp(restart, sig);
2997 #endif /* FEATURE_VI_USE_SIGNALS */
2999 static void do_cmd(int c);
3001 static int find_range(char **start, char **stop, char c)
3003 char *save_dot, *p, *q, *t;
3004 int cnt, multiline = 0, forward;
3009 // will a 'G' command move forwards or backwards?
3010 forward = cmdcnt == 0 || cmdcnt > count_lines(text, dot);
3012 if (strchr("cdy><", c)) {
3013 // these cmds operate on whole lines
3014 p = q = begin_line(p);
3015 for (cnt = 1; cnt < cmdcnt; cnt++) {
3019 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3020 // These cmds operate on char positions
3021 do_cmd(c); // execute movement cmd
3023 } else if (strchr("wW", c)) {
3024 do_cmd(c); // execute movement cmd
3025 // if we are at the next word's first char
3026 // step back one char
3027 // but check the possibilities when it is true
3028 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3029 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3030 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3031 dot--; // move back off of next word
3032 if (dot > text && *dot == '\n')
3033 dot--; // stay off NL
3035 } else if (strchr("H-k{", c) || (c == 'G' && !forward)) {
3036 // these operate on multi-lines backwards
3037 q = end_line(dot); // find NL
3038 do_cmd(c); // execute movement cmd
3041 } else if (strchr("L+j}\r\n", c) || (c == 'G' && forward)) {
3042 // these operate on multi-lines forwards
3043 p = begin_line(dot);
3044 do_cmd(c); // execute movement cmd
3045 dot_end(); // find NL
3048 // nothing -- this causes any other values of c to
3049 // represent the one-character range under the
3050 // cursor. this is correct for ' ' and 'l', but
3051 // perhaps no others.
3060 // backward char movements don't include start position
3061 if (q > p && strchr("^0bBh\b\177", c)) q--;
3064 for (t = p; t <= q; t++) {
3077 //---------------------------------------------------------------------
3078 //----- the Ascii Chart -----------------------------------------------
3079 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3080 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3081 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3082 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3083 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3084 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3085 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3086 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3087 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3088 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3089 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3090 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3091 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3092 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3093 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3094 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3095 //---------------------------------------------------------------------
3097 //----- Execute a Vi Command -----------------------------------
3098 static void do_cmd(int c)
3100 char *p, *q, *save_dot;
3106 // c1 = c; // quiet the compiler
3107 // cnt = yf = 0; // quiet the compiler
3108 // p = q = save_dot = buf; // quiet the compiler
3109 memset(buf, '\0', sizeof(buf));
3113 // if this is a cursor key, skip these checks
3121 case KEYCODE_PAGEUP:
3122 case KEYCODE_PAGEDOWN:
3123 case KEYCODE_DELETE:
3127 if (cmd_mode == 2) {
3128 // flip-flop Insert/Replace mode
3129 if (c == KEYCODE_INSERT)
3131 // we are 'R'eplacing the current *dot with new char
3133 // don't Replace past E-o-l
3134 cmd_mode = 1; // convert to insert
3135 undo_queue_commit();
3137 if (1 <= c || Isprint(c)) {
3139 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3140 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3145 if (cmd_mode == 1) {
3146 // hitting "Insert" twice means "R" replace mode
3147 if (c == KEYCODE_INSERT) goto dc5;
3148 // insert the char c at "dot"
3149 if (1 <= c || Isprint(c)) {
3150 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3165 #if ENABLE_FEATURE_VI_CRASHME
3166 case 0x14: // dc4 ctrl-T
3167 crashme = (crashme == 0) ? 1 : 0;
3197 default: // unrecognized command
3200 not_implemented(buf);
3201 end_cmd_q(); // stop adding to q
3202 case 0x00: // nul- ignore
3204 case 2: // ctrl-B scroll up full screen
3205 case KEYCODE_PAGEUP: // Cursor Key Page Up
3206 dot_scroll(rows - 2, -1);
3208 case 4: // ctrl-D scroll down half screen
3209 dot_scroll((rows - 2) / 2, 1);
3211 case 5: // ctrl-E scroll down one line
3214 case 6: // ctrl-F scroll down full screen
3215 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3216 dot_scroll(rows - 2, 1);
3218 case 7: // ctrl-G show current status
3219 last_status_cksum = 0; // force status update
3221 case 'h': // h- move left
3222 case KEYCODE_LEFT: // cursor key Left
3223 case 8: // ctrl-H- move left (This may be ERASE char)
3224 case 0x7f: // DEL- move left (This may be ERASE char)
3227 } while (--cmdcnt > 0);
3229 case 10: // Newline ^J
3230 case 'j': // j- goto next line, same col
3231 case KEYCODE_DOWN: // cursor key Down
3233 dot_next(); // go to next B-o-l
3234 // try stay in same col
3235 dot = move_to_col(dot, ccol + offset);
3236 } while (--cmdcnt > 0);
3238 case 12: // ctrl-L force redraw whole screen
3239 case 18: // ctrl-R force redraw
3240 redraw(TRUE); // this will redraw the entire display
3242 case 13: // Carriage Return ^M
3243 case '+': // +- goto next line
3247 } while (--cmdcnt > 0);
3249 case 21: // ctrl-U scroll up half screen
3250 dot_scroll((rows - 2) / 2, -1);
3252 case 25: // ctrl-Y scroll up one line
3258 cmd_mode = 0; // stop inserting
3259 undo_queue_commit();
3261 last_status_cksum = 0; // force status update
3263 case ' ': // move right
3264 case 'l': // move right
3265 case KEYCODE_RIGHT: // Cursor Key Right
3268 } while (--cmdcnt > 0);
3270 #if ENABLE_FEATURE_VI_YANKMARK
3271 case '"': // "- name a register to use for Delete/Yank
3272 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3273 if ((unsigned)c1 <= 25) { // a-z?
3279 case '\'': // '- goto a specific mark
3280 c1 = (get_one_char() | 0x20);
3281 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3285 if (text <= q && q < end) {
3287 dot_begin(); // go to B-o-l
3290 } else if (c1 == '\'') { // goto previous context
3291 dot = swap_context(dot); // swap current and previous context
3292 dot_begin(); // go to B-o-l
3298 case 'm': // m- Mark a line
3299 // this is really stupid. If there are any inserts or deletes
3300 // between text[0] and dot then this mark will not point to the
3301 // correct location! It could be off by many lines!
3302 // Well..., at least its quick and dirty.
3303 c1 = (get_one_char() | 0x20) - 'a';
3304 if ((unsigned)c1 <= 25) { // a-z?
3305 // remember the line
3311 case 'P': // P- Put register before
3312 case 'p': // p- put register after
3315 status_line_bold("Nothing in register %c", what_reg());
3318 // are we putting whole lines or strings
3319 if (strchr(p, '\n') != NULL) {
3321 dot_begin(); // putting lines- Put above
3324 // are we putting after very last line?
3325 if (end_line(dot) == (end - 1)) {
3326 dot = end; // force dot to end of text[]
3328 dot_next(); // next line, then put before
3333 dot_right(); // move to right, can move to NL
3335 string_insert(dot, p, ALLOW_UNDO); // insert the string
3336 end_cmd_q(); // stop adding to q
3338 case 'U': // U- Undo; replace current line with original version
3339 if (reg[Ureg] != NULL) {
3340 p = begin_line(dot);
3342 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3343 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3348 #endif /* FEATURE_VI_YANKMARK */
3349 #if ENABLE_FEATURE_VI_UNDO
3350 case 'u': // u- undo last operation
3354 case '$': // $- goto end of line
3355 case KEYCODE_END: // Cursor Key End
3357 dot = end_line(dot);
3363 case '%': // %- find matching char of pair () [] {}
3364 for (q = dot; q < end && *q != '\n'; q++) {
3365 if (strchr("()[]{}", *q) != NULL) {
3366 // we found half of a pair
3367 p = find_pair(q, *q);
3379 case 'f': // f- forward to a user specified char
3380 last_forward_char = get_one_char(); // get the search char
3382 // dont separate these two commands. 'f' depends on ';'
3384 //**** fall through to ... ';'
3385 case ';': // ;- look at rest of line for last forward char
3387 if (last_forward_char == 0)
3390 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3393 if (*q == last_forward_char)
3395 } while (--cmdcnt > 0);
3397 case ',': // repeat latest 'f' in opposite direction
3398 if (last_forward_char == 0)
3402 while (q >= text && *q != '\n' && *q != last_forward_char) {
3405 if (q >= text && *q == last_forward_char)
3407 } while (--cmdcnt > 0);
3410 case '-': // -- goto prev line
3414 } while (--cmdcnt > 0);
3416 #if ENABLE_FEATURE_VI_DOT_CMD
3417 case '.': // .- repeat the last modifying command
3418 // Stuff the last_modifying_cmd back into stdin
3419 // and let it be re-executed.
3421 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
3425 #if ENABLE_FEATURE_VI_SEARCH
3426 case '?': // /- search for a pattern
3427 case '/': // /- search for a pattern
3430 q = get_input_line(buf); // get input line- use "status line"
3431 if (q[0] && !q[1]) {
3432 if (last_search_pattern[0])
3433 last_search_pattern[0] = c;
3434 goto dc3; // if no pat re-use old pat
3436 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3437 // there is a new pat
3438 free(last_search_pattern);
3439 last_search_pattern = xstrdup(q);
3440 goto dc3; // now find the pattern
3442 // user changed mind and erased the "/"- do nothing
3444 case 'N': // N- backward search for last pattern
3445 dir = BACK; // assume BACKWARD search
3447 if (last_search_pattern[0] == '?') {
3451 goto dc4; // now search for pattern
3453 case 'n': // n- repeat search for last pattern
3454 // search rest of text[] starting at next char
3455 // if search fails return orignal "p" not the "p+1" address
3459 dir = FORWARD; // assume FORWARD search
3461 if (last_search_pattern[0] == '?') {
3466 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3468 dot = q; // good search, update "dot"
3472 // no pattern found between "dot" and "end"- continue at top
3477 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3478 if (q != NULL) { // found something
3479 dot = q; // found new pattern- goto it
3480 msg = "search hit BOTTOM, continuing at TOP";
3482 msg = "search hit TOP, continuing at BOTTOM";
3485 msg = "Pattern not found";
3489 status_line_bold("%s", msg);
3490 } while (--cmdcnt > 0);
3492 case '{': // {- move backward paragraph
3493 q = char_search(dot, "\n\n", ((unsigned)BACK << 1) | FULL);
3494 if (q != NULL) { // found blank line
3495 dot = next_line(q); // move to next blank line
3498 case '}': // }- move forward paragraph
3499 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3500 if (q != NULL) { // found blank line
3501 dot = next_line(q); // move to next blank line
3504 #endif /* FEATURE_VI_SEARCH */
3505 case '0': // 0- goto beginning of line
3515 if (c == '0' && cmdcnt < 1) {
3516 dot_begin(); // this was a standalone zero
3518 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3521 case ':': // :- the colon mode commands
3522 p = get_input_line(":"); // get input line- use "status line"
3523 colon(p); // execute the command
3525 case '<': // <- Left shift something
3526 case '>': // >- Right shift something
3527 cnt = count_lines(text, dot); // remember what line we are on
3528 c1 = get_one_char(); // get the type of thing to delete
3529 find_range(&p, &q, c1);
3530 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3533 i = count_lines(p, q); // # of lines we are shifting
3534 for ( ; i > 0; i--, p = next_line(p)) {
3536 // shift left- remove tab or 8 spaces
3538 // shrink buffer 1 char
3539 text_hole_delete(p, p, NO_UNDO);
3540 } else if (*p == ' ') {
3541 // we should be calculating columns, not just SPACE
3542 for (j = 0; *p == ' ' && j < tabstop; j++) {
3543 text_hole_delete(p, p, NO_UNDO);
3546 } else if (c == '>') {
3547 // shift right -- add tab or 8 spaces
3548 char_insert(p, '\t', ALLOW_UNDO);
3551 dot = find_line(cnt); // what line were we on
3553 end_cmd_q(); // stop adding to q
3555 case 'A': // A- append at e-o-l
3556 dot_end(); // go to e-o-l
3557 //**** fall through to ... 'a'
3558 case 'a': // a- append after current char
3563 case 'B': // B- back a blank-delimited Word
3564 case 'E': // E- end of a blank-delimited word
3565 case 'W': // W- forward a blank-delimited word
3570 if (c == 'W' || isspace(dot[dir])) {
3571 dot = skip_thing(dot, 1, dir, S_TO_WS);
3572 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3575 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3576 } while (--cmdcnt > 0);
3578 case 'C': // C- Change to e-o-l
3579 case 'D': // D- delete to e-o-l
3581 dot = dollar_line(dot); // move to before NL
3582 // copy text into a register and delete
3583 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3585 goto dc_i; // start inserting
3586 #if ENABLE_FEATURE_VI_DOT_CMD
3588 end_cmd_q(); // stop adding to q
3591 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3592 c1 = get_one_char();
3595 // c1 < 0 if the key was special. Try "g<up-arrow>"
3596 // TODO: if Unicode?
3597 buf[1] = (c1 >= 0 ? c1 : '*');
3599 not_implemented(buf);
3605 case 'G': // G- goto to a line number (default= E-O-F)
3606 dot = end - 1; // assume E-O-F
3608 dot = find_line(cmdcnt); // what line is #cmdcnt
3612 case 'H': // H- goto top line on screen
3614 if (cmdcnt > (rows - 1)) {
3615 cmdcnt = (rows - 1);
3622 case 'I': // I- insert before first non-blank
3625 //**** fall through to ... 'i'
3626 case 'i': // i- insert before current char
3627 case KEYCODE_INSERT: // Cursor Key Insert
3629 cmd_mode = 1; // start inserting
3630 undo_queue_commit(); // commit queue when cmd_mode changes
3632 case 'J': // J- join current and next lines together
3634 dot_end(); // move to NL
3635 if (dot < end - 1) { // make sure not last char in text[]
3636 #if ENABLE_FEATURE_VI_UNDO
3637 undo_push(dot, 1, UNDO_DEL);
3638 *dot++ = ' '; // replace NL with space
3639 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3644 while (isblank(*dot)) { // delete leading WS
3645 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3648 } while (--cmdcnt > 0);
3649 end_cmd_q(); // stop adding to q
3651 case 'L': // L- goto bottom line on screen
3653 if (cmdcnt > (rows - 1)) {
3654 cmdcnt = (rows - 1);
3662 case 'M': // M- goto middle line on screen
3664 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3665 dot = next_line(dot);
3667 case 'O': // O- open a empty line above
3669 p = begin_line(dot);
3670 if (p[-1] == '\n') {
3672 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3674 dot = char_insert(dot, '\n', ALLOW_UNDO);
3677 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3682 case 'R': // R- continuous Replace char
3685 undo_queue_commit();
3687 case KEYCODE_DELETE:
3689 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3691 case 'X': // X- delete char before dot
3692 case 'x': // x- delete the current char
3693 case 's': // s- substitute the current char
3698 if (dot[dir] != '\n') {
3700 dot--; // delete prev char
3701 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3703 } while (--cmdcnt > 0);
3704 end_cmd_q(); // stop adding to q
3706 goto dc_i; // start inserting
3708 case 'Z': // Z- if modified, {write}; exit
3709 // ZZ means to save file (if necessary), then exit
3710 c1 = get_one_char();
3715 if (modified_count) {
3716 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3717 status_line_bold("'%s' is read only", current_filename);
3720 cnt = file_write(current_filename, text, end - 1);
3723 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3724 } else if (cnt == (end - 1 - text + 1)) {
3731 case '^': // ^- move to first non-blank on line
3735 case 'b': // b- back a word
3736 case 'e': // e- end of word
3741 if ((dot + dir) < text || (dot + dir) > end - 1)
3744 if (isspace(*dot)) {
3745 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3747 if (isalnum(*dot) || *dot == '_') {
3748 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3749 } else if (ispunct(*dot)) {
3750 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3752 } while (--cmdcnt > 0);
3754 case 'c': // c- change something
3755 case 'd': // d- delete something
3756 #if ENABLE_FEATURE_VI_YANKMARK
3757 case 'y': // y- yank something
3758 case 'Y': // Y- Yank a line
3761 int yf, ml, whole = 0;
3762 yf = YANKDEL; // assume either "c" or "d"
3763 #if ENABLE_FEATURE_VI_YANKMARK
3764 if (c == 'y' || c == 'Y')
3769 c1 = get_one_char(); // get the type of thing to delete
3770 // determine range, and whether it spans lines
3771 ml = find_range(&p, &q, c1);
3773 if (c1 == 27) { // ESC- user changed mind and wants out
3774 c = c1 = 27; // Escape- do nothing
3775 } else if (strchr("wW", c1)) {
3776 ml = 0; // multi-line ranges aren't allowed for words
3778 // don't include trailing WS as part of word
3779 while (isspace(*q) && q > p) {
3783 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3784 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3785 // partial line copy text into a register and delete
3786 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3787 } else if (strchr("cdykjGHL+-{}\r\n", c1)) {
3788 // whole line copy text into a register and delete
3789 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3792 // could not recognize object
3793 c = c1 = 27; // error-
3799 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3800 // on the last line of file don't move to prev line
3801 if (whole && dot != (end-1)) {
3804 } else if (c == 'd') {
3810 // if CHANGING, not deleting, start inserting after the delete
3812 strcpy(buf, "Change");
3813 goto dc_i; // start inserting
3816 strcpy(buf, "Delete");
3818 #if ENABLE_FEATURE_VI_YANKMARK
3819 if (c == 'y' || c == 'Y') {
3820 strcpy(buf, "Yank");
3824 for (cnt = 0; p <= q; p++) {
3828 status_line("%s %u lines (%u chars) using [%c]",
3829 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3831 end_cmd_q(); // stop adding to q
3835 case 'k': // k- goto prev line, same col
3836 case KEYCODE_UP: // cursor key Up
3839 dot = move_to_col(dot, ccol + offset); // try stay in same col
3840 } while (--cmdcnt > 0);
3842 case 'r': // r- replace the current char with user input
3843 c1 = get_one_char(); // get the replacement char
3845 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3846 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3849 end_cmd_q(); // stop adding to q
3851 case 't': // t- move to char prior to next x
3852 last_forward_char = get_one_char();
3854 if (*dot == last_forward_char)
3856 last_forward_char = 0;
3858 case 'w': // w- forward a word
3860 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3861 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3862 } else if (ispunct(*dot)) { // we are on PUNCT
3863 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3866 dot++; // move over word
3867 if (isspace(*dot)) {
3868 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3870 } while (--cmdcnt > 0);
3873 c1 = get_one_char(); // get the replacement char
3876 cnt = (rows - 2) / 2; // put dot at center
3878 cnt = rows - 2; // put dot at bottom
3879 screenbegin = begin_line(dot); // start dot at top
3880 dot_scroll(cnt, -1);
3882 case '|': // |- move to column "cmdcnt"
3883 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3885 case '~': // ~- flip the case of letters a-z -> A-Z
3887 #if ENABLE_FEATURE_VI_UNDO
3888 if (islower(*dot)) {
3889 undo_push(dot, 1, UNDO_DEL);
3890 *dot = toupper(*dot);
3891 undo_push(dot, 1, UNDO_INS_CHAIN);
3892 } else if (isupper(*dot)) {
3893 undo_push(dot, 1, UNDO_DEL);
3894 *dot = tolower(*dot);
3895 undo_push(dot, 1, UNDO_INS_CHAIN);
3898 if (islower(*dot)) {
3899 *dot = toupper(*dot);
3901 } else if (isupper(*dot)) {
3902 *dot = tolower(*dot);
3907 } while (--cmdcnt > 0);
3908 end_cmd_q(); // stop adding to q
3910 //----- The Cursor and Function Keys -----------------------------
3911 case KEYCODE_HOME: // Cursor Key Home
3914 // The Fn keys could point to do_macro which could translate them
3916 case KEYCODE_FUN1: // Function Key F1
3917 case KEYCODE_FUN2: // Function Key F2
3918 case KEYCODE_FUN3: // Function Key F3
3919 case KEYCODE_FUN4: // Function Key F4
3920 case KEYCODE_FUN5: // Function Key F5
3921 case KEYCODE_FUN6: // Function Key F6
3922 case KEYCODE_FUN7: // Function Key F7
3923 case KEYCODE_FUN8: // Function Key F8
3924 case KEYCODE_FUN9: // Function Key F9
3925 case KEYCODE_FUN10: // Function Key F10
3926 case KEYCODE_FUN11: // Function Key F11
3927 case KEYCODE_FUN12: // Function Key F12
3933 // if text[] just became empty, add back an empty line
3935 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
3938 // it is OK for dot to exactly equal to end, otherwise check dot validity
3940 dot = bound_dot(dot); // make sure "dot" is valid
3942 #if ENABLE_FEATURE_VI_YANKMARK
3943 check_context(c); // update the current context
3947 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3948 cnt = dot - begin_line(dot);
3949 // Try to stay off of the Newline
3950 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3954 // NB! the CRASHME code is unmaintained, and doesn't currently build
3955 #if ENABLE_FEATURE_VI_CRASHME
3956 static int totalcmds = 0;
3957 static int Mp = 85; // Movement command Probability
3958 static int Np = 90; // Non-movement command Probability
3959 static int Dp = 96; // Delete command Probability
3960 static int Ip = 97; // Insert command Probability
3961 static int Yp = 98; // Yank command Probability
3962 static int Pp = 99; // Put command Probability
3963 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3964 static const char chars[20] = "\t012345 abcdABCD-=.$";
3965 static const char *const words[20] = {
3966 "this", "is", "a", "test",
3967 "broadcast", "the", "emergency", "of",
3968 "system", "quick", "brown", "fox",
3969 "jumped", "over", "lazy", "dogs",
3970 "back", "January", "Febuary", "March"
3972 static const char *const lines[20] = {
3973 "You should have received a copy of the GNU General Public License\n",
3974 "char c, cm, *cmd, *cmd1;\n",
3975 "generate a command by percentages\n",
3976 "Numbers may be typed as a prefix to some commands.\n",
3977 "Quit, discarding changes!\n",
3978 "Forced write, if permission originally not valid.\n",
3979 "In general, any ex or ed command (such as substitute or delete).\n",
3980 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3981 "Please get w/ me and I will go over it with you.\n",
3982 "The following is a list of scheduled, committed changes.\n",
3983 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3984 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3985 "Any question about transactions please contact Sterling Huxley.\n",
3986 "I will try to get back to you by Friday, December 31.\n",
3987 "This Change will be implemented on Friday.\n",
3988 "Let me know if you have problems accessing this;\n",
3989 "Sterling Huxley recently added you to the access list.\n",
3990 "Would you like to go to lunch?\n",
3991 "The last command will be automatically run.\n",
3992 "This is too much english for a computer geek.\n",
3994 static char *multilines[20] = {
3995 "You should have received a copy of the GNU General Public License\n",
3996 "char c, cm, *cmd, *cmd1;\n",
3997 "generate a command by percentages\n",
3998 "Numbers may be typed as a prefix to some commands.\n",
3999 "Quit, discarding changes!\n",
4000 "Forced write, if permission originally not valid.\n",
4001 "In general, any ex or ed command (such as substitute or delete).\n",
4002 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4003 "Please get w/ me and I will go over it with you.\n",
4004 "The following is a list of scheduled, committed changes.\n",
4005 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4006 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4007 "Any question about transactions please contact Sterling Huxley.\n",
4008 "I will try to get back to you by Friday, December 31.\n",
4009 "This Change will be implemented on Friday.\n",
4010 "Let me know if you have problems accessing this;\n",
4011 "Sterling Huxley recently added you to the access list.\n",
4012 "Would you like to go to lunch?\n",
4013 "The last command will be automatically run.\n",
4014 "This is too much english for a computer geek.\n",
4017 // create a random command to execute
4018 static void crash_dummy()
4020 static int sleeptime; // how long to pause between commands
4021 char c, cm, *cmd, *cmd1;
4022 int i, cnt, thing, rbi, startrbi, percent;
4024 // "dot" movement commands
4025 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4027 // is there already a command running?
4028 if (readbuffer[0] > 0)
4031 readbuffer[0] = 'X';
4033 sleeptime = 0; // how long to pause between commands
4034 memset(readbuffer, '\0', sizeof(readbuffer));
4035 // generate a command by percentages
4036 percent = (int) lrand48() % 100; // get a number from 0-99
4037 if (percent < Mp) { // Movement commands
4038 // available commands
4041 } else if (percent < Np) { // non-movement commands
4042 cmd = "mz<>\'\""; // available commands
4044 } else if (percent < Dp) { // Delete commands
4045 cmd = "dx"; // available commands
4047 } else if (percent < Ip) { // Inset commands
4048 cmd = "iIaAsrJ"; // available commands
4050 } else if (percent < Yp) { // Yank commands
4051 cmd = "yY"; // available commands
4053 } else if (percent < Pp) { // Put commands
4054 cmd = "pP"; // available commands
4057 // We do not know how to handle this command, try again
4061 // randomly pick one of the available cmds from "cmd[]"
4062 i = (int) lrand48() % strlen(cmd);
4064 if (strchr(":\024", cm))
4065 goto cd0; // dont allow colon or ctrl-T commands
4066 readbuffer[rbi++] = cm; // put cmd into input buffer
4068 // now we have the command-
4069 // there are 1, 2, and multi char commands
4070 // find out which and generate the rest of command as necessary
4071 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4072 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4073 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4074 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4076 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4078 readbuffer[rbi++] = c; // add movement to input buffer
4080 if (strchr("iIaAsc", cm)) { // multi-char commands
4082 // change some thing
4083 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4085 readbuffer[rbi++] = c; // add movement to input buffer
4087 thing = (int) lrand48() % 4; // what thing to insert
4088 cnt = (int) lrand48() % 10; // how many to insert
4089 for (i = 0; i < cnt; i++) {
4090 if (thing == 0) { // insert chars
4091 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4092 } else if (thing == 1) { // insert words
4093 strcat(readbuffer, words[(int) lrand48() % 20]);
4094 strcat(readbuffer, " ");
4095 sleeptime = 0; // how fast to type
4096 } else if (thing == 2) { // insert lines
4097 strcat(readbuffer, lines[(int) lrand48() % 20]);
4098 sleeptime = 0; // how fast to type
4099 } else { // insert multi-lines
4100 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4101 sleeptime = 0; // how fast to type
4104 strcat(readbuffer, ESC);
4106 readbuffer[0] = strlen(readbuffer + 1);
4110 mysleep(sleeptime); // sleep 1/100 sec
4113 // test to see if there are any errors
4114 static void crash_test()
4116 static time_t oldtim;
4123 strcat(msg, "end<text ");
4125 if (end > textend) {
4126 strcat(msg, "end>textend ");
4129 strcat(msg, "dot<text ");
4132 strcat(msg, "dot>end ");
4134 if (screenbegin < text) {
4135 strcat(msg, "screenbegin<text ");
4137 if (screenbegin > end - 1) {
4138 strcat(msg, "screenbegin>end-1 ");
4142 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4143 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4145 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4146 if (d[0] == '\n' || d[0] == '\r')
4151 if (tim >= (oldtim + 3)) {
4152 sprintf(status_buffer,
4153 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4154 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4160 static void edit_file(char *fn)
4162 #if ENABLE_FEATURE_VI_YANKMARK
4163 #define cur_line edit_file__cur_line
4166 #if ENABLE_FEATURE_VI_USE_SIGNALS
4170 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4174 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4175 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4176 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4178 write1(ESC"[999;999H" ESC"[6n");
4180 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4181 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4182 uint32_t rc = (k >> 32);
4183 columns = (rc & 0x7fff);
4184 if (columns > MAX_SCR_COLS)
4185 columns = MAX_SCR_COLS;
4186 rows = ((rc >> 16) & 0x7fff);
4187 if (rows > MAX_SCR_ROWS)
4188 rows = MAX_SCR_ROWS;
4192 new_screen(rows, columns); // get memory for virtual screen
4193 init_text_buffer(fn);
4195 #if ENABLE_FEATURE_VI_YANKMARK
4196 YDreg = 26; // default Yank/Delete reg
4197 // Ureg = 27; - const // hold orig line for "U" cmd
4198 mark[26] = mark[27] = text; // init "previous context"
4201 last_forward_char = '\0';
4202 #if ENABLE_FEATURE_VI_CRASHME
4203 last_input_char = '\0';
4208 #if ENABLE_FEATURE_VI_USE_SIGNALS
4209 signal(SIGWINCH, winch_handler);
4210 signal(SIGTSTP, tstp_handler);
4211 sig = sigsetjmp(restart, 1);
4213 screenbegin = dot = text;
4215 // int_handler() can jump to "restart",
4216 // must install handler *after* initializing "restart"
4217 signal(SIGINT, int_handler);
4220 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4223 offset = 0; // no horizontal offset
4225 #if ENABLE_FEATURE_VI_DOT_CMD
4232 #if ENABLE_FEATURE_VI_COLON
4237 while ((p = initial_cmds[n]) != NULL) {
4240 p = strchr(q, '\n');
4247 free(initial_cmds[n]);
4248 initial_cmds[n] = NULL;
4253 redraw(FALSE); // dont force every col re-draw
4254 //------This is the main Vi cmd handling loop -----------------------
4255 while (editing > 0) {
4256 #if ENABLE_FEATURE_VI_CRASHME
4258 if ((end - text) > 1) {
4259 crash_dummy(); // generate a random command
4262 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
4268 c = get_one_char(); // get a cmd from user
4269 #if ENABLE_FEATURE_VI_CRASHME
4270 last_input_char = c;
4272 #if ENABLE_FEATURE_VI_YANKMARK
4273 // save a copy of the current line- for the 'U" command
4274 if (begin_line(dot) != cur_line) {
4275 cur_line = begin_line(dot);
4276 text_yank(begin_line(dot), end_line(dot), Ureg);
4279 #if ENABLE_FEATURE_VI_DOT_CMD
4280 // If c is a command that changes text[],
4281 // (re)start remembering the input for the "." command.
4283 && ioq_start == NULL
4284 && cmd_mode == 0 // command mode
4285 && c > '\0' // exclude NUL and non-ASCII chars
4286 && c < 0x7f // (Unicode and such)
4287 && strchr(modifying_cmds, c)
4292 do_cmd(c); // execute the user command
4294 // poll to see if there is input already waiting. if we are
4295 // not able to display output fast enough to keep up, skip
4296 // the display update until we catch up with input.
4297 if (!readbuffer[0] && mysleep(0) == 0) {
4298 // no input pending - so update output
4302 #if ENABLE_FEATURE_VI_CRASHME
4304 crash_test(); // test editor variables
4307 //-------------------------------------------------------------------
4309 go_bottom_and_clear_to_eol();
4314 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4315 int vi_main(int argc, char **argv)
4321 #if ENABLE_FEATURE_VI_UNDO
4322 //undo_stack_tail = NULL; - already is
4323 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4324 undo_queue_state = UNDO_EMPTY;
4325 //undo_q = 0; - already is
4329 #if ENABLE_FEATURE_VI_CRASHME
4330 srand((long) getpid());
4332 #ifdef NO_SUCH_APPLET_YET
4333 // if we aren't "vi", we are "view"
4334 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4335 SET_READONLY_MODE(readonly_mode);
4339 // autoindent is not default in vim 7.3
4340 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4341 // 1- process $HOME/.exrc file (not inplemented yet)
4342 // 2- process EXINIT variable from environment
4343 // 3- process command line args
4344 #if ENABLE_FEATURE_VI_COLON
4346 char *p = getenv("EXINIT");
4348 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4351 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4353 #if ENABLE_FEATURE_VI_CRASHME
4358 #if ENABLE_FEATURE_VI_READONLY
4359 case 'R': // Read-only flag
4360 SET_READONLY_MODE(readonly_mode);
4363 #if ENABLE_FEATURE_VI_COLON
4364 case 'c': // cmd line vi command
4366 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4379 cmdline_filecnt = argc - optind;
4381 // "Save cursor, use alternate screen buffer, clear screen"
4382 write1(ESC"[?1049h");
4383 // This is the main file handling loop
4386 edit_file(argv[optind]); // might be NULL on 1st iteration
4387 // NB: optind can be changed by ":next" and ":rewind" commands
4389 if (optind >= cmdline_filecnt)
4392 // "Use normal screen buffer, restore cursor"
4393 write1(ESC"[?1049l");