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;)
2255 *addr = -1; // assume no addr
2256 if (*p == '.') { // the current line
2258 q = begin_line(dot);
2259 *addr = count_lines(text, q);
2261 #if ENABLE_FEATURE_VI_YANKMARK
2262 else if (*p == '\'') { // is this a mark addr
2266 if (c >= 'a' && c <= 'z') {
2269 q = mark[(unsigned char) c];
2270 if (q != NULL) { // is mark valid
2271 *addr = count_lines(text, q);
2276 #if ENABLE_FEATURE_VI_SEARCH
2277 else if (*p == '/') { // a search pattern
2278 q = strchrnul(p + 1, '/');
2280 // save copy of new pattern
2281 free(last_search_pattern);
2282 last_search_pattern = xstrndup(p, q - p);
2287 q = char_search(next_line(dot), last_search_pattern + 1,
2288 (FORWARD << 1) | FULL);
2290 *addr = count_lines(text, q);
2294 else if (*p == '$') { // the last line in file
2296 q = begin_line(end - 1);
2297 *addr = count_lines(text, q);
2298 } else if (isdigit(*p)) { // specific line number
2299 sscanf(p, "%d%n", addr, &st);
2302 // unrecognized address - assume -1
2308 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
2310 //----- get the address' i.e., 1,3 'a,'b -----
2311 // get FIRST addr, if present
2313 p++; // skip over leading spaces
2314 if (*p == '%') { // alias for 1,$
2317 *e = count_lines(text, end-1);
2320 p = get_one_address(p, b);
2323 if (*p == ',') { // is there a address separator
2327 // get SECOND addr, if present
2328 p = get_one_address(p, e);
2332 p++; // skip over trailing spaces
2336 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2337 static void setops(const char *args, const char *opname, int flg_no,
2338 const char *short_opname, int opt)
2340 const char *a = args + flg_no;
2341 int l = strlen(opname) - 1; // opname have + ' '
2343 // maybe strncmp? we had tons of erroneous strncasecmp's...
2344 if (strncasecmp(a, opname, l) == 0
2345 || strncasecmp(a, short_opname, 2) == 0
2355 #endif /* FEATURE_VI_COLON */
2357 // buf must be no longer than MAX_INPUT_LEN!
2358 static void colon(char *buf)
2360 #if !ENABLE_FEATURE_VI_COLON
2361 // Simple ":cmd" handler with minimal set of commands
2370 if (strncmp(p, "quit", cnt) == 0
2371 || strncmp(p, "q!", cnt) == 0
2373 if (modified_count && p[1] != '!') {
2374 status_line_bold("No write since last change (:%s! overrides)", p);
2380 if (strncmp(p, "write", cnt) == 0
2381 || strncmp(p, "wq", cnt) == 0
2382 || strncmp(p, "wn", cnt) == 0
2383 || (p[0] == 'x' && !p[1])
2385 if (modified_count != 0 || p[0] != 'x') {
2386 cnt = file_write(current_filename, text, end - 1);
2390 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2393 last_modified_count = -1;
2394 status_line("'%s' %uL, %uC",
2396 count_lines(text, end - 1), cnt
2399 || p[1] == 'q' || p[1] == 'n'
2400 || p[1] == 'Q' || p[1] == 'N'
2407 if (strncmp(p, "file", cnt) == 0) {
2408 last_status_cksum = 0; // force status update
2411 if (sscanf(p, "%d", &cnt) > 0) {
2412 dot = find_line(cnt);
2419 char c, *buf1, *q, *r;
2420 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2423 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2427 // :3154 // if (-e line 3154) goto it else stay put
2428 // :4,33w! foo // write a portion of buffer to file "foo"
2429 // :w // write all of buffer to current file
2431 // :q! // quit- dont care about modified file
2432 // :'a,'z!sort -u // filter block through sort
2433 // :'f // goto mark "f"
2434 // :'fl // list literal the mark "f" line
2435 // :.r bar // read file "bar" into buffer before dot
2436 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2437 // :/xyz/ // goto the "xyz" line
2438 // :s/find/replace/ // substitute pattern "find" with "replace"
2439 // :!<cmd> // run <cmd> then return
2445 buf++; // move past the ':'
2449 q = text; // assume 1,$ for the range
2451 li = count_lines(text, end - 1);
2452 fn = current_filename;
2454 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2455 buf = get_address(buf, &b, &e);
2457 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2458 // remember orig command line
2462 // get the COMMAND into cmd[]
2464 while (*buf != '\0') {
2470 // get any ARGuments
2471 while (isblank(*buf))
2475 buf1 = last_char_is(cmd, '!');
2478 *buf1 = '\0'; // get rid of !
2481 // if there is only one addr, then the addr
2482 // is the line number of the single line the
2483 // user wants. So, reset the end
2484 // pointer to point at end of the "b" line
2485 q = find_line(b); // what line is #b
2490 // we were given two addrs. change the
2491 // end pointer to the addr given by user.
2492 r = find_line(e); // what line is #e
2496 // ------------ now look for the command ------------
2498 if (i == 0) { // :123CR goto line #123
2500 dot = find_line(b); // what line is #b
2504 # if ENABLE_FEATURE_ALLOW_EXEC
2505 else if (cmd[0] == '!') { // run a cmd
2507 // :!ls run the <cmd>
2508 go_bottom_and_clear_to_eol();
2510 retcode = system(orig_buf + 1); // run the cmd
2512 printf("\nshell returned %i\n\n", retcode);
2514 Hit_Return(); // let user see results
2517 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
2518 if (b < 0) { // no addr given- use defaults
2519 b = e = count_lines(text, dot);
2521 status_line("%d", b);
2522 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
2523 if (b < 0) { // no addr given- use defaults
2524 q = begin_line(dot); // assume .,. for the range
2527 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
2529 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
2532 // don't edit, if the current file has been modified
2533 if (modified_count && !useforce) {
2534 status_line_bold("No write since last change (:%s! overrides)", cmd);
2538 // the user supplied a file name
2540 } else if (current_filename && current_filename[0]) {
2541 // no user supplied name- use the current filename
2542 // fn = current_filename; was set by default
2544 // no user file name, no current name- punt
2545 status_line_bold("No current filename");
2549 size = init_text_buffer(fn);
2551 # if ENABLE_FEATURE_VI_YANKMARK
2552 if (Ureg >= 0 && Ureg < 28) {
2553 free(reg[Ureg]); // free orig line reg- for 'U'
2556 /*if (YDreg < 28) - always true*/ {
2557 free(reg[YDreg]); // free default yank/delete register
2561 // how many lines in text[]?
2562 li = count_lines(text, end - 1);
2563 status_line("'%s'%s"
2564 IF_FEATURE_VI_READONLY("%s")
2567 (size < 0 ? " [New file]" : ""),
2568 IF_FEATURE_VI_READONLY(
2569 ((readonly_mode) ? " [Readonly]" : ""),
2571 li, (int)(end - text)
2573 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
2574 if (b != -1 || e != -1) {
2575 status_line_bold("No address allowed on this command");
2579 // user wants a new filename
2580 free(current_filename);
2581 current_filename = xstrdup(args);
2583 // user wants file status info
2584 last_status_cksum = 0; // force status update
2586 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
2587 // print out values of all features
2588 go_bottom_and_clear_to_eol();
2593 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
2594 if (b < 0) { // no addr given- use defaults
2595 q = begin_line(dot); // assume .,. for the range
2598 go_bottom_and_clear_to_eol();
2600 for (; q <= r; q++) {
2604 c_is_no_print = (c & 0x80) && !Isprint(c);
2605 if (c_is_no_print) {
2611 } else if (c < ' ' || c == 127) {
2623 } else if (strncmp(cmd, "quit", i) == 0 // quit
2624 || strncmp(cmd, "next", i) == 0 // edit next file
2625 || strncmp(cmd, "prev", i) == 0 // edit previous file
2630 // force end of argv list
2631 optind = cmdline_filecnt;
2636 // don't exit if the file been modified
2637 if (modified_count) {
2638 status_line_bold("No write since last change (:%s! overrides)", cmd);
2641 // are there other file to edit
2642 n = cmdline_filecnt - optind - 1;
2643 if (*cmd == 'q' && n > 0) {
2644 status_line_bold("%u more file(s) to edit", n);
2647 if (*cmd == 'n' && n <= 0) {
2648 status_line_bold("No more files to edit");
2652 // are there previous files to edit
2654 status_line_bold("No previous files to edit");
2660 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
2665 status_line_bold("No filename given");
2668 if (b < 0) { // no addr given- use defaults
2669 q = begin_line(dot); // assume "dot"
2671 // read after current line- unless user said ":0r foo"
2674 // read after last line
2678 { // dance around potentially-reallocated text[]
2679 uintptr_t ofs = q - text;
2680 size = file_insert(fn, q, 0);
2684 goto ret; // nothing was inserted
2685 // how many lines in text[]?
2686 li = count_lines(q, q + size - 1);
2688 IF_FEATURE_VI_READONLY("%s")
2691 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2695 // if the insert is before "dot" then we need to update
2699 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
2700 if (modified_count && !useforce) {
2701 status_line_bold("No write since last change (:%s! overrides)", cmd);
2703 // reset the filenames to edit
2704 optind = -1; // start from 0th file
2707 # if ENABLE_FEATURE_VI_SET
2708 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
2709 # if ENABLE_FEATURE_VI_SETOPTS
2712 i = 0; // offset into args
2713 // only blank is regarded as args delimiter. What about tab '\t'?
2714 if (!args[0] || strcasecmp(args, "all") == 0) {
2715 // print out values of all options
2716 # if ENABLE_FEATURE_VI_SETOPTS
2723 autoindent ? "" : "no",
2724 err_method ? "" : "no",
2725 ignorecase ? "" : "no",
2726 showmatch ? "" : "no",
2732 # if ENABLE_FEATURE_VI_SETOPTS
2735 if (strncmp(argp, "no", 2) == 0)
2736 i = 2; // ":set noautoindent"
2737 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2738 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
2739 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2740 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2741 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2743 sscanf(argp + i+8, "%u", &t);
2744 if (t > 0 && t <= MAX_TABSTOP)
2747 argp = skip_non_whitespace(argp);
2748 argp = skip_whitespace(argp);
2750 # endif /* FEATURE_VI_SETOPTS */
2751 # endif /* FEATURE_VI_SET */
2753 # if ENABLE_FEATURE_VI_SEARCH
2754 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
2755 char *F, *R, *flags;
2756 size_t len_F, len_R;
2757 int gflag; // global replace flag
2758 # if ENABLE_FEATURE_VI_UNDO
2759 int dont_chain_first_item = ALLOW_UNDO;
2762 // F points to the "find" pattern
2763 // R points to the "replace" pattern
2764 // replace the cmd line delimiters "/" with NULs
2765 c = orig_buf[1]; // what is the delimiter
2766 F = orig_buf + 2; // start of "find"
2767 R = strchr(F, c); // middle delimiter
2771 *R++ = '\0'; // terminate "find"
2772 flags = strchr(R, c);
2776 *flags++ = '\0'; // terminate "replace"
2780 if (b < 0) { // maybe :s/foo/bar/
2781 q = begin_line(dot); // start with cur line
2782 b = count_lines(text, q); // cur line number
2785 e = b; // maybe :.s/foo/bar/
2787 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2788 char *ls = q; // orig line start
2791 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
2794 // we found the "find" pattern - delete it
2795 // For undo support, the first item should not be chained
2796 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2797 # if ENABLE_FEATURE_VI_UNDO
2798 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2800 // insert the "replace" patern
2801 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2804 //q += bias; - recalculated anyway
2805 // check for "global" :s/foo/bar/g
2807 if ((found + len_R) < end_line(ls)) {
2809 goto vc4; // don't let q move past cur line
2815 # endif /* FEATURE_VI_SEARCH */
2816 } else if (strncmp(cmd, "version", i) == 0) { // show software version
2817 status_line(BB_VER);
2818 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2819 || strncmp(cmd, "wq", i) == 0
2820 || strncmp(cmd, "wn", i) == 0
2821 || (cmd[0] == 'x' && !cmd[1])
2824 //int forced = FALSE;
2826 // is there a file name to write to?
2830 # if ENABLE_FEATURE_VI_READONLY
2831 if (readonly_mode && !useforce) {
2832 status_line_bold("'%s' is read only", fn);
2837 // if "fn" is not write-able, chmod u+w
2838 // sprintf(syscmd, "chmod u+w %s", fn);
2842 if (modified_count != 0 || cmd[0] != 'x') {
2844 l = file_write(fn, q, r);
2849 //if (useforce && forced) {
2851 // sprintf(syscmd, "chmod u-w %s", fn);
2857 status_line_bold_errno(fn);
2859 // how many lines written
2860 li = count_lines(q, q + l - 1);
2861 status_line("'%s' %uL, %uC", fn, li, l);
2863 if (q == text && q + l == end) {
2865 last_modified_count = -1;
2868 || cmd[1] == 'q' || cmd[1] == 'n'
2869 || cmd[1] == 'Q' || cmd[1] == 'N'
2875 # if ENABLE_FEATURE_VI_YANKMARK
2876 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
2877 if (b < 0) { // no addr given- use defaults
2878 q = begin_line(dot); // assume .,. for the range
2881 text_yank(q, r, YDreg);
2882 li = count_lines(q, r);
2883 status_line("Yank %d lines (%d chars) into [%c]",
2884 li, strlen(reg[YDreg]), what_reg());
2888 not_implemented(cmd);
2891 dot = bound_dot(dot); // make sure "dot" is valid
2893 # if ENABLE_FEATURE_VI_SEARCH
2895 status_line(":s expression missing delimiters");
2897 #endif /* FEATURE_VI_COLON */
2900 //----- Char Routines --------------------------------------------
2901 // Chars that are part of a word-
2902 // 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2903 // Chars that are Not part of a word (stoppers)
2904 // !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2905 // Chars that are WhiteSpace
2906 // TAB NEWLINE VT FF RETURN SPACE
2907 // DO NOT COUNT NEWLINE AS WHITESPACE
2909 static int st_test(char *p, int type, int dir, char *tested)
2919 if (type == S_BEFORE_WS) {
2921 test = (!isspace(c) || c == '\n');
2923 if (type == S_TO_WS) {
2925 test = (!isspace(c) || c == '\n');
2927 if (type == S_OVER_WS) {
2931 if (type == S_END_PUNCT) {
2935 if (type == S_END_ALNUM) {
2937 test = (isalnum(c) || c == '_');
2943 static char *skip_thing(char *p, int linecnt, int dir, int type)
2947 while (st_test(p, type, dir, &c)) {
2948 // make sure we limit search to correct number of lines
2949 if (c == '\n' && --linecnt < 1)
2951 if (dir >= 0 && p >= end - 1)
2953 if (dir < 0 && p <= text)
2955 p += dir; // move to next char
2960 #if ENABLE_FEATURE_VI_USE_SIGNALS
2961 static void winch_handler(int sig UNUSED_PARAM)
2963 int save_errno = errno;
2964 // FIXME: do it in main loop!!!
2965 signal(SIGWINCH, winch_handler);
2966 query_screen_dimensions();
2967 new_screen(rows, columns); // get memory for virtual screen
2968 redraw(TRUE); // re-draw the screen
2971 static void tstp_handler(int sig UNUSED_PARAM)
2973 int save_errno = errno;
2975 // ioctl inside cookmode() was seen to generate SIGTTOU,
2976 // stopping us too early. Prevent that:
2977 signal(SIGTTOU, SIG_IGN);
2979 go_bottom_and_clear_to_eol();
2980 cookmode(); // terminal to "cooked"
2983 //signal(SIGTSTP, SIG_DFL);
2985 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2986 //signal(SIGTSTP, tstp_handler);
2988 // we have been "continued" with SIGCONT, restore screen and termios
2989 rawmode(); // terminal to "raw"
2990 last_status_cksum = 0; // force status update
2991 redraw(TRUE); // re-draw the screen
2995 static void int_handler(int sig)
2997 signal(SIGINT, int_handler);
2998 siglongjmp(restart, sig);
3000 #endif /* FEATURE_VI_USE_SIGNALS */
3002 static void do_cmd(int c);
3004 static int find_range(char **start, char **stop, char c)
3006 char *save_dot, *p, *q, *t;
3007 int cnt, multiline = 0, forward;
3012 // will a 'G' command move forwards or backwards?
3013 forward = cmdcnt == 0 || cmdcnt > count_lines(text, dot);
3015 if (strchr("cdy><", c)) {
3016 // these cmds operate on whole lines
3017 p = q = begin_line(p);
3018 for (cnt = 1; cnt < cmdcnt; cnt++) {
3022 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3023 // These cmds operate on char positions
3024 do_cmd(c); // execute movement cmd
3026 } else if (strchr("wW", c)) {
3027 do_cmd(c); // execute movement cmd
3028 // if we are at the next word's first char
3029 // step back one char
3030 // but check the possibilities when it is true
3031 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3032 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3033 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3034 dot--; // move back off of next word
3035 if (dot > text && *dot == '\n')
3036 dot--; // stay off NL
3038 } else if (strchr("H-k{", c) || (c == 'G' && !forward)) {
3039 // these operate on multi-lines backwards
3040 q = end_line(dot); // find NL
3041 do_cmd(c); // execute movement cmd
3044 } else if (strchr("L+j}\r\n", c) || (c == 'G' && forward)) {
3045 // these operate on multi-lines forwards
3046 p = begin_line(dot);
3047 do_cmd(c); // execute movement cmd
3048 dot_end(); // find NL
3051 // nothing -- this causes any other values of c to
3052 // represent the one-character range under the
3053 // cursor. this is correct for ' ' and 'l', but
3054 // perhaps no others.
3063 // backward char movements don't include start position
3064 if (q > p && strchr("^0bBh\b\177", c)) q--;
3067 for (t = p; t <= q; t++) {
3080 //---------------------------------------------------------------------
3081 //----- the Ascii Chart -----------------------------------------------
3082 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3083 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3084 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3085 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3086 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3087 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3088 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3089 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3090 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3091 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3092 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3093 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3094 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3095 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3096 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3097 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3098 //---------------------------------------------------------------------
3100 //----- Execute a Vi Command -----------------------------------
3101 static void do_cmd(int c)
3103 char *p, *q, *save_dot;
3109 // c1 = c; // quiet the compiler
3110 // cnt = yf = 0; // quiet the compiler
3111 // p = q = save_dot = buf; // quiet the compiler
3112 memset(buf, '\0', sizeof(buf));
3116 // if this is a cursor key, skip these checks
3124 case KEYCODE_PAGEUP:
3125 case KEYCODE_PAGEDOWN:
3126 case KEYCODE_DELETE:
3130 if (cmd_mode == 2) {
3131 // flip-flop Insert/Replace mode
3132 if (c == KEYCODE_INSERT)
3134 // we are 'R'eplacing the current *dot with new char
3136 // don't Replace past E-o-l
3137 cmd_mode = 1; // convert to insert
3138 undo_queue_commit();
3140 if (1 <= c || Isprint(c)) {
3142 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3143 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3148 if (cmd_mode == 1) {
3149 // hitting "Insert" twice means "R" replace mode
3150 if (c == KEYCODE_INSERT) goto dc5;
3151 // insert the char c at "dot"
3152 if (1 <= c || Isprint(c)) {
3153 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3168 #if ENABLE_FEATURE_VI_CRASHME
3169 case 0x14: // dc4 ctrl-T
3170 crashme = (crashme == 0) ? 1 : 0;
3200 default: // unrecognized command
3203 not_implemented(buf);
3204 end_cmd_q(); // stop adding to q
3205 case 0x00: // nul- ignore
3207 case 2: // ctrl-B scroll up full screen
3208 case KEYCODE_PAGEUP: // Cursor Key Page Up
3209 dot_scroll(rows - 2, -1);
3211 case 4: // ctrl-D scroll down half screen
3212 dot_scroll((rows - 2) / 2, 1);
3214 case 5: // ctrl-E scroll down one line
3217 case 6: // ctrl-F scroll down full screen
3218 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3219 dot_scroll(rows - 2, 1);
3221 case 7: // ctrl-G show current status
3222 last_status_cksum = 0; // force status update
3224 case 'h': // h- move left
3225 case KEYCODE_LEFT: // cursor key Left
3226 case 8: // ctrl-H- move left (This may be ERASE char)
3227 case 0x7f: // DEL- move left (This may be ERASE char)
3230 } while (--cmdcnt > 0);
3232 case 10: // Newline ^J
3233 case 'j': // j- goto next line, same col
3234 case KEYCODE_DOWN: // cursor key Down
3236 dot_next(); // go to next B-o-l
3237 // try stay in same col
3238 dot = move_to_col(dot, ccol + offset);
3239 } while (--cmdcnt > 0);
3241 case 12: // ctrl-L force redraw whole screen
3242 case 18: // ctrl-R force redraw
3243 redraw(TRUE); // this will redraw the entire display
3245 case 13: // Carriage Return ^M
3246 case '+': // +- goto next line
3250 } while (--cmdcnt > 0);
3252 case 21: // ctrl-U scroll up half screen
3253 dot_scroll((rows - 2) / 2, -1);
3255 case 25: // ctrl-Y scroll up one line
3261 cmd_mode = 0; // stop inserting
3262 undo_queue_commit();
3264 last_status_cksum = 0; // force status update
3266 case ' ': // move right
3267 case 'l': // move right
3268 case KEYCODE_RIGHT: // Cursor Key Right
3271 } while (--cmdcnt > 0);
3273 #if ENABLE_FEATURE_VI_YANKMARK
3274 case '"': // "- name a register to use for Delete/Yank
3275 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3276 if ((unsigned)c1 <= 25) { // a-z?
3282 case '\'': // '- goto a specific mark
3283 c1 = (get_one_char() | 0x20);
3284 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3288 if (text <= q && q < end) {
3290 dot_begin(); // go to B-o-l
3293 } else if (c1 == '\'') { // goto previous context
3294 dot = swap_context(dot); // swap current and previous context
3295 dot_begin(); // go to B-o-l
3301 case 'm': // m- Mark a line
3302 // this is really stupid. If there are any inserts or deletes
3303 // between text[0] and dot then this mark will not point to the
3304 // correct location! It could be off by many lines!
3305 // Well..., at least its quick and dirty.
3306 c1 = (get_one_char() | 0x20) - 'a';
3307 if ((unsigned)c1 <= 25) { // a-z?
3308 // remember the line
3314 case 'P': // P- Put register before
3315 case 'p': // p- put register after
3318 status_line_bold("Nothing in register %c", what_reg());
3321 // are we putting whole lines or strings
3322 if (strchr(p, '\n') != NULL) {
3324 dot_begin(); // putting lines- Put above
3327 // are we putting after very last line?
3328 if (end_line(dot) == (end - 1)) {
3329 dot = end; // force dot to end of text[]
3331 dot_next(); // next line, then put before
3336 dot_right(); // move to right, can move to NL
3338 string_insert(dot, p, ALLOW_UNDO); // insert the string
3339 end_cmd_q(); // stop adding to q
3341 case 'U': // U- Undo; replace current line with original version
3342 if (reg[Ureg] != NULL) {
3343 p = begin_line(dot);
3345 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3346 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3351 #endif /* FEATURE_VI_YANKMARK */
3352 #if ENABLE_FEATURE_VI_UNDO
3353 case 'u': // u- undo last operation
3357 case '$': // $- goto end of line
3358 case KEYCODE_END: // Cursor Key End
3360 dot = end_line(dot);
3366 case '%': // %- find matching char of pair () [] {}
3367 for (q = dot; q < end && *q != '\n'; q++) {
3368 if (strchr("()[]{}", *q) != NULL) {
3369 // we found half of a pair
3370 p = find_pair(q, *q);
3382 case 'f': // f- forward to a user specified char
3383 last_forward_char = get_one_char(); // get the search char
3385 // dont separate these two commands. 'f' depends on ';'
3387 //**** fall through to ... ';'
3388 case ';': // ;- look at rest of line for last forward char
3390 if (last_forward_char == 0)
3393 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3396 if (*q == last_forward_char)
3398 } while (--cmdcnt > 0);
3400 case ',': // repeat latest 'f' in opposite direction
3401 if (last_forward_char == 0)
3405 while (q >= text && *q != '\n' && *q != last_forward_char) {
3408 if (q >= text && *q == last_forward_char)
3410 } while (--cmdcnt > 0);
3413 case '-': // -- goto prev line
3417 } while (--cmdcnt > 0);
3419 #if ENABLE_FEATURE_VI_DOT_CMD
3420 case '.': // .- repeat the last modifying command
3421 // Stuff the last_modifying_cmd back into stdin
3422 // and let it be re-executed.
3424 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
3428 #if ENABLE_FEATURE_VI_SEARCH
3429 case '?': // /- search for a pattern
3430 case '/': // /- search for a pattern
3433 q = get_input_line(buf); // get input line- use "status line"
3434 if (q[0] && !q[1]) {
3435 if (last_search_pattern[0])
3436 last_search_pattern[0] = c;
3437 goto dc3; // if no pat re-use old pat
3439 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3440 // there is a new pat
3441 free(last_search_pattern);
3442 last_search_pattern = xstrdup(q);
3443 goto dc3; // now find the pattern
3445 // user changed mind and erased the "/"- do nothing
3447 case 'N': // N- backward search for last pattern
3448 dir = BACK; // assume BACKWARD search
3450 if (last_search_pattern[0] == '?') {
3454 goto dc4; // now search for pattern
3456 case 'n': // n- repeat search for last pattern
3457 // search rest of text[] starting at next char
3458 // if search fails return orignal "p" not the "p+1" address
3462 dir = FORWARD; // assume FORWARD search
3464 if (last_search_pattern[0] == '?') {
3469 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3471 dot = q; // good search, update "dot"
3475 // no pattern found between "dot" and "end"- continue at top
3480 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3481 if (q != NULL) { // found something
3482 dot = q; // found new pattern- goto it
3483 msg = "search hit BOTTOM, continuing at TOP";
3485 msg = "search hit TOP, continuing at BOTTOM";
3488 msg = "Pattern not found";
3492 status_line_bold("%s", msg);
3493 } while (--cmdcnt > 0);
3495 case '{': // {- move backward paragraph
3496 q = char_search(dot, "\n\n", ((unsigned)BACK << 1) | FULL);
3497 if (q != NULL) { // found blank line
3498 dot = next_line(q); // move to next blank line
3501 case '}': // }- move forward paragraph
3502 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3503 if (q != NULL) { // found blank line
3504 dot = next_line(q); // move to next blank line
3507 #endif /* FEATURE_VI_SEARCH */
3508 case '0': // 0- goto beginning of line
3518 if (c == '0' && cmdcnt < 1) {
3519 dot_begin(); // this was a standalone zero
3521 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3524 case ':': // :- the colon mode commands
3525 p = get_input_line(":"); // get input line- use "status line"
3526 colon(p); // execute the command
3528 case '<': // <- Left shift something
3529 case '>': // >- Right shift something
3530 cnt = count_lines(text, dot); // remember what line we are on
3531 c1 = get_one_char(); // get the type of thing to delete
3532 find_range(&p, &q, c1);
3533 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3536 i = count_lines(p, q); // # of lines we are shifting
3537 for ( ; i > 0; i--, p = next_line(p)) {
3539 // shift left- remove tab or 8 spaces
3541 // shrink buffer 1 char
3542 text_hole_delete(p, p, NO_UNDO);
3543 } else if (*p == ' ') {
3544 // we should be calculating columns, not just SPACE
3545 for (j = 0; *p == ' ' && j < tabstop; j++) {
3546 text_hole_delete(p, p, NO_UNDO);
3549 } else if (c == '>') {
3550 // shift right -- add tab or 8 spaces
3551 char_insert(p, '\t', ALLOW_UNDO);
3554 dot = find_line(cnt); // what line were we on
3556 end_cmd_q(); // stop adding to q
3558 case 'A': // A- append at e-o-l
3559 dot_end(); // go to e-o-l
3560 //**** fall through to ... 'a'
3561 case 'a': // a- append after current char
3566 case 'B': // B- back a blank-delimited Word
3567 case 'E': // E- end of a blank-delimited word
3568 case 'W': // W- forward a blank-delimited word
3573 if (c == 'W' || isspace(dot[dir])) {
3574 dot = skip_thing(dot, 1, dir, S_TO_WS);
3575 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3578 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3579 } while (--cmdcnt > 0);
3581 case 'C': // C- Change to e-o-l
3582 case 'D': // D- delete to e-o-l
3584 dot = dollar_line(dot); // move to before NL
3585 // copy text into a register and delete
3586 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3588 goto dc_i; // start inserting
3589 #if ENABLE_FEATURE_VI_DOT_CMD
3591 end_cmd_q(); // stop adding to q
3594 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3595 c1 = get_one_char();
3598 // c1 < 0 if the key was special. Try "g<up-arrow>"
3599 // TODO: if Unicode?
3600 buf[1] = (c1 >= 0 ? c1 : '*');
3602 not_implemented(buf);
3608 case 'G': // G- goto to a line number (default= E-O-F)
3609 dot = end - 1; // assume E-O-F
3611 dot = find_line(cmdcnt); // what line is #cmdcnt
3615 case 'H': // H- goto top line on screen
3617 if (cmdcnt > (rows - 1)) {
3618 cmdcnt = (rows - 1);
3625 case 'I': // I- insert before first non-blank
3628 //**** fall through to ... 'i'
3629 case 'i': // i- insert before current char
3630 case KEYCODE_INSERT: // Cursor Key Insert
3632 cmd_mode = 1; // start inserting
3633 undo_queue_commit(); // commit queue when cmd_mode changes
3635 case 'J': // J- join current and next lines together
3637 dot_end(); // move to NL
3638 if (dot < end - 1) { // make sure not last char in text[]
3639 #if ENABLE_FEATURE_VI_UNDO
3640 undo_push(dot, 1, UNDO_DEL);
3641 *dot++ = ' '; // replace NL with space
3642 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3647 while (isblank(*dot)) { // delete leading WS
3648 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3651 } while (--cmdcnt > 0);
3652 end_cmd_q(); // stop adding to q
3654 case 'L': // L- goto bottom line on screen
3656 if (cmdcnt > (rows - 1)) {
3657 cmdcnt = (rows - 1);
3665 case 'M': // M- goto middle line on screen
3667 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3668 dot = next_line(dot);
3670 case 'O': // O- open a empty line above
3672 p = begin_line(dot);
3673 if (p[-1] == '\n') {
3675 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3677 dot = char_insert(dot, '\n', ALLOW_UNDO);
3680 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3685 case 'R': // R- continuous Replace char
3688 undo_queue_commit();
3690 case KEYCODE_DELETE:
3692 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3694 case 'X': // X- delete char before dot
3695 case 'x': // x- delete the current char
3696 case 's': // s- substitute the current char
3701 if (dot[dir] != '\n') {
3703 dot--; // delete prev char
3704 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3706 } while (--cmdcnt > 0);
3707 end_cmd_q(); // stop adding to q
3709 goto dc_i; // start inserting
3711 case 'Z': // Z- if modified, {write}; exit
3712 // ZZ means to save file (if necessary), then exit
3713 c1 = get_one_char();
3718 if (modified_count) {
3719 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3720 status_line_bold("'%s' is read only", current_filename);
3723 cnt = file_write(current_filename, text, end - 1);
3726 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3727 } else if (cnt == (end - 1 - text + 1)) {
3734 case '^': // ^- move to first non-blank on line
3738 case 'b': // b- back a word
3739 case 'e': // e- end of word
3744 if ((dot + dir) < text || (dot + dir) > end - 1)
3747 if (isspace(*dot)) {
3748 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3750 if (isalnum(*dot) || *dot == '_') {
3751 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3752 } else if (ispunct(*dot)) {
3753 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3755 } while (--cmdcnt > 0);
3757 case 'c': // c- change something
3758 case 'd': // d- delete something
3759 #if ENABLE_FEATURE_VI_YANKMARK
3760 case 'y': // y- yank something
3761 case 'Y': // Y- Yank a line
3764 int yf, ml, whole = 0;
3765 yf = YANKDEL; // assume either "c" or "d"
3766 #if ENABLE_FEATURE_VI_YANKMARK
3767 if (c == 'y' || c == 'Y')
3772 c1 = get_one_char(); // get the type of thing to delete
3773 // determine range, and whether it spans lines
3774 ml = find_range(&p, &q, c1);
3776 if (c1 == 27) { // ESC- user changed mind and wants out
3777 c = c1 = 27; // Escape- do nothing
3778 } else if (strchr("wW", c1)) {
3779 ml = 0; // multi-line ranges aren't allowed for words
3781 // don't include trailing WS as part of word
3782 while (isspace(*q) && q > p) {
3786 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3787 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3788 // partial line copy text into a register and delete
3789 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3790 } else if (strchr("cdykjGHL+-{}\r\n", c1)) {
3791 // whole line copy text into a register and delete
3792 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3795 // could not recognize object
3796 c = c1 = 27; // error-
3802 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3803 // on the last line of file don't move to prev line
3804 if (whole && dot != (end-1)) {
3807 } else if (c == 'd') {
3813 // if CHANGING, not deleting, start inserting after the delete
3815 strcpy(buf, "Change");
3816 goto dc_i; // start inserting
3819 strcpy(buf, "Delete");
3821 #if ENABLE_FEATURE_VI_YANKMARK
3822 if (c == 'y' || c == 'Y') {
3823 strcpy(buf, "Yank");
3827 for (cnt = 0; p <= q; p++) {
3831 status_line("%s %u lines (%u chars) using [%c]",
3832 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3834 end_cmd_q(); // stop adding to q
3838 case 'k': // k- goto prev line, same col
3839 case KEYCODE_UP: // cursor key Up
3842 dot = move_to_col(dot, ccol + offset); // try stay in same col
3843 } while (--cmdcnt > 0);
3845 case 'r': // r- replace the current char with user input
3846 c1 = get_one_char(); // get the replacement char
3848 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3849 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3852 end_cmd_q(); // stop adding to q
3854 case 't': // t- move to char prior to next x
3855 last_forward_char = get_one_char();
3857 if (*dot == last_forward_char)
3859 last_forward_char = 0;
3861 case 'w': // w- forward a word
3863 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3864 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3865 } else if (ispunct(*dot)) { // we are on PUNCT
3866 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3869 dot++; // move over word
3870 if (isspace(*dot)) {
3871 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3873 } while (--cmdcnt > 0);
3876 c1 = get_one_char(); // get the replacement char
3879 cnt = (rows - 2) / 2; // put dot at center
3881 cnt = rows - 2; // put dot at bottom
3882 screenbegin = begin_line(dot); // start dot at top
3883 dot_scroll(cnt, -1);
3885 case '|': // |- move to column "cmdcnt"
3886 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3888 case '~': // ~- flip the case of letters a-z -> A-Z
3890 #if ENABLE_FEATURE_VI_UNDO
3891 if (islower(*dot)) {
3892 undo_push(dot, 1, UNDO_DEL);
3893 *dot = toupper(*dot);
3894 undo_push(dot, 1, UNDO_INS_CHAIN);
3895 } else if (isupper(*dot)) {
3896 undo_push(dot, 1, UNDO_DEL);
3897 *dot = tolower(*dot);
3898 undo_push(dot, 1, UNDO_INS_CHAIN);
3901 if (islower(*dot)) {
3902 *dot = toupper(*dot);
3904 } else if (isupper(*dot)) {
3905 *dot = tolower(*dot);
3910 } while (--cmdcnt > 0);
3911 end_cmd_q(); // stop adding to q
3913 //----- The Cursor and Function Keys -----------------------------
3914 case KEYCODE_HOME: // Cursor Key Home
3917 // The Fn keys could point to do_macro which could translate them
3919 case KEYCODE_FUN1: // Function Key F1
3920 case KEYCODE_FUN2: // Function Key F2
3921 case KEYCODE_FUN3: // Function Key F3
3922 case KEYCODE_FUN4: // Function Key F4
3923 case KEYCODE_FUN5: // Function Key F5
3924 case KEYCODE_FUN6: // Function Key F6
3925 case KEYCODE_FUN7: // Function Key F7
3926 case KEYCODE_FUN8: // Function Key F8
3927 case KEYCODE_FUN9: // Function Key F9
3928 case KEYCODE_FUN10: // Function Key F10
3929 case KEYCODE_FUN11: // Function Key F11
3930 case KEYCODE_FUN12: // Function Key F12
3936 // if text[] just became empty, add back an empty line
3938 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
3941 // it is OK for dot to exactly equal to end, otherwise check dot validity
3943 dot = bound_dot(dot); // make sure "dot" is valid
3945 #if ENABLE_FEATURE_VI_YANKMARK
3946 check_context(c); // update the current context
3950 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3951 cnt = dot - begin_line(dot);
3952 // Try to stay off of the Newline
3953 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3957 // NB! the CRASHME code is unmaintained, and doesn't currently build
3958 #if ENABLE_FEATURE_VI_CRASHME
3959 static int totalcmds = 0;
3960 static int Mp = 85; // Movement command Probability
3961 static int Np = 90; // Non-movement command Probability
3962 static int Dp = 96; // Delete command Probability
3963 static int Ip = 97; // Insert command Probability
3964 static int Yp = 98; // Yank command Probability
3965 static int Pp = 99; // Put command Probability
3966 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3967 static const char chars[20] = "\t012345 abcdABCD-=.$";
3968 static const char *const words[20] = {
3969 "this", "is", "a", "test",
3970 "broadcast", "the", "emergency", "of",
3971 "system", "quick", "brown", "fox",
3972 "jumped", "over", "lazy", "dogs",
3973 "back", "January", "Febuary", "March"
3975 static const char *const lines[20] = {
3976 "You should have received a copy of the GNU General Public License\n",
3977 "char c, cm, *cmd, *cmd1;\n",
3978 "generate a command by percentages\n",
3979 "Numbers may be typed as a prefix to some commands.\n",
3980 "Quit, discarding changes!\n",
3981 "Forced write, if permission originally not valid.\n",
3982 "In general, any ex or ed command (such as substitute or delete).\n",
3983 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3984 "Please get w/ me and I will go over it with you.\n",
3985 "The following is a list of scheduled, committed changes.\n",
3986 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3987 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3988 "Any question about transactions please contact Sterling Huxley.\n",
3989 "I will try to get back to you by Friday, December 31.\n",
3990 "This Change will be implemented on Friday.\n",
3991 "Let me know if you have problems accessing this;\n",
3992 "Sterling Huxley recently added you to the access list.\n",
3993 "Would you like to go to lunch?\n",
3994 "The last command will be automatically run.\n",
3995 "This is too much english for a computer geek.\n",
3997 static char *multilines[20] = {
3998 "You should have received a copy of the GNU General Public License\n",
3999 "char c, cm, *cmd, *cmd1;\n",
4000 "generate a command by percentages\n",
4001 "Numbers may be typed as a prefix to some commands.\n",
4002 "Quit, discarding changes!\n",
4003 "Forced write, if permission originally not valid.\n",
4004 "In general, any ex or ed command (such as substitute or delete).\n",
4005 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4006 "Please get w/ me and I will go over it with you.\n",
4007 "The following is a list of scheduled, committed changes.\n",
4008 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4009 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4010 "Any question about transactions please contact Sterling Huxley.\n",
4011 "I will try to get back to you by Friday, December 31.\n",
4012 "This Change will be implemented on Friday.\n",
4013 "Let me know if you have problems accessing this;\n",
4014 "Sterling Huxley recently added you to the access list.\n",
4015 "Would you like to go to lunch?\n",
4016 "The last command will be automatically run.\n",
4017 "This is too much english for a computer geek.\n",
4020 // create a random command to execute
4021 static void crash_dummy()
4023 static int sleeptime; // how long to pause between commands
4024 char c, cm, *cmd, *cmd1;
4025 int i, cnt, thing, rbi, startrbi, percent;
4027 // "dot" movement commands
4028 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4030 // is there already a command running?
4031 if (readbuffer[0] > 0)
4034 readbuffer[0] = 'X';
4036 sleeptime = 0; // how long to pause between commands
4037 memset(readbuffer, '\0', sizeof(readbuffer));
4038 // generate a command by percentages
4039 percent = (int) lrand48() % 100; // get a number from 0-99
4040 if (percent < Mp) { // Movement commands
4041 // available commands
4044 } else if (percent < Np) { // non-movement commands
4045 cmd = "mz<>\'\""; // available commands
4047 } else if (percent < Dp) { // Delete commands
4048 cmd = "dx"; // available commands
4050 } else if (percent < Ip) { // Inset commands
4051 cmd = "iIaAsrJ"; // available commands
4053 } else if (percent < Yp) { // Yank commands
4054 cmd = "yY"; // available commands
4056 } else if (percent < Pp) { // Put commands
4057 cmd = "pP"; // available commands
4060 // We do not know how to handle this command, try again
4064 // randomly pick one of the available cmds from "cmd[]"
4065 i = (int) lrand48() % strlen(cmd);
4067 if (strchr(":\024", cm))
4068 goto cd0; // dont allow colon or ctrl-T commands
4069 readbuffer[rbi++] = cm; // put cmd into input buffer
4071 // now we have the command-
4072 // there are 1, 2, and multi char commands
4073 // find out which and generate the rest of command as necessary
4074 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4075 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4076 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4077 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4079 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4081 readbuffer[rbi++] = c; // add movement to input buffer
4083 if (strchr("iIaAsc", cm)) { // multi-char commands
4085 // change some thing
4086 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4088 readbuffer[rbi++] = c; // add movement to input buffer
4090 thing = (int) lrand48() % 4; // what thing to insert
4091 cnt = (int) lrand48() % 10; // how many to insert
4092 for (i = 0; i < cnt; i++) {
4093 if (thing == 0) { // insert chars
4094 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4095 } else if (thing == 1) { // insert words
4096 strcat(readbuffer, words[(int) lrand48() % 20]);
4097 strcat(readbuffer, " ");
4098 sleeptime = 0; // how fast to type
4099 } else if (thing == 2) { // insert lines
4100 strcat(readbuffer, lines[(int) lrand48() % 20]);
4101 sleeptime = 0; // how fast to type
4102 } else { // insert multi-lines
4103 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4104 sleeptime = 0; // how fast to type
4107 strcat(readbuffer, ESC);
4109 readbuffer[0] = strlen(readbuffer + 1);
4113 mysleep(sleeptime); // sleep 1/100 sec
4116 // test to see if there are any errors
4117 static void crash_test()
4119 static time_t oldtim;
4126 strcat(msg, "end<text ");
4128 if (end > textend) {
4129 strcat(msg, "end>textend ");
4132 strcat(msg, "dot<text ");
4135 strcat(msg, "dot>end ");
4137 if (screenbegin < text) {
4138 strcat(msg, "screenbegin<text ");
4140 if (screenbegin > end - 1) {
4141 strcat(msg, "screenbegin>end-1 ");
4145 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4146 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4148 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4149 if (d[0] == '\n' || d[0] == '\r')
4154 if (tim >= (oldtim + 3)) {
4155 sprintf(status_buffer,
4156 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4157 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4163 static void edit_file(char *fn)
4165 #if ENABLE_FEATURE_VI_YANKMARK
4166 #define cur_line edit_file__cur_line
4169 #if ENABLE_FEATURE_VI_USE_SIGNALS
4173 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4177 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4178 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4179 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4181 write1(ESC"[999;999H" ESC"[6n");
4183 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4184 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4185 uint32_t rc = (k >> 32);
4186 columns = (rc & 0x7fff);
4187 if (columns > MAX_SCR_COLS)
4188 columns = MAX_SCR_COLS;
4189 rows = ((rc >> 16) & 0x7fff);
4190 if (rows > MAX_SCR_ROWS)
4191 rows = MAX_SCR_ROWS;
4195 new_screen(rows, columns); // get memory for virtual screen
4196 init_text_buffer(fn);
4198 #if ENABLE_FEATURE_VI_YANKMARK
4199 YDreg = 26; // default Yank/Delete reg
4200 // Ureg = 27; - const // hold orig line for "U" cmd
4201 mark[26] = mark[27] = text; // init "previous context"
4204 last_forward_char = '\0';
4205 #if ENABLE_FEATURE_VI_CRASHME
4206 last_input_char = '\0';
4211 #if ENABLE_FEATURE_VI_USE_SIGNALS
4212 signal(SIGWINCH, winch_handler);
4213 signal(SIGTSTP, tstp_handler);
4214 sig = sigsetjmp(restart, 1);
4216 screenbegin = dot = text;
4218 // int_handler() can jump to "restart",
4219 // must install handler *after* initializing "restart"
4220 signal(SIGINT, int_handler);
4223 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4226 offset = 0; // no horizontal offset
4228 #if ENABLE_FEATURE_VI_DOT_CMD
4235 #if ENABLE_FEATURE_VI_COLON
4240 while ((p = initial_cmds[n]) != NULL) {
4243 p = strchr(q, '\n');
4250 free(initial_cmds[n]);
4251 initial_cmds[n] = NULL;
4256 redraw(FALSE); // dont force every col re-draw
4257 //------This is the main Vi cmd handling loop -----------------------
4258 while (editing > 0) {
4259 #if ENABLE_FEATURE_VI_CRASHME
4261 if ((end - text) > 1) {
4262 crash_dummy(); // generate a random command
4265 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
4271 c = get_one_char(); // get a cmd from user
4272 #if ENABLE_FEATURE_VI_CRASHME
4273 last_input_char = c;
4275 #if ENABLE_FEATURE_VI_YANKMARK
4276 // save a copy of the current line- for the 'U" command
4277 if (begin_line(dot) != cur_line) {
4278 cur_line = begin_line(dot);
4279 text_yank(begin_line(dot), end_line(dot), Ureg);
4282 #if ENABLE_FEATURE_VI_DOT_CMD
4283 // If c is a command that changes text[],
4284 // (re)start remembering the input for the "." command.
4286 && ioq_start == NULL
4287 && cmd_mode == 0 // command mode
4288 && c > '\0' // exclude NUL and non-ASCII chars
4289 && c < 0x7f // (Unicode and such)
4290 && strchr(modifying_cmds, c)
4295 do_cmd(c); // execute the user command
4297 // poll to see if there is input already waiting. if we are
4298 // not able to display output fast enough to keep up, skip
4299 // the display update until we catch up with input.
4300 if (!readbuffer[0] && mysleep(0) == 0) {
4301 // no input pending - so update output
4305 #if ENABLE_FEATURE_VI_CRASHME
4307 crash_test(); // test editor variables
4310 //-------------------------------------------------------------------
4312 go_bottom_and_clear_to_eol();
4317 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4318 int vi_main(int argc, char **argv)
4324 #if ENABLE_FEATURE_VI_UNDO
4325 //undo_stack_tail = NULL; - already is
4326 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4327 undo_queue_state = UNDO_EMPTY;
4328 //undo_q = 0; - already is
4332 #if ENABLE_FEATURE_VI_CRASHME
4333 srand((long) getpid());
4335 #ifdef NO_SUCH_APPLET_YET
4336 // if we aren't "vi", we are "view"
4337 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4338 SET_READONLY_MODE(readonly_mode);
4342 // autoindent is not default in vim 7.3
4343 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4344 // 1- process $HOME/.exrc file (not inplemented yet)
4345 // 2- process EXINIT variable from environment
4346 // 3- process command line args
4347 #if ENABLE_FEATURE_VI_COLON
4349 char *p = getenv("EXINIT");
4351 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4354 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4356 #if ENABLE_FEATURE_VI_CRASHME
4361 #if ENABLE_FEATURE_VI_READONLY
4362 case 'R': // Read-only flag
4363 SET_READONLY_MODE(readonly_mode);
4366 #if ENABLE_FEATURE_VI_COLON
4367 case 'c': // cmd line vi command
4369 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4382 cmdline_filecnt = argc - optind;
4384 // "Save cursor, use alternate screen buffer, clear screen"
4385 write1(ESC"[?1049h");
4386 // This is the main file handling loop
4389 edit_file(argv[optind]); // might be NULL on 1st iteration
4390 // NB: optind can be changed by ":next" and ":rewind" commands
4392 if (optind >= cmdline_filecnt)
4395 // "Use normal screen buffer, restore cursor"
4396 write1(ESC"[?1049l");