1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (23 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
241 #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
243 ///* Cursor up and down */
244 //#define ESC_CURSOR_UP ESC"[A"
245 //#define ESC_CURSOR_DOWN "\n"
247 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
248 // cmds modifying text[]
249 // vda: removed "aAiIs" as they switch us into insert mode
250 // and remembering input for replay after them makes no sense
251 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
257 FORWARD = 1, // code depends on "1" for array index
258 BACK = -1, // code depends on "-1" for array index
259 LIMITED = 0, // char_search() only current line
260 FULL = 1, // char_search() to the end/beginning of entire text
262 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
263 S_TO_WS = 2, // used in skip_thing() for moving "dot"
264 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
265 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
266 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
270 /* vi.c expects chars to be unsigned. */
271 /* busybox build system provides that, but it's better */
272 /* to audit and fix the source */
275 /* many references - keep near the top of globals */
276 char *text, *end; // pointers to the user data in memory
277 char *dot; // where all the action takes place
278 int text_size; // size of the allocated buffer
282 #define VI_AUTOINDENT 1
283 #define VI_SHOWMATCH 2
284 #define VI_IGNORECASE 4
285 #define VI_ERR_METHOD 8
286 #define autoindent (vi_setops & VI_AUTOINDENT)
287 #define showmatch (vi_setops & VI_SHOWMATCH )
288 #define ignorecase (vi_setops & VI_IGNORECASE)
289 /* indicate error with beep or flash */
290 #define err_method (vi_setops & VI_ERR_METHOD)
292 #if ENABLE_FEATURE_VI_READONLY
293 smallint readonly_mode;
294 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
295 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
296 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
298 #define SET_READONLY_FILE(flags) ((void)0)
299 #define SET_READONLY_MODE(flags) ((void)0)
300 #define UNSET_READONLY_FILE(flags) ((void)0)
303 smallint editing; // >0 while we are editing a file
304 // [code audit says "can be 0, 1 or 2 only"]
305 smallint cmd_mode; // 0=command 1=insert 2=replace
306 int modified_count; // buffer contents changed if !0
307 int last_modified_count; // = -1;
308 int save_argc; // how many file names on cmd line
309 int cmdcnt; // repetition count
310 unsigned rows, columns; // the terminal screen is this size
311 #if ENABLE_FEATURE_VI_ASK_TERMINAL
312 int get_rowcol_error;
314 int crow, ccol; // cursor is on Crow x Ccol
315 int offset; // chars scrolled off the screen to the left
316 int have_status_msg; // is default edit status needed?
317 // [don't make smallint!]
318 int last_status_cksum; // hash of current status line
319 char *current_filename;
320 char *screenbegin; // index into text[], of top line on the screen
321 char *screen; // pointer to the virtual screen buffer
322 int screensize; // and its size
324 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
325 char erase_char; // the users erase character
326 char last_input_char; // last char read from user
328 #if ENABLE_FEATURE_VI_DOT_CMD
329 smallint adding2q; // are we currently adding user input to q
330 int lmc_len; // length of last_modifying_cmd
331 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
333 #if ENABLE_FEATURE_VI_SEARCH
334 char *last_search_pattern; // last pattern from a '/' or '?' search
338 #if ENABLE_FEATURE_VI_YANKMARK
339 char *edit_file__cur_line;
341 int refresh__old_offset;
342 int format_edit_status__tot;
344 /* a few references only */
345 #if ENABLE_FEATURE_VI_YANKMARK
346 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
348 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
349 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
350 char *context_start, *context_end;
352 #if ENABLE_FEATURE_VI_USE_SIGNALS
353 sigjmp_buf restart; // int_handler() jumps to location remembered here
355 struct termios term_orig; // remember what the cooked mode was
356 #if ENABLE_FEATURE_VI_COLON
357 char *initial_cmds[3]; // currently 2 entries, NULL terminated
359 // Should be just enough to hold a key sequence,
360 // but CRASHME mode uses it as generated command buffer too
361 #if ENABLE_FEATURE_VI_CRASHME
362 char readbuffer[128];
364 char readbuffer[KEYCODE_BUFFER_SIZE];
366 #define STATUS_BUFFER_LEN 200
367 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
368 #if ENABLE_FEATURE_VI_DOT_CMD
369 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
371 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
373 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
374 #if ENABLE_FEATURE_VI_UNDO
375 // undo_push() operations
378 #define UNDO_INS_CHAIN 2
379 #define UNDO_DEL_CHAIN 3
380 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
381 #define UNDO_QUEUED_FLAG 4
382 #define UNDO_INS_QUEUED 4
383 #define UNDO_DEL_QUEUED 5
384 #define UNDO_USE_SPOS 32
385 #define UNDO_EMPTY 64
386 // Pass-through flags for functions that can be undone
389 #define ALLOW_UNDO_CHAIN 2
390 # if ENABLE_FEATURE_VI_UNDO_QUEUE
391 #define ALLOW_UNDO_QUEUED 3
392 char undo_queue_state;
394 char *undo_queue_spos; // Start position of queued operation
395 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
397 // If undo queuing disabled, don't invoke the missing queue logic
398 #define ALLOW_UNDO_QUEUED 1
402 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
403 int start; // Offset where the data should be restored/deleted
404 int length; // total data size
405 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
406 char undo_text[1]; // text that was deleted (if deletion)
408 #endif /* ENABLE_FEATURE_VI_UNDO */
410 #define G (*ptr_to_globals)
411 #define text (G.text )
412 #define text_size (G.text_size )
417 #define vi_setops (G.vi_setops )
418 #define editing (G.editing )
419 #define cmd_mode (G.cmd_mode )
420 #define modified_count (G.modified_count )
421 #define last_modified_count (G.last_modified_count)
422 #define save_argc (G.save_argc )
423 #define cmdcnt (G.cmdcnt )
424 #define rows (G.rows )
425 #define columns (G.columns )
426 #define crow (G.crow )
427 #define ccol (G.ccol )
428 #define offset (G.offset )
429 #define status_buffer (G.status_buffer )
430 #define have_status_msg (G.have_status_msg )
431 #define last_status_cksum (G.last_status_cksum )
432 #define current_filename (G.current_filename )
433 #define screen (G.screen )
434 #define screensize (G.screensize )
435 #define screenbegin (G.screenbegin )
436 #define tabstop (G.tabstop )
437 #define last_forward_char (G.last_forward_char )
438 #define erase_char (G.erase_char )
439 #define last_input_char (G.last_input_char )
440 #if ENABLE_FEATURE_VI_READONLY
441 #define readonly_mode (G.readonly_mode )
443 #define readonly_mode 0
445 #define adding2q (G.adding2q )
446 #define lmc_len (G.lmc_len )
448 #define ioq_start (G.ioq_start )
449 #define last_search_pattern (G.last_search_pattern)
451 #define edit_file__cur_line (G.edit_file__cur_line)
452 #define refresh__old_offset (G.refresh__old_offset)
453 #define format_edit_status__tot (G.format_edit_status__tot)
455 #define YDreg (G.YDreg )
456 //#define Ureg (G.Ureg )
457 #define mark (G.mark )
458 #define context_start (G.context_start )
459 #define context_end (G.context_end )
460 #define restart (G.restart )
461 #define term_orig (G.term_orig )
462 #define initial_cmds (G.initial_cmds )
463 #define readbuffer (G.readbuffer )
464 #define scr_out_buf (G.scr_out_buf )
465 #define last_modifying_cmd (G.last_modifying_cmd )
466 #define get_input_line__buf (G.get_input_line__buf)
468 #if ENABLE_FEATURE_VI_UNDO
469 #define undo_stack_tail (G.undo_stack_tail )
470 # if ENABLE_FEATURE_VI_UNDO_QUEUE
471 #define undo_queue_state (G.undo_queue_state)
472 #define undo_q (G.undo_q )
473 #define undo_queue (G.undo_queue )
474 #define undo_queue_spos (G.undo_queue_spos )
478 #define INIT_G() do { \
479 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
480 last_modified_count = -1; \
481 /* "" but has space for 2 chars: */ \
482 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
486 static void show_status_line(void); // put a message on the bottom line
487 static void status_line_bold(const char *, ...);
489 #if ENABLE_FEATURE_VI_CRASHME
490 static void crash_dummy();
491 static void crash_test();
492 static int crashme = 0;
495 static void show_help(void)
497 puts("These features are available:"
498 #if ENABLE_FEATURE_VI_SEARCH
499 "\n\tPattern searches with / and ?"
501 #if ENABLE_FEATURE_VI_DOT_CMD
502 "\n\tLast command repeat with ."
504 #if ENABLE_FEATURE_VI_YANKMARK
505 "\n\tLine marking with 'x"
506 "\n\tNamed buffers with \"x"
508 #if ENABLE_FEATURE_VI_READONLY
509 //not implemented: "\n\tReadonly if vi is called as \"view\""
510 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
512 #if ENABLE_FEATURE_VI_SET
513 "\n\tSome colon mode commands with :"
515 #if ENABLE_FEATURE_VI_SETOPTS
516 "\n\tSettable options with \":set\""
518 #if ENABLE_FEATURE_VI_USE_SIGNALS
519 "\n\tSignal catching- ^C"
520 "\n\tJob suspend and resume with ^Z"
522 #if ENABLE_FEATURE_VI_WIN_RESIZE
523 "\n\tAdapt to window re-sizes"
528 static void write1(const char *out)
533 #if ENABLE_FEATURE_VI_WIN_RESIZE
534 static int query_screen_dimensions(void)
536 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
537 if (rows > MAX_SCR_ROWS)
539 if (columns > MAX_SCR_COLS)
540 columns = MAX_SCR_COLS;
544 static ALWAYS_INLINE int query_screen_dimensions(void)
550 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
551 static int mysleep(int hund)
553 struct pollfd pfd[1];
558 pfd[0].fd = STDIN_FILENO;
559 pfd[0].events = POLLIN;
560 return safe_poll(pfd, 1, hund*10) > 0;
563 //----- Set terminal attributes --------------------------------
564 static void rawmode(void)
566 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
567 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
568 erase_char = term_orig.c_cc[VERASE];
571 static void cookmode(void)
574 tcsetattr_stdin_TCSANOW(&term_orig);
577 //----- Terminal Drawing ---------------------------------------
578 // The terminal is made up of 'rows' line of 'columns' columns.
579 // classically this would be 24 x 80.
580 // screen coordinates
586 // 23,0 ... 23,79 <- status line
588 //----- Move the cursor to row x col (count from 0, not 1) -------
589 static void place_cursor(int row, int col)
591 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
593 if (row < 0) row = 0;
594 if (row >= rows) row = rows - 1;
595 if (col < 0) col = 0;
596 if (col >= columns) col = columns - 1;
598 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
602 //----- Erase from cursor to end of line -----------------------
603 static void clear_to_eol(void)
605 write1(ESC_CLEAR2EOL);
608 static void go_bottom_and_clear_to_eol(void)
610 place_cursor(rows - 1, 0);
614 //----- Start standout mode ------------------------------------
615 static void standout_start(void)
617 write1(ESC_BOLD_TEXT);
620 //----- End standout mode --------------------------------------
621 static void standout_end(void)
623 write1(ESC_NORM_TEXT);
626 //----- Text Movement Routines ---------------------------------
627 static char *begin_line(char *p) // return pointer to first char cur line
630 p = memrchr(text, '\n', p - text);
638 static char *end_line(char *p) // return pointer to NL of cur line
641 p = memchr(p, '\n', end - p - 1);
648 static char *dollar_line(char *p) // return pointer to just before NL line
651 // Try to stay off of the Newline
652 if (*p == '\n' && (p - begin_line(p)) > 0)
657 static char *prev_line(char *p) // return pointer first char prev line
659 p = begin_line(p); // goto beginning of cur line
660 if (p > text && p[-1] == '\n')
661 p--; // step to prev line
662 p = begin_line(p); // goto beginning of prev line
666 static char *next_line(char *p) // return pointer first char next line
669 if (p < end - 1 && *p == '\n')
670 p++; // step to next line
674 //----- Text Information Routines ------------------------------
675 static char *end_screen(void)
680 // find new bottom line
682 for (cnt = 0; cnt < rows - 2; cnt++)
688 // count line from start to stop
689 static int count_lines(char *start, char *stop)
694 if (stop < start) { // start and stop are backwards- reverse them
700 stop = end_line(stop);
701 while (start <= stop && start <= end - 1) {
702 start = end_line(start);
710 static char *find_line(int li) // find beginning of line #li
714 for (q = text; li > 1; li--) {
720 static int next_tabstop(int col)
722 return col + ((tabstop - 1) - (col % tabstop));
725 //----- Erase the Screen[] memory ------------------------------
726 static void screen_erase(void)
728 memset(screen, ' ', screensize); // clear new screen
731 //----- Synchronize the cursor to Dot --------------------------
732 static NOINLINE void sync_cursor(char *d, int *row, int *col)
734 char *beg_cur; // begin and end of "d" line
738 beg_cur = begin_line(d); // first char of cur line
740 if (beg_cur < screenbegin) {
741 // "d" is before top line on screen
742 // how many lines do we have to move
743 cnt = count_lines(beg_cur, screenbegin);
745 screenbegin = beg_cur;
746 if (cnt > (rows - 1) / 2) {
747 // we moved too many lines. put "dot" in middle of screen
748 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
749 screenbegin = prev_line(screenbegin);
753 char *end_scr; // begin and end of screen
754 end_scr = end_screen(); // last char of screen
755 if (beg_cur > end_scr) {
756 // "d" is after bottom line on screen
757 // how many lines do we have to move
758 cnt = count_lines(end_scr, beg_cur);
759 if (cnt > (rows - 1) / 2)
760 goto sc1; // too many lines
761 for (ro = 0; ro < cnt - 1; ro++) {
762 // move screen begin the same amount
763 screenbegin = next_line(screenbegin);
764 // now, move the end of screen
765 end_scr = next_line(end_scr);
766 end_scr = end_line(end_scr);
770 // "d" is on screen- find out which row
772 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
778 // find out what col "d" is on
780 while (tp < d) { // drive "co" to correct column
781 if (*tp == '\n') //vda || *tp == '\0')
784 // handle tabs like real vi
785 if (d == tp && cmd_mode) {
788 co = next_tabstop(co);
789 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
790 co++; // display as ^X, use 2 columns
796 // "co" is the column where "dot" is.
797 // The screen has "columns" columns.
798 // The currently displayed columns are 0+offset -- columns+ofset
799 // |-------------------------------------------------------------|
801 // offset | |------- columns ----------------|
803 // If "co" is already in this range then we do not have to adjust offset
804 // but, we do have to subtract the "offset" bias from "co".
805 // If "co" is outside this range then we have to change "offset".
806 // If the first char of a line is a tab the cursor will try to stay
807 // in column 7, but we have to set offset to 0.
809 if (co < 0 + offset) {
812 if (co >= columns + offset) {
813 offset = co - columns + 1;
815 // if the first char of the line is a tab, and "dot" is sitting on it
816 // force offset to 0.
817 if (d == beg_cur && *d == '\t') {
826 //----- Format a text[] line into a buffer ---------------------
827 static char* format_line(char *src /*, int li*/)
832 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
834 c = '~'; // char in col 0 in non-existent lines is '~'
836 while (co < columns + tabstop) {
837 // have we gone past the end?
842 if ((c & 0x80) && !Isprint(c)) {
845 if (c < ' ' || c == 0x7f) {
849 while ((co % tabstop) != (tabstop - 1)) {
857 c += '@'; // Ctrl-X -> 'X'
862 // discard scrolled-off-to-the-left portion,
863 // in tabstop-sized pieces
864 if (ofs >= tabstop && co >= tabstop) {
865 memmove(dest, dest + tabstop, co);
872 // check "short line, gigantic offset" case
875 // discard last scrolled off part
878 // fill the rest with spaces
880 memset(&dest[co], ' ', columns - co);
884 //----- Refresh the changed screen lines -----------------------
885 // Copy the source line from text[] into the buffer and note
886 // if the current screenline is different from the new buffer.
887 // If they differ then that line needs redrawing on the terminal.
889 static void refresh(int full_screen)
891 #define old_offset refresh__old_offset
894 char *tp, *sp; // pointer into text[] and screen[]
896 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
897 unsigned c = columns, r = rows;
898 query_screen_dimensions();
899 #if ENABLE_FEATURE_VI_USE_SIGNALS
900 full_screen |= (c - columns) | (r - rows);
902 if (c != columns || r != rows) {
904 // update screen memory since SIGWINCH won't have done it
905 new_screen(rows, columns);
909 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
910 tp = screenbegin; // index into text[] of top line
912 // compare text[] to screen[] and mark screen[] lines that need updating
913 for (li = 0; li < rows - 1; li++) {
914 int cs, ce; // column start & end
916 // format current text line
917 out_buf = format_line(tp /*, li*/);
919 // skip to the end of the current text[] line
921 char *t = memchr(tp, '\n', end - tp);
926 // see if there are any changes between virtual screen and out_buf
927 changed = FALSE; // assume no change
930 sp = &screen[li * columns]; // start of screen line
932 // force re-draw of every single column from 0 - columns-1
935 // compare newly formatted buffer with virtual screen
936 // look forward for first difference between buf and screen
937 for (; cs <= ce; cs++) {
938 if (out_buf[cs] != sp[cs]) {
939 changed = TRUE; // mark for redraw
944 // look backward for last difference between out_buf and screen
945 for (; ce >= cs; ce--) {
946 if (out_buf[ce] != sp[ce]) {
947 changed = TRUE; // mark for redraw
951 // now, cs is index of first diff, and ce is index of last diff
953 // if horz offset has changed, force a redraw
954 if (offset != old_offset) {
959 // make a sanity check of columns indexes
961 if (ce > columns - 1) ce = columns - 1;
962 if (cs > ce) { cs = 0; ce = columns - 1; }
963 // is there a change between virtual screen and out_buf
965 // copy changed part of buffer to virtual screen
966 memcpy(sp+cs, out_buf+cs, ce-cs+1);
967 place_cursor(li, cs);
968 // write line out to terminal
969 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
973 place_cursor(crow, ccol);
979 //----- Force refresh of all Lines -----------------------------
980 static void redraw(int full_screen)
982 // cursor to top,left; clear to the end of screen
983 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
984 screen_erase(); // erase the internal screen buffer
985 last_status_cksum = 0; // force status update
986 refresh(full_screen); // this will redraw the entire display
990 //----- Flash the screen --------------------------------------
991 static void flash(int h)
1000 static void indicate_error(void)
1002 #if ENABLE_FEATURE_VI_CRASHME
1013 //----- IO Routines --------------------------------------------
1014 static int readit(void) // read (maybe cursor) key from stdin
1020 // Wait for input. TIMEOUT = -1 makes read_key wait even
1021 // on nonblocking stdin.
1022 // Note: read_key sets errno to 0 on success.
1024 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1025 if (c == -1) { // EOF/error
1026 if (errno == EAGAIN) // paranoia
1028 go_bottom_and_clear_to_eol();
1029 cookmode(); // terminal to "cooked"
1030 bb_error_msg_and_die("can't read user input");
1035 static int get_one_char(void)
1039 #if ENABLE_FEATURE_VI_DOT_CMD
1041 // we are not adding to the q.
1042 // but, we may be reading from a q
1044 // there is no current q, read from STDIN
1045 c = readit(); // get the users input
1047 // there is a queue to get chars from first
1048 // careful with correct sign expansion!
1049 c = (unsigned char)*ioq++;
1051 // the end of the q, read from STDIN
1053 ioq_start = ioq = 0;
1054 c = readit(); // get the users input
1058 // adding STDIN chars to q
1059 c = readit(); // get the users input
1060 if (lmc_len >= MAX_INPUT_LEN - 1) {
1061 status_line_bold("last_modifying_cmd overrun");
1063 // add new char to q
1064 last_modifying_cmd[lmc_len++] = c;
1068 c = readit(); // get the users input
1069 #endif /* FEATURE_VI_DOT_CMD */
1073 // Get input line (uses "status line" area)
1074 static char *get_input_line(const char *prompt)
1076 // char [MAX_INPUT_LEN]
1077 #define buf get_input_line__buf
1082 strcpy(buf, prompt);
1083 last_status_cksum = 0; // force status update
1084 go_bottom_and_clear_to_eol();
1085 write1(prompt); // write out the :, /, or ? prompt
1088 while (i < MAX_INPUT_LEN) {
1090 if (c == '\n' || c == '\r' || c == 27)
1091 break; // this is end of input
1092 if (c == erase_char || c == 8 || c == 127) {
1093 // user wants to erase prev char
1095 write1("\b \b"); // erase char on screen
1096 if (i <= 0) // user backs up before b-o-l, exit
1098 } else if (c > 0 && c < 256) { // exclude Unicode
1099 // (TODO: need to handle Unicode)
1110 static void Hit_Return(void)
1115 write1("[Hit return to continue]");
1117 while ((c = get_one_char()) != '\n' && c != '\r')
1119 redraw(TRUE); // force redraw all
1122 //----- Draw the status line at bottom of the screen -------------
1123 // show file status on status line
1124 static int format_edit_status(void)
1126 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1128 #define tot format_edit_status__tot
1130 int cur, percent, ret, trunc_at;
1132 // modified_count is now a counter rather than a flag. this
1133 // helps reduce the amount of line counting we need to do.
1134 // (this will cause a mis-reporting of modified status
1135 // once every MAXINT editing operations.)
1137 // it would be nice to do a similar optimization here -- if
1138 // we haven't done a motion that could have changed which line
1139 // we're on, then we shouldn't have to do this count_lines()
1140 cur = count_lines(text, dot);
1142 // count_lines() is expensive.
1143 // Call it only if something was changed since last time
1145 if (modified_count != last_modified_count) {
1146 tot = cur + count_lines(dot, end - 1) - 1;
1147 last_modified_count = modified_count;
1150 // current line percent
1151 // ------------- ~~ ----------
1154 percent = (100 * cur) / tot;
1160 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1161 columns : STATUS_BUFFER_LEN-1;
1163 ret = snprintf(status_buffer, trunc_at+1,
1164 #if ENABLE_FEATURE_VI_READONLY
1165 "%c %s%s%s %d/%d %d%%",
1167 "%c %s%s %d/%d %d%%",
1169 cmd_mode_indicator[cmd_mode & 3],
1170 (current_filename != NULL ? current_filename : "No file"),
1171 #if ENABLE_FEATURE_VI_READONLY
1172 (readonly_mode ? " [Readonly]" : ""),
1174 (modified_count ? " [Modified]" : ""),
1177 if (ret >= 0 && ret < trunc_at)
1178 return ret; // it all fit
1180 return trunc_at; // had to truncate
1184 static int bufsum(char *buf, int count)
1187 char *e = buf + count;
1189 sum += (unsigned char) *buf++;
1193 static void show_status_line(void)
1195 int cnt = 0, cksum = 0;
1197 // either we already have an error or status message, or we
1199 if (!have_status_msg) {
1200 cnt = format_edit_status();
1201 cksum = bufsum(status_buffer, cnt);
1203 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1204 last_status_cksum = cksum; // remember if we have seen this line
1205 go_bottom_and_clear_to_eol();
1206 write1(status_buffer);
1207 if (have_status_msg) {
1208 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1210 have_status_msg = 0;
1213 have_status_msg = 0;
1215 place_cursor(crow, ccol); // put cursor back in correct place
1220 //----- format the status buffer, the bottom line of screen ------
1221 // format status buffer, with STANDOUT mode
1222 static void status_line_bold(const char *format, ...)
1226 va_start(args, format);
1227 strcpy(status_buffer, ESC_BOLD_TEXT);
1228 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
1229 strcat(status_buffer, ESC_NORM_TEXT);
1232 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
1235 static void status_line_bold_errno(const char *fn)
1237 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1240 // format status buffer
1241 static void status_line(const char *format, ...)
1245 va_start(args, format);
1246 vsprintf(status_buffer, format, args);
1249 have_status_msg = 1;
1252 // copy s to buf, convert unprintable
1253 static void print_literal(char *buf, const char *s)
1267 c_is_no_print = (c & 0x80) && !Isprint(c);
1268 if (c_is_no_print) {
1269 strcpy(d, ESC_NORM_TEXT);
1270 d += sizeof(ESC_NORM_TEXT)-1;
1273 if (c < ' ' || c == 0x7f) {
1281 if (c_is_no_print) {
1282 strcpy(d, ESC_BOLD_TEXT);
1283 d += sizeof(ESC_BOLD_TEXT)-1;
1289 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1294 static void not_implemented(const char *s)
1296 char buf[MAX_INPUT_LEN];
1298 print_literal(buf, s);
1299 status_line_bold("\'%s\' is not implemented", buf);
1302 #if ENABLE_FEATURE_VI_YANKMARK
1303 static char *text_yank(char *p, char *q, int dest) // copy text into a register
1306 if (cnt < 0) { // they are backwards- reverse them
1310 free(reg[dest]); // if already a yank register, free it
1311 reg[dest] = xstrndup(p, cnt + 1);
1315 static char what_reg(void)
1319 c = 'D'; // default to D-reg
1320 if (0 <= YDreg && YDreg <= 25)
1321 c = 'a' + (char) YDreg;
1329 static void check_context(char cmd)
1331 // A context is defined to be "modifying text"
1332 // Any modifying command establishes a new context.
1334 if (dot < context_start || dot > context_end) {
1335 if (strchr(modifying_cmds, cmd) != NULL) {
1336 // we are trying to modify text[]- make this the current context
1337 mark[27] = mark[26]; // move cur to prev
1338 mark[26] = dot; // move local to cur
1339 context_start = prev_line(prev_line(dot));
1340 context_end = next_line(next_line(dot));
1341 //loiter= start_loiter= now;
1346 static char *swap_context(char *p) // goto new context for '' command make this the current context
1350 // the current context is in mark[26]
1351 // the previous context is in mark[27]
1352 // only swap context if other context is valid
1353 if (text <= mark[27] && mark[27] <= end - 1) {
1357 context_start = prev_line(prev_line(prev_line(p)));
1358 context_end = next_line(next_line(next_line(p)));
1362 #endif /* FEATURE_VI_YANKMARK */
1364 #if ENABLE_FEATURE_VI_UNDO
1365 static void undo_push(char *, unsigned, unsigned char);
1368 // open a hole in text[]
1369 // might reallocate text[]! use p += text_hole_make(p, ...),
1370 // and be careful to not use pointers into potentially freed text[]!
1371 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1377 end += size; // adjust the new END
1378 if (end >= (text + text_size)) {
1380 text_size += end - (text + text_size) + 10240;
1381 new_text = xrealloc(text, text_size);
1382 bias = (new_text - text);
1383 screenbegin += bias;
1387 #if ENABLE_FEATURE_VI_YANKMARK
1390 for (i = 0; i < ARRAY_SIZE(mark); i++)
1397 memmove(p + size, p, end - size - p);
1398 memset(p, ' ', size); // clear new hole
1402 // close a hole in text[] - delete "p" through "q", inclusive
1403 // "undo" value indicates if this operation should be undo-able
1404 #if !ENABLE_FEATURE_VI_UNDO
1405 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
1407 static char *text_hole_delete(char *p, char *q, int undo)
1412 // move forwards, from beginning
1416 if (q < p) { // they are backward- swap them
1420 hole_size = q - p + 1;
1422 #if ENABLE_FEATURE_VI_UNDO
1427 undo_push(p, hole_size, UNDO_DEL);
1429 case ALLOW_UNDO_CHAIN:
1430 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1432 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1433 case ALLOW_UNDO_QUEUED:
1434 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1440 if (src < text || src > end)
1442 if (dest < text || dest >= end)
1446 goto thd_atend; // just delete the end of the buffer
1447 memmove(dest, src, cnt);
1449 end = end - hole_size; // adjust the new END
1451 dest = end - 1; // make sure dest in below end-1
1453 dest = end = text; // keep pointers valid
1458 #if ENABLE_FEATURE_VI_UNDO
1460 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1461 // Flush any queued objects to the undo stack
1462 static void undo_queue_commit(void)
1464 // Pushes the queue object onto the undo stack
1466 // Deleted character undo events grow from the end
1467 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1469 (undo_queue_state | UNDO_USE_SPOS)
1471 undo_queue_state = UNDO_EMPTY;
1476 # define undo_queue_commit() ((void)0)
1479 static void flush_undo_data(void)
1481 struct undo_object *undo_entry;
1483 while (undo_stack_tail) {
1484 undo_entry = undo_stack_tail;
1485 undo_stack_tail = undo_entry->prev;
1490 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1491 // Add to the undo stack
1492 static void undo_push(char *src, unsigned length, uint8_t u_type)
1494 struct undo_object *undo_entry;
1497 // UNDO_INS: insertion, undo will remove from buffer
1498 // UNDO_DEL: deleted text, undo will restore to buffer
1499 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1500 // The CHAIN operations are for handling multiple operations that the user
1501 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1502 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1503 // for the INS/DEL operation. The raw values should be equal to the values of
1504 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1506 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1507 // This undo queuing functionality groups multiple character typing or backspaces
1508 // into a single large undo object. This greatly reduces calls to malloc() for
1509 // single-character operations while typing and has the side benefit of letting
1510 // an undo operation remove chunks of text rather than a single character.
1512 case UNDO_EMPTY: // Just in case this ever happens...
1514 case UNDO_DEL_QUEUED:
1516 return; // Only queue single characters
1517 switch (undo_queue_state) {
1519 undo_queue_state = UNDO_DEL;
1521 undo_queue_spos = src;
1523 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1524 // If queue is full, dump it into an object
1525 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1526 undo_queue_commit();
1529 // Switch from storing inserted text to deleted text
1530 undo_queue_commit();
1531 undo_push(src, length, UNDO_DEL_QUEUED);
1535 case UNDO_INS_QUEUED:
1538 switch (undo_queue_state) {
1540 undo_queue_state = UNDO_INS;
1541 undo_queue_spos = src;
1544 undo_q++; // Don't need to save any data for insertions
1545 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1546 undo_queue_commit();
1550 // Switch from storing deleted text to inserted text
1551 undo_queue_commit();
1552 undo_push(src, length, UNDO_INS_QUEUED);
1558 // If undo queuing is disabled, ignore the queuing flag entirely
1559 u_type = u_type & ~UNDO_QUEUED_FLAG;
1562 // Allocate a new undo object
1563 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1564 // For UNDO_DEL objects, save deleted text
1565 if ((text + length) == end)
1567 // If this deletion empties text[], strip the newline. When the buffer becomes
1568 // zero-length, a newline is added back, which requires this to compensate.
1569 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1570 memcpy(undo_entry->undo_text, src, length);
1572 undo_entry = xzalloc(sizeof(*undo_entry));
1574 undo_entry->length = length;
1575 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1576 if ((u_type & UNDO_USE_SPOS) != 0) {
1577 undo_entry->start = undo_queue_spos - text; // use start position from queue
1579 undo_entry->start = src - text; // use offset from start of text buffer
1581 u_type = (u_type & ~UNDO_USE_SPOS);
1583 undo_entry->start = src - text;
1585 undo_entry->u_type = u_type;
1587 // Push it on undo stack
1588 undo_entry->prev = undo_stack_tail;
1589 undo_stack_tail = undo_entry;
1593 static void undo_push_insert(char *p, int len, int undo)
1597 undo_push(p, len, UNDO_INS);
1599 case ALLOW_UNDO_CHAIN:
1600 undo_push(p, len, UNDO_INS_CHAIN);
1602 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1603 case ALLOW_UNDO_QUEUED:
1604 undo_push(p, len, UNDO_INS_QUEUED);
1610 // Undo the last operation
1611 static void undo_pop(void)
1614 char *u_start, *u_end;
1615 struct undo_object *undo_entry;
1617 // Commit pending undo queue before popping (should be unnecessary)
1618 undo_queue_commit();
1620 undo_entry = undo_stack_tail;
1621 // Check for an empty undo stack
1623 status_line("Already at oldest change");
1627 switch (undo_entry->u_type) {
1629 case UNDO_DEL_CHAIN:
1630 // make hole and put in text that was deleted; deallocate text
1631 u_start = text + undo_entry->start;
1632 text_hole_make(u_start, undo_entry->length);
1633 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1634 status_line("Undo [%d] %s %d chars at position %d",
1635 modified_count, "restored",
1636 undo_entry->length, undo_entry->start
1640 case UNDO_INS_CHAIN:
1641 // delete what was inserted
1642 u_start = undo_entry->start + text;
1643 u_end = u_start - 1 + undo_entry->length;
1644 text_hole_delete(u_start, u_end, NO_UNDO);
1645 status_line("Undo [%d] %s %d chars at position %d",
1646 modified_count, "deleted",
1647 undo_entry->length, undo_entry->start
1652 switch (undo_entry->u_type) {
1653 // If this is the end of a chain, lower modification count and refresh display
1656 dot = (text + undo_entry->start);
1659 case UNDO_DEL_CHAIN:
1660 case UNDO_INS_CHAIN:
1664 // Deallocate the undo object we just processed
1665 undo_stack_tail = undo_entry->prev;
1668 // For chained operations, continue popping all the way down the chain.
1670 undo_pop(); // Follow the undo chain if one exists
1675 # define flush_undo_data() ((void)0)
1676 # define undo_queue_commit() ((void)0)
1677 #endif /* ENABLE_FEATURE_VI_UNDO */
1679 //----- Dot Movement Routines ----------------------------------
1680 static void dot_left(void)
1682 undo_queue_commit();
1683 if (dot > text && dot[-1] != '\n')
1687 static void dot_right(void)
1689 undo_queue_commit();
1690 if (dot < end - 1 && *dot != '\n')
1694 static void dot_begin(void)
1696 undo_queue_commit();
1697 dot = begin_line(dot); // return pointer to first char cur line
1700 static void dot_end(void)
1702 undo_queue_commit();
1703 dot = end_line(dot); // return pointer to last char cur line
1706 static char *move_to_col(char *p, int l)
1712 while (co < l && p < end) {
1713 if (*p == '\n') //vda || *p == '\0')
1716 co = next_tabstop(co);
1717 } else if (*p < ' ' || *p == 127) {
1718 co++; // display as ^X, use 2 columns
1726 static void dot_next(void)
1728 undo_queue_commit();
1729 dot = next_line(dot);
1732 static void dot_prev(void)
1734 undo_queue_commit();
1735 dot = prev_line(dot);
1738 static void dot_skip_over_ws(void)
1741 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1745 static void dot_scroll(int cnt, int dir)
1749 undo_queue_commit();
1750 for (; cnt > 0; cnt--) {
1753 // ctrl-Y scroll up one line
1754 screenbegin = prev_line(screenbegin);
1757 // ctrl-E scroll down one line
1758 screenbegin = next_line(screenbegin);
1761 // make sure "dot" stays on the screen so we dont scroll off
1762 if (dot < screenbegin)
1764 q = end_screen(); // find new bottom line
1766 dot = begin_line(q); // is dot is below bottom line?
1770 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1772 if (p >= end && end > text) {
1783 #if ENABLE_FEATURE_VI_DOT_CMD
1784 static void start_new_cmd_q(char c)
1786 // get buffer for new cmd
1787 // if there is a current cmd count put it in the buffer first
1789 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1790 } else { // just save char c onto queue
1791 last_modifying_cmd[0] = c;
1796 static void end_cmd_q(void)
1798 # if ENABLE_FEATURE_VI_YANKMARK
1799 YDreg = 26; // go back to default Yank/Delete reg
1804 # define end_cmd_q() ((void)0)
1805 #endif /* FEATURE_VI_DOT_CMD */
1807 // copy text into register, then delete text.
1808 // if dist <= 0, do not include, or go past, a NewLine
1810 #if !ENABLE_FEATURE_VI_UNDO
1811 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1813 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1817 // make sure start <= stop
1819 // they are backwards, reverse them
1825 // we cannot cross NL boundaries
1829 // dont go past a NewLine
1830 for (; p + 1 <= stop; p++) {
1832 stop = p; // "stop" just before NewLine
1838 #if ENABLE_FEATURE_VI_YANKMARK
1839 text_yank(start, stop, YDreg);
1841 if (yf == YANKDEL) {
1842 p = text_hole_delete(start, stop, undo);
1847 // might reallocate text[]!
1848 static int file_insert(const char *fn, char *p, int initial)
1852 struct stat statbuf;
1859 fd = open(fn, O_RDONLY);
1862 status_line_bold_errno(fn);
1867 if (fstat(fd, &statbuf) < 0) {
1868 status_line_bold_errno(fn);
1871 if (!S_ISREG(statbuf.st_mode)) {
1872 status_line_bold("'%s' is not a regular file", fn);
1875 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1876 p += text_hole_make(p, size);
1877 cnt = full_read(fd, p, size);
1879 status_line_bold_errno(fn);
1880 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1881 } else if (cnt < size) {
1882 // There was a partial read, shrink unused space
1883 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1884 status_line_bold("can't read '%s'", fn);
1889 #if ENABLE_FEATURE_VI_READONLY
1891 && ((access(fn, W_OK) < 0) ||
1892 // root will always have access()
1893 // so we check fileperms too
1894 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1897 SET_READONLY_FILE(readonly_mode);
1903 // find matching char of pair () [] {}
1904 // will crash if c is not one of these
1905 static char *find_pair(char *p, const char c)
1907 const char *braces = "()[]{}";
1911 dir = strchr(braces, c) - braces;
1913 match = braces[dir];
1914 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1916 // look for match, count levels of pairs (( ))
1920 if (p < text || p >= end)
1923 level++; // increase pair levels
1925 level--; // reduce pair level
1927 return p; // found matching pair
1932 #if ENABLE_FEATURE_VI_SETOPTS
1933 // show the matching char of a pair, () [] {}
1934 static void showmatching(char *p)
1938 // we found half of a pair
1939 q = find_pair(p, *p); // get loc of matching char
1941 indicate_error(); // no matching char
1943 // "q" now points to matching pair
1944 save_dot = dot; // remember where we are
1945 dot = q; // go to new loc
1946 refresh(FALSE); // let the user see it
1947 mysleep(40); // give user some time
1948 dot = save_dot; // go back to old loc
1952 #endif /* FEATURE_VI_SETOPTS */
1954 // might reallocate text[]! use p += stupid_insert(p, ...),
1955 // and be careful to not use pointers into potentially freed text[]!
1956 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1959 bias = text_hole_make(p, 1);
1965 #if !ENABLE_FEATURE_VI_UNDO
1966 #define char_insert(a,b,c) char_insert(a,b)
1968 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1970 if (c == 22) { // Is this an ctrl-V?
1971 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1972 refresh(FALSE); // show the ^
1975 #if ENABLE_FEATURE_VI_UNDO
1976 undo_push_insert(p, 1, undo);
1979 #endif /* ENABLE_FEATURE_VI_UNDO */
1981 } else if (c == 27) { // Is this an ESC?
1983 undo_queue_commit();
1985 end_cmd_q(); // stop adding to q
1986 last_status_cksum = 0; // force status update
1987 if ((p[-1] != '\n') && (dot > text)) {
1990 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1993 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1996 // insert a char into text[]
1998 c = '\n'; // translate \r to \n
1999 #if ENABLE_FEATURE_VI_UNDO
2000 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2002 undo_queue_commit();
2004 undo_push_insert(p, 1, undo);
2007 #endif /* ENABLE_FEATURE_VI_UNDO */
2008 p += 1 + stupid_insert(p, c); // insert the char
2009 #if ENABLE_FEATURE_VI_SETOPTS
2010 if (showmatch && strchr(")]}", c) != NULL) {
2011 showmatching(p - 1);
2013 if (autoindent && c == '\n') { // auto indent the new line
2016 q = prev_line(p); // use prev line as template
2017 len = strspn(q, " \t"); // space or tab
2020 bias = text_hole_make(p, len);
2023 #if ENABLE_FEATURE_VI_UNDO
2024 undo_push_insert(p, len, undo);
2035 // read text from file or create an empty buf
2036 // will also update current_filename
2037 static int init_text_buffer(char *fn)
2041 // allocate/reallocate text buffer
2044 screenbegin = dot = end = text = xzalloc(text_size);
2046 if (fn != current_filename) {
2047 free(current_filename);
2048 current_filename = xstrdup(fn);
2050 rc = file_insert(fn, text, 1);
2052 // file doesnt exist. Start empty buf with dummy line
2053 char_insert(text, '\n', NO_UNDO);
2058 last_modified_count = -1;
2059 #if ENABLE_FEATURE_VI_YANKMARK
2061 memset(mark, 0, sizeof(mark));
2066 #if ENABLE_FEATURE_VI_YANKMARK \
2067 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2068 || ENABLE_FEATURE_VI_CRASHME
2069 // might reallocate text[]! use p += string_insert(p, ...),
2070 // and be careful to not use pointers into potentially freed text[]!
2071 # if !ENABLE_FEATURE_VI_UNDO
2072 # define string_insert(a,b,c) string_insert(a,b)
2074 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2080 #if ENABLE_FEATURE_VI_UNDO
2081 undo_push_insert(p, i, undo);
2083 bias = text_hole_make(p, i);
2086 #if ENABLE_FEATURE_VI_YANKMARK
2089 for (cnt = 0; *s != '\0'; s++) {
2093 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2100 static int file_write(char *fn, char *first, char *last)
2102 int fd, cnt, charcnt;
2105 status_line_bold("No current filename");
2108 // By popular request we do not open file with O_TRUNC,
2109 // but instead ftruncate() it _after_ successful write.
2110 // Might reduce amount of data lost on power fail etc.
2111 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2114 cnt = last - first + 1;
2115 charcnt = full_write(fd, first, cnt);
2116 ftruncate(fd, charcnt);
2117 if (charcnt == cnt) {
2119 //modified_count = FALSE;
2127 #if ENABLE_FEATURE_VI_SEARCH
2128 # if ENABLE_FEATURE_VI_REGEX_SEARCH
2129 // search for pattern starting at p
2130 static char *char_search(char *p, const char *pat, int dir_and_range)
2132 struct re_pattern_buffer preg;
2139 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2141 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2143 memset(&preg, 0, sizeof(preg));
2144 err = re_compile_pattern(pat, strlen(pat), &preg);
2146 status_line_bold("bad search pattern '%s': %s", pat, err);
2150 range = (dir_and_range & 1);
2151 q = end - 1; // if FULL
2152 if (range == LIMITED)
2154 if (dir_and_range < 0) { // BACK?
2156 if (range == LIMITED)
2160 // RANGE could be negative if we are searching backwards
2170 // search for the compiled pattern, preg, in p[]
2171 // range < 0: search backward
2172 // range > 0: search forward
2174 // re_search() < 0: not found or error
2175 // re_search() >= 0: index of found pattern
2176 // struct pattern char int int int struct reg
2177 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2178 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2182 if (dir_and_range > 0) // FORWARD?
2189 # if ENABLE_FEATURE_VI_SETOPTS
2190 static int mycmp(const char *s1, const char *s2, int len)
2193 return strncasecmp(s1, s2, len);
2195 return strncmp(s1, s2, len);
2198 # define mycmp strncmp
2200 static char *char_search(char *p, const char *pat, int dir_and_range)
2207 range = (dir_and_range & 1);
2208 if (dir_and_range > 0) { //FORWARD?
2209 stop = end - 1; // assume range is p..end-1
2210 if (range == LIMITED)
2211 stop = next_line(p); // range is to next line
2212 for (start = p; start < stop; start++) {
2213 if (mycmp(start, pat, len) == 0) {
2218 stop = text; // assume range is text..p
2219 if (range == LIMITED)
2220 stop = prev_line(p); // range is to prev line
2221 for (start = p - len; start >= stop; start--) {
2222 if (mycmp(start, pat, len) == 0) {
2227 // pattern not found
2231 #endif /* FEATURE_VI_SEARCH */
2233 //----- The Colon commands -------------------------------------
2234 #if ENABLE_FEATURE_VI_COLON
2235 static char *get_one_address(char *p, int *addr) // get colon addr, if present
2239 IF_FEATURE_VI_YANKMARK(char c;)
2240 IF_FEATURE_VI_SEARCH(char *pat;)
2242 *addr = -1; // assume no addr
2243 if (*p == '.') { // the current line
2245 q = begin_line(dot);
2246 *addr = count_lines(text, q);
2248 #if ENABLE_FEATURE_VI_YANKMARK
2249 else if (*p == '\'') { // is this a mark addr
2253 if (c >= 'a' && c <= 'z') {
2256 q = mark[(unsigned char) c];
2257 if (q != NULL) { // is mark valid
2258 *addr = count_lines(text, q);
2263 #if ENABLE_FEATURE_VI_SEARCH
2264 else if (*p == '/') { // a search pattern
2265 q = strchrnul(++p, '/');
2266 pat = xstrndup(p, q - p); // save copy of pattern
2270 q = char_search(dot, pat, (FORWARD << 1) | FULL);
2272 *addr = count_lines(text, q);
2277 else if (*p == '$') { // the last line in file
2279 q = begin_line(end - 1);
2280 *addr = count_lines(text, q);
2281 } else if (isdigit(*p)) { // specific line number
2282 sscanf(p, "%d%n", addr, &st);
2285 // unrecognized address - assume -1
2291 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
2293 //----- get the address' i.e., 1,3 'a,'b -----
2294 // get FIRST addr, if present
2296 p++; // skip over leading spaces
2297 if (*p == '%') { // alias for 1,$
2300 *e = count_lines(text, end-1);
2303 p = get_one_address(p, b);
2306 if (*p == ',') { // is there a address separator
2310 // get SECOND addr, if present
2311 p = get_one_address(p, e);
2315 p++; // skip over trailing spaces
2319 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2320 static void setops(const char *args, const char *opname, int flg_no,
2321 const char *short_opname, int opt)
2323 const char *a = args + flg_no;
2324 int l = strlen(opname) - 1; // opname have + ' '
2326 // maybe strncmp? we had tons of erroneous strncasecmp's...
2327 if (strncasecmp(a, opname, l) == 0
2328 || strncasecmp(a, short_opname, 2) == 0
2338 #endif /* FEATURE_VI_COLON */
2340 // buf must be no longer than MAX_INPUT_LEN!
2341 static void colon(char *buf)
2343 #if !ENABLE_FEATURE_VI_COLON
2344 // Simple ":cmd" handler with minimal set of commands
2353 if (strncmp(p, "quit", cnt) == 0
2354 || strncmp(p, "q!", cnt) == 0
2356 if (modified_count && p[1] != '!') {
2357 status_line_bold("No write since last change (:%s! overrides)", p);
2363 if (strncmp(p, "write", cnt) == 0
2364 || strncmp(p, "wq", cnt) == 0
2365 || strncmp(p, "wn", cnt) == 0
2366 || (p[0] == 'x' && !p[1])
2368 if (modified_count != 0 || p[0] != 'x') {
2369 cnt = file_write(current_filename, text, end - 1);
2373 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2376 last_modified_count = -1;
2377 status_line("'%s' %dL, %dC",
2379 count_lines(text, end - 1), cnt
2382 || p[1] == 'q' || p[1] == 'n'
2383 || p[1] == 'Q' || p[1] == 'N'
2390 if (strncmp(p, "file", cnt) == 0) {
2391 last_status_cksum = 0; // force status update
2394 if (sscanf(p, "%d", &cnt) > 0) {
2395 dot = find_line(cnt);
2402 char c, *buf1, *q, *r;
2403 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2406 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2410 // :3154 // if (-e line 3154) goto it else stay put
2411 // :4,33w! foo // write a portion of buffer to file "foo"
2412 // :w // write all of buffer to current file
2414 // :q! // quit- dont care about modified file
2415 // :'a,'z!sort -u // filter block through sort
2416 // :'f // goto mark "f"
2417 // :'fl // list literal the mark "f" line
2418 // :.r bar // read file "bar" into buffer before dot
2419 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2420 // :/xyz/ // goto the "xyz" line
2421 // :s/find/replace/ // substitute pattern "find" with "replace"
2422 // :!<cmd> // run <cmd> then return
2428 buf++; // move past the ':'
2432 q = text; // assume 1,$ for the range
2434 li = count_lines(text, end - 1);
2435 fn = current_filename;
2437 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2438 buf = get_address(buf, &b, &e);
2440 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2441 // remember orig command line
2445 // get the COMMAND into cmd[]
2447 while (*buf != '\0') {
2453 // get any ARGuments
2454 while (isblank(*buf))
2458 buf1 = last_char_is(cmd, '!');
2461 *buf1 = '\0'; // get rid of !
2464 // if there is only one addr, then the addr
2465 // is the line number of the single line the
2466 // user wants. So, reset the end
2467 // pointer to point at end of the "b" line
2468 q = find_line(b); // what line is #b
2473 // we were given two addrs. change the
2474 // end pointer to the addr given by user.
2475 r = find_line(e); // what line is #e
2479 // ------------ now look for the command ------------
2481 if (i == 0) { // :123CR goto line #123
2483 dot = find_line(b); // what line is #b
2487 # if ENABLE_FEATURE_ALLOW_EXEC
2488 else if (cmd[0] == '!') { // run a cmd
2490 // :!ls run the <cmd>
2491 go_bottom_and_clear_to_eol();
2493 retcode = system(orig_buf + 1); // run the cmd
2495 printf("\nshell returned %i\n\n", retcode);
2497 Hit_Return(); // let user see results
2500 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
2501 if (b < 0) { // no addr given- use defaults
2502 b = e = count_lines(text, dot);
2504 status_line("%d", b);
2505 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
2506 if (b < 0) { // no addr given- use defaults
2507 q = begin_line(dot); // assume .,. for the range
2510 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
2512 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
2515 // don't edit, if the current file has been modified
2516 if (modified_count && !useforce) {
2517 status_line_bold("No write since last change (:%s! overrides)", cmd);
2521 // the user supplied a file name
2523 } else if (current_filename && current_filename[0]) {
2524 // no user supplied name- use the current filename
2525 // fn = current_filename; was set by default
2527 // no user file name, no current name- punt
2528 status_line_bold("No current filename");
2532 size = init_text_buffer(fn);
2534 # if ENABLE_FEATURE_VI_YANKMARK
2535 if (Ureg >= 0 && Ureg < 28) {
2536 free(reg[Ureg]); // free orig line reg- for 'U'
2539 if (YDreg >= 0 && YDreg < 28) {
2540 free(reg[YDreg]); // free default yank/delete register
2544 // how many lines in text[]?
2545 li = count_lines(text, end - 1);
2546 status_line("'%s'%s"
2547 IF_FEATURE_VI_READONLY("%s")
2550 (size < 0 ? " [New file]" : ""),
2551 IF_FEATURE_VI_READONLY(
2552 ((readonly_mode) ? " [Readonly]" : ""),
2554 li, (int)(end - text)
2556 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
2557 if (b != -1 || e != -1) {
2558 status_line_bold("No address allowed on this command");
2562 // user wants a new filename
2563 free(current_filename);
2564 current_filename = xstrdup(args);
2566 // user wants file status info
2567 last_status_cksum = 0; // force status update
2569 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
2570 // print out values of all features
2571 go_bottom_and_clear_to_eol();
2576 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
2577 if (b < 0) { // no addr given- use defaults
2578 q = begin_line(dot); // assume .,. for the range
2581 go_bottom_and_clear_to_eol();
2583 for (; q <= r; q++) {
2587 c_is_no_print = (c & 0x80) && !Isprint(c);
2588 if (c_is_no_print) {
2594 } else if (c < ' ' || c == 127) {
2606 } else if (strncmp(cmd, "quit", i) == 0 // quit
2607 || strncmp(cmd, "next", i) == 0 // edit next file
2608 || strncmp(cmd, "prev", i) == 0 // edit previous file
2613 // force end of argv list
2619 // don't exit if the file been modified
2620 if (modified_count) {
2621 status_line_bold("No write since last change (:%s! overrides)", cmd);
2624 // are there other file to edit
2625 n = save_argc - optind - 1;
2626 if (*cmd == 'q' && n > 0) {
2627 status_line_bold("%d more file(s) to edit", n);
2630 if (*cmd == 'n' && n <= 0) {
2631 status_line_bold("No more files to edit");
2635 // are there previous files to edit
2637 status_line_bold("No previous files to edit");
2643 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
2648 status_line_bold("No filename given");
2651 if (b < 0) { // no addr given- use defaults
2652 q = begin_line(dot); // assume "dot"
2654 // read after current line- unless user said ":0r foo"
2657 // read after last line
2661 { // dance around potentially-reallocated text[]
2662 uintptr_t ofs = q - text;
2663 size = file_insert(fn, q, 0);
2667 goto ret; // nothing was inserted
2668 // how many lines in text[]?
2669 li = count_lines(q, q + size - 1);
2671 IF_FEATURE_VI_READONLY("%s")
2674 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2678 // if the insert is before "dot" then we need to update
2682 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
2683 if (modified_count && !useforce) {
2684 status_line_bold("No write since last change (:%s! overrides)", cmd);
2686 // reset the filenames to edit
2687 optind = -1; // start from 0th file
2690 # if ENABLE_FEATURE_VI_SET
2691 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
2692 # if ENABLE_FEATURE_VI_SETOPTS
2695 i = 0; // offset into args
2696 // only blank is regarded as args delimiter. What about tab '\t'?
2697 if (!args[0] || strcasecmp(args, "all") == 0) {
2698 // print out values of all options
2699 # if ENABLE_FEATURE_VI_SETOPTS
2706 autoindent ? "" : "no",
2707 err_method ? "" : "no",
2708 ignorecase ? "" : "no",
2709 showmatch ? "" : "no",
2715 # if ENABLE_FEATURE_VI_SETOPTS
2718 if (strncmp(argp, "no", 2) == 0)
2719 i = 2; // ":set noautoindent"
2720 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2721 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
2722 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2723 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2724 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2726 sscanf(argp + i+8, "%u", &t);
2727 if (t > 0 && t <= MAX_TABSTOP)
2730 argp = skip_non_whitespace(argp);
2731 argp = skip_whitespace(argp);
2733 # endif /* FEATURE_VI_SETOPTS */
2734 # endif /* FEATURE_VI_SET */
2736 # if ENABLE_FEATURE_VI_SEARCH
2737 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
2738 char *F, *R, *flags;
2739 size_t len_F, len_R;
2740 int gflag; // global replace flag
2741 # if ENABLE_FEATURE_VI_UNDO
2742 int dont_chain_first_item = ALLOW_UNDO;
2745 // F points to the "find" pattern
2746 // R points to the "replace" pattern
2747 // replace the cmd line delimiters "/" with NULs
2748 c = orig_buf[1]; // what is the delimiter
2749 F = orig_buf + 2; // start of "find"
2750 R = strchr(F, c); // middle delimiter
2754 *R++ = '\0'; // terminate "find"
2755 flags = strchr(R, c);
2759 *flags++ = '\0'; // terminate "replace"
2763 if (b < 0) { // maybe :s/foo/bar/
2764 q = begin_line(dot); // start with cur line
2765 b = count_lines(text, q); // cur line number
2768 e = b; // maybe :.s/foo/bar/
2770 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2771 char *ls = q; // orig line start
2774 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
2777 // we found the "find" pattern - delete it
2778 // For undo support, the first item should not be chained
2779 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2780 # if ENABLE_FEATURE_VI_UNDO
2781 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2783 // insert the "replace" patern
2784 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2787 /*q += bias; - recalculated anyway */
2788 // check for "global" :s/foo/bar/g
2790 if ((found + len_R) < end_line(ls)) {
2792 goto vc4; // don't let q move past cur line
2798 # endif /* FEATURE_VI_SEARCH */
2799 } else if (strncmp(cmd, "version", i) == 0) { // show software version
2800 status_line(BB_VER);
2801 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2802 || strncmp(cmd, "wq", i) == 0
2803 || strncmp(cmd, "wn", i) == 0
2804 || (cmd[0] == 'x' && !cmd[1])
2807 //int forced = FALSE;
2809 // is there a file name to write to?
2813 # if ENABLE_FEATURE_VI_READONLY
2814 if (readonly_mode && !useforce) {
2815 status_line_bold("'%s' is read only", fn);
2820 // if "fn" is not write-able, chmod u+w
2821 // sprintf(syscmd, "chmod u+w %s", fn);
2825 if (modified_count != 0 || cmd[0] != 'x') {
2827 l = file_write(fn, q, r);
2832 //if (useforce && forced) {
2834 // sprintf(syscmd, "chmod u-w %s", fn);
2840 status_line_bold_errno(fn);
2842 // how many lines written
2843 li = count_lines(q, q + l - 1);
2844 status_line("'%s' %dL, %dC", fn, li, l);
2846 if (q == text && q + l == end) {
2848 last_modified_count = -1;
2851 || cmd[1] == 'q' || cmd[1] == 'n'
2852 || cmd[1] == 'Q' || cmd[1] == 'N'
2858 # if ENABLE_FEATURE_VI_YANKMARK
2859 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
2860 if (b < 0) { // no addr given- use defaults
2861 q = begin_line(dot); // assume .,. for the range
2864 text_yank(q, r, YDreg);
2865 li = count_lines(q, r);
2866 status_line("Yank %d lines (%d chars) into [%c]",
2867 li, strlen(reg[YDreg]), what_reg());
2871 not_implemented(cmd);
2874 dot = bound_dot(dot); // make sure "dot" is valid
2876 # if ENABLE_FEATURE_VI_SEARCH
2878 status_line(":s expression missing delimiters");
2880 #endif /* FEATURE_VI_COLON */
2883 //----- Helper Utility Routines --------------------------------
2885 //----------------------------------------------------------------
2886 //----- Char Routines --------------------------------------------
2887 /* Chars that are part of a word-
2888 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2889 * Chars that are Not part of a word (stoppers)
2890 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2891 * Chars that are WhiteSpace
2892 * TAB NEWLINE VT FF RETURN SPACE
2893 * DO NOT COUNT NEWLINE AS WHITESPACE
2896 static char *new_screen(int ro, int co)
2901 screensize = ro * co + 8;
2902 screen = xmalloc(screensize);
2903 // initialize the new screen. assume this will be a empty file.
2905 // non-existent text[] lines start with a tilde (~).
2906 for (li = 1; li < ro - 1; li++) {
2907 screen[(li * co) + 0] = '~';
2912 static int st_test(char *p, int type, int dir, char *tested)
2922 if (type == S_BEFORE_WS) {
2924 test = (!isspace(c) || c == '\n');
2926 if (type == S_TO_WS) {
2928 test = (!isspace(c) || c == '\n');
2930 if (type == S_OVER_WS) {
2934 if (type == S_END_PUNCT) {
2938 if (type == S_END_ALNUM) {
2940 test = (isalnum(c) || c == '_');
2946 static char *skip_thing(char *p, int linecnt, int dir, int type)
2950 while (st_test(p, type, dir, &c)) {
2951 // make sure we limit search to correct number of lines
2952 if (c == '\n' && --linecnt < 1)
2954 if (dir >= 0 && p >= end - 1)
2956 if (dir < 0 && p <= text)
2958 p += dir; // move to next char
2963 #if ENABLE_FEATURE_VI_USE_SIGNALS
2964 static void winch_handler(int sig UNUSED_PARAM)
2966 int save_errno = errno;
2967 // FIXME: do it in main loop!!!
2968 signal(SIGWINCH, winch_handler);
2969 query_screen_dimensions();
2970 new_screen(rows, columns); // get memory for virtual screen
2971 redraw(TRUE); // re-draw the screen
2974 static void tstp_handler(int sig UNUSED_PARAM)
2976 int save_errno = errno;
2978 // ioctl inside cookmode() was seen to generate SIGTTOU,
2979 // stopping us too early. Prevent that:
2980 signal(SIGTTOU, SIG_IGN);
2982 go_bottom_and_clear_to_eol();
2983 cookmode(); // terminal to "cooked"
2986 //signal(SIGTSTP, SIG_DFL);
2988 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2989 //signal(SIGTSTP, tstp_handler);
2991 // we have been "continued" with SIGCONT, restore screen and termios
2992 rawmode(); // terminal to "raw"
2993 last_status_cksum = 0; // force status update
2994 redraw(TRUE); // re-draw the screen
2998 static void int_handler(int sig)
3000 signal(SIGINT, int_handler);
3001 siglongjmp(restart, sig);
3003 #endif /* FEATURE_VI_USE_SIGNALS */
3005 static void do_cmd(int c);
3007 static int find_range(char **start, char **stop, char c)
3009 char *save_dot, *p, *q, *t;
3010 int cnt, multiline = 0;
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)) {
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)) {
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 insrting
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 last_modifying_cmd[lmc_len] = 0;
3425 ioq = ioq_start = xstrdup(last_modifying_cmd);
3429 #if ENABLE_FEATURE_VI_SEARCH
3430 case '?': // /- search for a pattern
3431 case '/': // /- search for a pattern
3434 q = get_input_line(buf); // get input line- use "status line"
3435 if (q[0] && !q[1]) {
3436 if (last_search_pattern[0])
3437 last_search_pattern[0] = c;
3438 goto dc3; // if no pat re-use old pat
3440 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3441 // there is a new pat
3442 free(last_search_pattern);
3443 last_search_pattern = xstrdup(q);
3444 goto dc3; // now find the pattern
3446 // user changed mind and erased the "/"- do nothing
3448 case 'N': // N- backward search for last pattern
3449 dir = BACK; // assume BACKWARD search
3451 if (last_search_pattern[0] == '?') {
3455 goto dc4; // now search for pattern
3457 case 'n': // n- repeat search for last pattern
3458 // search rest of text[] starting at next char
3459 // if search fails return orignal "p" not the "p+1" address
3463 dir = FORWARD; // assume FORWARD search
3465 if (last_search_pattern[0] == '?') {
3470 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3472 dot = q; // good search, update "dot"
3476 // no pattern found between "dot" and "end"- continue at top
3481 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3482 if (q != NULL) { // found something
3483 dot = q; // found new pattern- goto it
3484 msg = "search hit BOTTOM, continuing at TOP";
3486 msg = "search hit TOP, continuing at BOTTOM";
3489 msg = "Pattern not found";
3493 status_line_bold("%s", msg);
3494 } while (--cmdcnt > 0);
3496 case '{': // {- move backward paragraph
3497 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3498 if (q != NULL) { // found blank line
3499 dot = next_line(q); // move to next blank line
3502 case '}': // }- move forward paragraph
3503 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3504 if (q != NULL) { // found blank line
3505 dot = next_line(q); // move to next blank line
3508 #endif /* FEATURE_VI_SEARCH */
3509 case '0': // 0- goto beginning of line
3519 if (c == '0' && cmdcnt < 1) {
3520 dot_begin(); // this was a standalone zero
3522 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3525 case ':': // :- the colon mode commands
3526 p = get_input_line(":"); // get input line- use "status line"
3527 colon(p); // execute the command
3529 case '<': // <- Left shift something
3530 case '>': // >- Right shift something
3531 cnt = count_lines(text, dot); // remember what line we are on
3532 c1 = get_one_char(); // get the type of thing to delete
3533 find_range(&p, &q, c1);
3534 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3537 i = count_lines(p, q); // # of lines we are shifting
3538 for ( ; i > 0; i--, p = next_line(p)) {
3540 // shift left- remove tab or 8 spaces
3542 // shrink buffer 1 char
3543 text_hole_delete(p, p, NO_UNDO);
3544 } else if (*p == ' ') {
3545 // we should be calculating columns, not just SPACE
3546 for (j = 0; *p == ' ' && j < tabstop; j++) {
3547 text_hole_delete(p, p, NO_UNDO);
3550 } else if (c == '>') {
3551 // shift right -- add tab or 8 spaces
3552 char_insert(p, '\t', ALLOW_UNDO);
3555 dot = find_line(cnt); // what line were we on
3557 end_cmd_q(); // stop adding to q
3559 case 'A': // A- append at e-o-l
3560 dot_end(); // go to e-o-l
3561 //**** fall through to ... 'a'
3562 case 'a': // a- append after current char
3567 case 'B': // B- back a blank-delimited Word
3568 case 'E': // E- end of a blank-delimited word
3569 case 'W': // W- forward a blank-delimited word
3574 if (c == 'W' || isspace(dot[dir])) {
3575 dot = skip_thing(dot, 1, dir, S_TO_WS);
3576 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3579 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3580 } while (--cmdcnt > 0);
3582 case 'C': // C- Change to e-o-l
3583 case 'D': // D- delete to e-o-l
3585 dot = dollar_line(dot); // move to before NL
3586 // copy text into a register and delete
3587 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3589 goto dc_i; // start inserting
3590 #if ENABLE_FEATURE_VI_DOT_CMD
3592 end_cmd_q(); // stop adding to q
3595 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3596 c1 = get_one_char();
3599 // c1 < 0 if the key was special. Try "g<up-arrow>"
3600 // TODO: if Unicode?
3601 buf[1] = (c1 >= 0 ? c1 : '*');
3603 not_implemented(buf);
3609 case 'G': // G- goto to a line number (default= E-O-F)
3610 dot = end - 1; // assume E-O-F
3612 dot = find_line(cmdcnt); // what line is #cmdcnt
3616 case 'H': // H- goto top line on screen
3618 if (cmdcnt > (rows - 1)) {
3619 cmdcnt = (rows - 1);
3626 case 'I': // I- insert before first non-blank
3629 //**** fall through to ... 'i'
3630 case 'i': // i- insert before current char
3631 case KEYCODE_INSERT: // Cursor Key Insert
3633 cmd_mode = 1; // start inserting
3634 undo_queue_commit(); // commit queue when cmd_mode changes
3636 case 'J': // J- join current and next lines together
3638 dot_end(); // move to NL
3639 if (dot < end - 1) { // make sure not last char in text[]
3640 #if ENABLE_FEATURE_VI_UNDO
3641 undo_push(dot, 1, UNDO_DEL);
3642 *dot++ = ' '; // replace NL with space
3643 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3648 while (isblank(*dot)) { // delete leading WS
3649 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3652 } while (--cmdcnt > 0);
3653 end_cmd_q(); // stop adding to q
3655 case 'L': // L- goto bottom line on screen
3657 if (cmdcnt > (rows - 1)) {
3658 cmdcnt = (rows - 1);
3666 case 'M': // M- goto middle line on screen
3668 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3669 dot = next_line(dot);
3671 case 'O': // O- open a empty line above
3673 p = begin_line(dot);
3674 if (p[-1] == '\n') {
3676 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3678 dot = char_insert(dot, '\n', ALLOW_UNDO);
3681 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3686 case 'R': // R- continuous Replace char
3689 undo_queue_commit();
3691 case KEYCODE_DELETE:
3693 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3695 case 'X': // X- delete char before dot
3696 case 'x': // x- delete the current char
3697 case 's': // s- substitute the current char
3702 if (dot[dir] != '\n') {
3704 dot--; // delete prev char
3705 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3707 } while (--cmdcnt > 0);
3708 end_cmd_q(); // stop adding to q
3710 goto dc_i; // start inserting
3712 case 'Z': // Z- if modified, {write}; exit
3713 // ZZ means to save file (if necessary), then exit
3714 c1 = get_one_char();
3719 if (modified_count) {
3720 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3721 status_line_bold("'%s' is read only", current_filename);
3724 cnt = file_write(current_filename, text, end - 1);
3727 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3728 } else if (cnt == (end - 1 - text + 1)) {
3735 case '^': // ^- move to first non-blank on line
3739 case 'b': // b- back a word
3740 case 'e': // e- end of word
3745 if ((dot + dir) < text || (dot + dir) > end - 1)
3748 if (isspace(*dot)) {
3749 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3751 if (isalnum(*dot) || *dot == '_') {
3752 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3753 } else if (ispunct(*dot)) {
3754 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3756 } while (--cmdcnt > 0);
3758 case 'c': // c- change something
3759 case 'd': // d- delete something
3760 #if ENABLE_FEATURE_VI_YANKMARK
3761 case 'y': // y- yank something
3762 case 'Y': // Y- Yank a line
3765 int yf, ml, whole = 0;
3766 yf = YANKDEL; // assume either "c" or "d"
3767 #if ENABLE_FEATURE_VI_YANKMARK
3768 if (c == 'y' || c == 'Y')
3773 c1 = get_one_char(); // get the type of thing to delete
3774 // determine range, and whether it spans lines
3775 ml = find_range(&p, &q, c1);
3777 if (c1 == 27) { // ESC- user changed mind and wants out
3778 c = c1 = 27; // Escape- do nothing
3779 } else if (strchr("wW", c1)) {
3781 // don't include trailing WS as part of word
3782 while (isblank(*q)) {
3783 if (q <= text || q[-1] == '\n')
3788 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3789 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3790 // partial line copy text into a register and delete
3791 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3792 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3793 // whole line copy text into a register and delete
3794 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3797 // could not recognize object
3798 c = c1 = 27; // error-
3804 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3805 // on the last line of file don't move to prev line
3806 if (whole && dot != (end-1)) {
3809 } else if (c == 'd') {
3815 // if CHANGING, not deleting, start inserting after the delete
3817 strcpy(buf, "Change");
3818 goto dc_i; // start inserting
3821 strcpy(buf, "Delete");
3823 #if ENABLE_FEATURE_VI_YANKMARK
3824 if (c == 'y' || c == 'Y') {
3825 strcpy(buf, "Yank");
3829 for (cnt = 0; p <= q; p++) {
3833 status_line("%s %u lines (%u chars) using [%c]",
3834 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3836 end_cmd_q(); // stop adding to q
3840 case 'k': // k- goto prev line, same col
3841 case KEYCODE_UP: // cursor key Up
3844 dot = move_to_col(dot, ccol + offset); // try stay in same col
3845 } while (--cmdcnt > 0);
3847 case 'r': // r- replace the current char with user input
3848 c1 = get_one_char(); // get the replacement char
3850 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3851 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3854 end_cmd_q(); // stop adding to q
3856 case 't': // t- move to char prior to next x
3857 last_forward_char = get_one_char();
3859 if (*dot == last_forward_char)
3861 last_forward_char = 0;
3863 case 'w': // w- forward a word
3865 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3866 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3867 } else if (ispunct(*dot)) { // we are on PUNCT
3868 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3871 dot++; // move over word
3872 if (isspace(*dot)) {
3873 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3875 } while (--cmdcnt > 0);
3878 c1 = get_one_char(); // get the replacement char
3881 cnt = (rows - 2) / 2; // put dot at center
3883 cnt = rows - 2; // put dot at bottom
3884 screenbegin = begin_line(dot); // start dot at top
3885 dot_scroll(cnt, -1);
3887 case '|': // |- move to column "cmdcnt"
3888 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3890 case '~': // ~- flip the case of letters a-z -> A-Z
3892 #if ENABLE_FEATURE_VI_UNDO
3893 if (islower(*dot)) {
3894 undo_push(dot, 1, UNDO_DEL);
3895 *dot = toupper(*dot);
3896 undo_push(dot, 1, UNDO_INS_CHAIN);
3897 } else if (isupper(*dot)) {
3898 undo_push(dot, 1, UNDO_DEL);
3899 *dot = tolower(*dot);
3900 undo_push(dot, 1, UNDO_INS_CHAIN);
3903 if (islower(*dot)) {
3904 *dot = toupper(*dot);
3906 } else if (isupper(*dot)) {
3907 *dot = tolower(*dot);
3912 } while (--cmdcnt > 0);
3913 end_cmd_q(); // stop adding to q
3915 //----- The Cursor and Function Keys -----------------------------
3916 case KEYCODE_HOME: // Cursor Key Home
3919 // The Fn keys could point to do_macro which could translate them
3921 case KEYCODE_FUN1: // Function Key F1
3922 case KEYCODE_FUN2: // Function Key F2
3923 case KEYCODE_FUN3: // Function Key F3
3924 case KEYCODE_FUN4: // Function Key F4
3925 case KEYCODE_FUN5: // Function Key F5
3926 case KEYCODE_FUN6: // Function Key F6
3927 case KEYCODE_FUN7: // Function Key F7
3928 case KEYCODE_FUN8: // Function Key F8
3929 case KEYCODE_FUN9: // Function Key F9
3930 case KEYCODE_FUN10: // Function Key F10
3931 case KEYCODE_FUN11: // Function Key F11
3932 case KEYCODE_FUN12: // Function Key F12
3938 // if text[] just became empty, add back an empty line
3940 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
3943 // it is OK for dot to exactly equal to end, otherwise check dot validity
3945 dot = bound_dot(dot); // make sure "dot" is valid
3947 #if ENABLE_FEATURE_VI_YANKMARK
3948 check_context(c); // update the current context
3952 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3953 cnt = dot - begin_line(dot);
3954 // Try to stay off of the Newline
3955 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3959 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3960 #if ENABLE_FEATURE_VI_CRASHME
3961 static int totalcmds = 0;
3962 static int Mp = 85; // Movement command Probability
3963 static int Np = 90; // Non-movement command Probability
3964 static int Dp = 96; // Delete command Probability
3965 static int Ip = 97; // Insert command Probability
3966 static int Yp = 98; // Yank command Probability
3967 static int Pp = 99; // Put command Probability
3968 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3969 static const char chars[20] = "\t012345 abcdABCD-=.$";
3970 static const char *const words[20] = {
3971 "this", "is", "a", "test",
3972 "broadcast", "the", "emergency", "of",
3973 "system", "quick", "brown", "fox",
3974 "jumped", "over", "lazy", "dogs",
3975 "back", "January", "Febuary", "March"
3977 static const char *const lines[20] = {
3978 "You should have received a copy of the GNU General Public License\n",
3979 "char c, cm, *cmd, *cmd1;\n",
3980 "generate a command by percentages\n",
3981 "Numbers may be typed as a prefix to some commands.\n",
3982 "Quit, discarding changes!\n",
3983 "Forced write, if permission originally not valid.\n",
3984 "In general, any ex or ed command (such as substitute or delete).\n",
3985 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3986 "Please get w/ me and I will go over it with you.\n",
3987 "The following is a list of scheduled, committed changes.\n",
3988 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3989 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3990 "Any question about transactions please contact Sterling Huxley.\n",
3991 "I will try to get back to you by Friday, December 31.\n",
3992 "This Change will be implemented on Friday.\n",
3993 "Let me know if you have problems accessing this;\n",
3994 "Sterling Huxley recently added you to the access list.\n",
3995 "Would you like to go to lunch?\n",
3996 "The last command will be automatically run.\n",
3997 "This is too much english for a computer geek.\n",
3999 static char *multilines[20] = {
4000 "You should have received a copy of the GNU General Public License\n",
4001 "char c, cm, *cmd, *cmd1;\n",
4002 "generate a command by percentages\n",
4003 "Numbers may be typed as a prefix to some commands.\n",
4004 "Quit, discarding changes!\n",
4005 "Forced write, if permission originally not valid.\n",
4006 "In general, any ex or ed command (such as substitute or delete).\n",
4007 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4008 "Please get w/ me and I will go over it with you.\n",
4009 "The following is a list of scheduled, committed changes.\n",
4010 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4011 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4012 "Any question about transactions please contact Sterling Huxley.\n",
4013 "I will try to get back to you by Friday, December 31.\n",
4014 "This Change will be implemented on Friday.\n",
4015 "Let me know if you have problems accessing this;\n",
4016 "Sterling Huxley recently added you to the access list.\n",
4017 "Would you like to go to lunch?\n",
4018 "The last command will be automatically run.\n",
4019 "This is too much english for a computer geek.\n",
4022 // create a random command to execute
4023 static void crash_dummy()
4025 static int sleeptime; // how long to pause between commands
4026 char c, cm, *cmd, *cmd1;
4027 int i, cnt, thing, rbi, startrbi, percent;
4029 // "dot" movement commands
4030 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4032 // is there already a command running?
4033 if (readbuffer[0] > 0)
4036 readbuffer[0] = 'X';
4038 sleeptime = 0; // how long to pause between commands
4039 memset(readbuffer, '\0', sizeof(readbuffer));
4040 // generate a command by percentages
4041 percent = (int) lrand48() % 100; // get a number from 0-99
4042 if (percent < Mp) { // Movement commands
4043 // available commands
4046 } else if (percent < Np) { // non-movement commands
4047 cmd = "mz<>\'\""; // available commands
4049 } else if (percent < Dp) { // Delete commands
4050 cmd = "dx"; // available commands
4052 } else if (percent < Ip) { // Inset commands
4053 cmd = "iIaAsrJ"; // available commands
4055 } else if (percent < Yp) { // Yank commands
4056 cmd = "yY"; // available commands
4058 } else if (percent < Pp) { // Put commands
4059 cmd = "pP"; // available commands
4062 // We do not know how to handle this command, try again
4066 // randomly pick one of the available cmds from "cmd[]"
4067 i = (int) lrand48() % strlen(cmd);
4069 if (strchr(":\024", cm))
4070 goto cd0; // dont allow colon or ctrl-T commands
4071 readbuffer[rbi++] = cm; // put cmd into input buffer
4073 // now we have the command-
4074 // there are 1, 2, and multi char commands
4075 // find out which and generate the rest of command as necessary
4076 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4077 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4078 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4079 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4081 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4083 readbuffer[rbi++] = c; // add movement to input buffer
4085 if (strchr("iIaAsc", cm)) { // multi-char commands
4087 // change some thing
4088 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4090 readbuffer[rbi++] = c; // add movement to input buffer
4092 thing = (int) lrand48() % 4; // what thing to insert
4093 cnt = (int) lrand48() % 10; // how many to insert
4094 for (i = 0; i < cnt; i++) {
4095 if (thing == 0) { // insert chars
4096 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4097 } else if (thing == 1) { // insert words
4098 strcat(readbuffer, words[(int) lrand48() % 20]);
4099 strcat(readbuffer, " ");
4100 sleeptime = 0; // how fast to type
4101 } else if (thing == 2) { // insert lines
4102 strcat(readbuffer, lines[(int) lrand48() % 20]);
4103 sleeptime = 0; // how fast to type
4104 } else { // insert multi-lines
4105 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4106 sleeptime = 0; // how fast to type
4109 strcat(readbuffer, ESC);
4111 readbuffer[0] = strlen(readbuffer + 1);
4115 mysleep(sleeptime); // sleep 1/100 sec
4118 // test to see if there are any errors
4119 static void crash_test()
4121 static time_t oldtim;
4128 strcat(msg, "end<text ");
4130 if (end > textend) {
4131 strcat(msg, "end>textend ");
4134 strcat(msg, "dot<text ");
4137 strcat(msg, "dot>end ");
4139 if (screenbegin < text) {
4140 strcat(msg, "screenbegin<text ");
4142 if (screenbegin > end - 1) {
4143 strcat(msg, "screenbegin>end-1 ");
4147 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4148 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4150 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4151 if (d[0] == '\n' || d[0] == '\r')
4156 if (tim >= (oldtim + 3)) {
4157 sprintf(status_buffer,
4158 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4159 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4165 static void edit_file(char *fn)
4167 #if ENABLE_FEATURE_VI_YANKMARK
4168 #define cur_line edit_file__cur_line
4171 #if ENABLE_FEATURE_VI_USE_SIGNALS
4175 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4179 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4180 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4181 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4183 write1(ESC"[999;999H" ESC"[6n");
4185 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4186 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4187 uint32_t rc = (k >> 32);
4188 columns = (rc & 0x7fff);
4189 if (columns > MAX_SCR_COLS)
4190 columns = MAX_SCR_COLS;
4191 rows = ((rc >> 16) & 0x7fff);
4192 if (rows > MAX_SCR_ROWS)
4193 rows = MAX_SCR_ROWS;
4197 new_screen(rows, columns); // get memory for virtual screen
4198 init_text_buffer(fn);
4200 #if ENABLE_FEATURE_VI_YANKMARK
4201 YDreg = 26; // default Yank/Delete reg
4202 // Ureg = 27; - const // hold orig line for "U" cmd
4203 mark[26] = mark[27] = text; // init "previous context"
4206 last_forward_char = last_input_char = '\0';
4210 #if ENABLE_FEATURE_VI_USE_SIGNALS
4211 signal(SIGWINCH, winch_handler);
4212 signal(SIGTSTP, tstp_handler);
4213 sig = sigsetjmp(restart, 1);
4215 screenbegin = dot = text;
4217 // int_handler() can jump to "restart",
4218 // must install handler *after* initializing "restart"
4219 signal(SIGINT, int_handler);
4222 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4225 offset = 0; // no horizontal offset
4227 #if ENABLE_FEATURE_VI_DOT_CMD
4229 ioq = ioq_start = NULL;
4234 #if ENABLE_FEATURE_VI_COLON
4239 while ((p = initial_cmds[n]) != NULL) {
4242 p = strchr(q, '\n');
4249 free(initial_cmds[n]);
4250 initial_cmds[n] = NULL;
4255 redraw(FALSE); // dont force every col re-draw
4256 //------This is the main Vi cmd handling loop -----------------------
4257 while (editing > 0) {
4258 #if ENABLE_FEATURE_VI_CRASHME
4260 if ((end - text) > 1) {
4261 crash_dummy(); // generate a random command
4264 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
4270 last_input_char = c = get_one_char(); // get a cmd from user
4271 #if ENABLE_FEATURE_VI_YANKMARK
4272 // save a copy of the current line- for the 'U" command
4273 if (begin_line(dot) != cur_line) {
4274 cur_line = begin_line(dot);
4275 text_yank(begin_line(dot), end_line(dot), Ureg);
4278 #if ENABLE_FEATURE_VI_DOT_CMD
4279 // These are commands that change text[].
4280 // Remember the input for the "." command
4281 if (!adding2q && ioq_start == NULL
4282 && cmd_mode == 0 // command mode
4283 && c > '\0' // exclude NUL and non-ASCII chars
4284 && c < 0x7f // (Unicode and such)
4285 && strchr(modifying_cmds, c)
4290 do_cmd(c); // execute the user command
4292 // poll to see if there is input already waiting. if we are
4293 // not able to display output fast enough to keep up, skip
4294 // the display update until we catch up with input.
4295 if (!readbuffer[0] && mysleep(0) == 0) {
4296 // no input pending - so update output
4300 #if ENABLE_FEATURE_VI_CRASHME
4302 crash_test(); // test editor variables
4305 //-------------------------------------------------------------------
4307 go_bottom_and_clear_to_eol();
4312 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4313 int vi_main(int argc, char **argv)
4319 #if ENABLE_FEATURE_VI_UNDO
4320 /* undo_stack_tail = NULL; - already is */
4321 #if ENABLE_FEATURE_VI_UNDO_QUEUE
4322 undo_queue_state = UNDO_EMPTY;
4323 /* undo_q = 0; - already is */
4327 #if ENABLE_FEATURE_VI_CRASHME
4328 srand((long) getpid());
4330 #ifdef NO_SUCH_APPLET_YET
4331 // if we aren't "vi", we are "view"
4332 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4333 SET_READONLY_MODE(readonly_mode);
4337 // autoindent is not default in vim 7.3
4338 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4339 // 1- process $HOME/.exrc file (not inplemented yet)
4340 // 2- process EXINIT variable from environment
4341 // 3- process command line args
4342 #if ENABLE_FEATURE_VI_COLON
4344 char *p = getenv("EXINIT");
4346 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4349 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4351 #if ENABLE_FEATURE_VI_CRASHME
4356 #if ENABLE_FEATURE_VI_READONLY
4357 case 'R': // Read-only flag
4358 SET_READONLY_MODE(readonly_mode);
4361 #if ENABLE_FEATURE_VI_COLON
4362 case 'c': // cmd line vi command
4364 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4376 // The argv array can be used by the ":next" and ":rewind" commands
4380 //----- This is the main file handling loop --------------
4383 // "Save cursor, use alternate screen buffer, clear screen"
4384 write1(ESC"[?1049h");
4386 edit_file(argv[optind]); // param might be NULL
4387 if (++optind >= argc)
4390 // "Use normal screen buffer, restore cursor"
4391 write1(ESC"[?1049l");
4392 //-----------------------------------------------------------