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 do_cmd(int); // execute a command
487 static int next_tabstop(int);
488 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
489 static char *begin_line(char *); // return pointer to cur line B-o-l
490 static char *end_line(char *); // return pointer to cur line E-o-l
491 static char *prev_line(char *); // return pointer to prev line B-o-l
492 static char *next_line(char *); // return pointer to next line B-o-l
493 static char *end_screen(void); // get pointer to last char on screen
494 static int count_lines(char *, char *); // count line from start to stop
495 static char *find_line(int); // find beginning of line #li
496 static char *move_to_col(char *, int); // move "p" to column l
497 static void dot_left(void); // move dot left- dont leave line
498 static void dot_right(void); // move dot right- dont leave line
499 static void dot_begin(void); // move dot to B-o-l
500 static void dot_end(void); // move dot to E-o-l
501 static void dot_next(void); // move dot to next line B-o-l
502 static void dot_prev(void); // move dot to prev line B-o-l
503 static void dot_scroll(int, int); // move the screen up or down
504 static void dot_skip_over_ws(void); // move dot pat WS
505 static char *bound_dot(char *); // make sure text[0] <= P < "end"
506 static char *new_screen(int, int); // malloc virtual screen memory
507 #if !ENABLE_FEATURE_VI_UNDO
508 #define char_insert(a,b,c) char_insert(a,b)
510 static char *char_insert(char *, char, int); // insert the char c at 'p'
511 // might reallocate text[]! use p += stupid_insert(p, ...),
512 // and be careful to not use pointers into potentially freed text[]!
513 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
514 static int st_test(char *, int, int, char *); // helper for skip_thing()
515 static char *skip_thing(char *, int, int, int); // skip some object
516 static char *find_pair(char *, char); // find matching pair () [] {}
517 #if !ENABLE_FEATURE_VI_UNDO
518 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
520 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
521 // might reallocate text[]! use p += text_hole_make(p, ...),
522 // and be careful to not use pointers into potentially freed text[]!
523 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
524 #if !ENABLE_FEATURE_VI_UNDO
525 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
527 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
528 static void rawmode(void); // set "raw" mode on tty
529 static void cookmode(void); // return to "cooked" mode on tty
530 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
531 static int mysleep(int);
532 static int get_one_char(void); // read 1 char from stdin
533 // file_insert might reallocate text[]!
534 static int file_insert(const char *, char *, int);
535 static int file_write(char *, char *, char *);
536 static void screen_erase(void);
537 static void go_bottom_and_clear_to_eol(void);
538 static void standout_start(void); // send "start reverse video" sequence
539 static void standout_end(void); // send "end reverse video" sequence
540 static void flash(int); // flash the terminal screen
541 static void show_status_line(void); // put a message on the bottom line
542 static void status_line(const char *, ...); // print to status buf
543 static void status_line_bold(const char *, ...);
544 static void status_line_bold_errno(const char *fn);
545 static void not_implemented(const char *); // display "Not implemented" message
546 static int format_edit_status(void); // format file status on status line
547 static void redraw(int); // force a full screen refresh
548 static char* format_line(char* /*, int*/);
549 static void refresh(int); // update the terminal from screen[]
551 static void indicate_error(void); // use flash or beep to indicate error
552 static void Hit_Return(void);
554 #if ENABLE_FEATURE_VI_SEARCH
555 static char *char_search(char *, const char *, int); // search for pattern starting at p
557 #if ENABLE_FEATURE_VI_COLON
558 static char *get_one_address(char *, int *); // get colon addr, if present
559 static char *get_address(char *, int *, int *); // get two colon addrs, if present
561 static void colon(char *); // execute the "colon" mode cmds
562 #if ENABLE_FEATURE_VI_USE_SIGNALS
563 static void winch_handler(int); // catch window size changes
564 static void tstp_handler(int); // catch ctrl-Z
565 static void int_handler(int); // catch ctrl-C
567 #if ENABLE_FEATURE_VI_DOT_CMD
568 static void start_new_cmd_q(char); // new queue for command
569 static void end_cmd_q(void); // stop saving input chars
571 #define end_cmd_q() ((void)0)
573 #if ENABLE_FEATURE_VI_SETOPTS
574 static void showmatching(char *); // show the matching pair () [] {}
576 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
577 // might reallocate text[]! use p += string_insert(p, ...),
578 // and be careful to not use pointers into potentially freed text[]!
579 # if !ENABLE_FEATURE_VI_UNDO
580 #define string_insert(a,b,c) string_insert(a,b)
582 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
584 #if ENABLE_FEATURE_VI_YANKMARK
585 static char *text_yank(char *, char *, int); // save copy of "p" into a register
586 static char what_reg(void); // what is letter of current YDreg
587 static void check_context(char); // remember context for '' command
589 #if ENABLE_FEATURE_VI_UNDO
590 static void flush_undo_data(void);
591 static void undo_push(char *, unsigned, unsigned char); // push an operation on the undo stack
592 static void undo_push_insert(char *, int, int); // convenience function
593 static void undo_pop(void); // undo the last operation
594 # if ENABLE_FEATURE_VI_UNDO_QUEUE
595 static void undo_queue_commit(void); // flush any queued objects to the undo stack
597 # define undo_queue_commit() ((void)0)
600 #define flush_undo_data() ((void)0)
601 #define undo_queue_commit() ((void)0)
604 #if ENABLE_FEATURE_VI_CRASHME
605 static void crash_dummy();
606 static void crash_test();
607 static int crashme = 0;
610 static void show_help(void)
612 puts("These features are available:"
613 #if ENABLE_FEATURE_VI_SEARCH
614 "\n\tPattern searches with / and ?"
616 #if ENABLE_FEATURE_VI_DOT_CMD
617 "\n\tLast command repeat with ."
619 #if ENABLE_FEATURE_VI_YANKMARK
620 "\n\tLine marking with 'x"
621 "\n\tNamed buffers with \"x"
623 #if ENABLE_FEATURE_VI_READONLY
624 //not implemented: "\n\tReadonly if vi is called as \"view\""
625 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
627 #if ENABLE_FEATURE_VI_SET
628 "\n\tSome colon mode commands with :"
630 #if ENABLE_FEATURE_VI_SETOPTS
631 "\n\tSettable options with \":set\""
633 #if ENABLE_FEATURE_VI_USE_SIGNALS
634 "\n\tSignal catching- ^C"
635 "\n\tJob suspend and resume with ^Z"
637 #if ENABLE_FEATURE_VI_WIN_RESIZE
638 "\n\tAdapt to window re-sizes"
643 static void write1(const char *out)
648 /* read text from file or create an empty buf */
649 /* will also update current_filename */
650 static int init_text_buffer(char *fn)
654 /* allocate/reallocate text buffer */
657 screenbegin = dot = end = text = xzalloc(text_size);
659 if (fn != current_filename) {
660 free(current_filename);
661 current_filename = xstrdup(fn);
663 rc = file_insert(fn, text, 1);
665 // file doesnt exist. Start empty buf with dummy line
666 char_insert(text, '\n', NO_UNDO);
671 last_modified_count = -1;
672 #if ENABLE_FEATURE_VI_YANKMARK
674 memset(mark, 0, sizeof(mark));
679 #if ENABLE_FEATURE_VI_WIN_RESIZE
680 static int query_screen_dimensions(void)
682 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
683 if (rows > MAX_SCR_ROWS)
685 if (columns > MAX_SCR_COLS)
686 columns = MAX_SCR_COLS;
690 static ALWAYS_INLINE int query_screen_dimensions(void)
696 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
697 static int mysleep(int hund)
699 struct pollfd pfd[1];
704 pfd[0].fd = STDIN_FILENO;
705 pfd[0].events = POLLIN;
706 return safe_poll(pfd, 1, hund*10) > 0;
709 //----- Set terminal attributes --------------------------------
710 static void rawmode(void)
712 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
713 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
714 erase_char = term_orig.c_cc[VERASE];
717 static void cookmode(void)
720 tcsetattr_stdin_TCSANOW(&term_orig);
723 //----- Terminal Drawing ---------------------------------------
724 // The terminal is made up of 'rows' line of 'columns' columns.
725 // classically this would be 24 x 80.
726 // screen coordinates
732 // 23,0 ... 23,79 <- status line
734 //----- Move the cursor to row x col (count from 0, not 1) -------
735 static void place_cursor(int row, int col)
737 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
739 if (row < 0) row = 0;
740 if (row >= rows) row = rows - 1;
741 if (col < 0) col = 0;
742 if (col >= columns) col = columns - 1;
744 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
748 //----- Erase from cursor to end of line -----------------------
749 static void clear_to_eol(void)
751 write1(ESC_CLEAR2EOL);
754 static void go_bottom_and_clear_to_eol(void)
756 place_cursor(rows - 1, 0);
760 //----- Start standout mode ------------------------------------
761 static void standout_start(void)
763 write1(ESC_BOLD_TEXT);
766 //----- End standout mode --------------------------------------
767 static void standout_end(void)
769 write1(ESC_NORM_TEXT);
772 //----- Text Movement Routines ---------------------------------
773 static char *begin_line(char *p) // return pointer to first char cur line
776 p = memrchr(text, '\n', p - text);
784 static char *end_line(char *p) // return pointer to NL of cur line
787 p = memchr(p, '\n', end - p - 1);
794 static char *dollar_line(char *p) // return pointer to just before NL line
797 // Try to stay off of the Newline
798 if (*p == '\n' && (p - begin_line(p)) > 0)
803 static char *prev_line(char *p) // return pointer first char prev line
805 p = begin_line(p); // goto beginning of cur line
806 if (p > text && p[-1] == '\n')
807 p--; // step to prev line
808 p = begin_line(p); // goto beginning of prev line
812 static char *next_line(char *p) // return pointer first char next line
815 if (p < end - 1 && *p == '\n')
816 p++; // step to next line
820 //----- Text Information Routines ------------------------------
821 static char *end_screen(void)
826 // find new bottom line
828 for (cnt = 0; cnt < rows - 2; cnt++)
834 // count line from start to stop
835 static int count_lines(char *start, char *stop)
840 if (stop < start) { // start and stop are backwards- reverse them
846 stop = end_line(stop);
847 while (start <= stop && start <= end - 1) {
848 start = end_line(start);
856 static char *find_line(int li) // find beginning of line #li
860 for (q = text; li > 1; li--) {
866 static int next_tabstop(int col)
868 return col + ((tabstop - 1) - (col % tabstop));
871 //----- Erase the Screen[] memory ------------------------------
872 static void screen_erase(void)
874 memset(screen, ' ', screensize); // clear new screen
877 //----- Synchronize the cursor to Dot --------------------------
878 static NOINLINE void sync_cursor(char *d, int *row, int *col)
880 char *beg_cur; // begin and end of "d" line
884 beg_cur = begin_line(d); // first char of cur line
886 if (beg_cur < screenbegin) {
887 // "d" is before top line on screen
888 // how many lines do we have to move
889 cnt = count_lines(beg_cur, screenbegin);
891 screenbegin = beg_cur;
892 if (cnt > (rows - 1) / 2) {
893 // we moved too many lines. put "dot" in middle of screen
894 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
895 screenbegin = prev_line(screenbegin);
899 char *end_scr; // begin and end of screen
900 end_scr = end_screen(); // last char of screen
901 if (beg_cur > end_scr) {
902 // "d" is after bottom line on screen
903 // how many lines do we have to move
904 cnt = count_lines(end_scr, beg_cur);
905 if (cnt > (rows - 1) / 2)
906 goto sc1; // too many lines
907 for (ro = 0; ro < cnt - 1; ro++) {
908 // move screen begin the same amount
909 screenbegin = next_line(screenbegin);
910 // now, move the end of screen
911 end_scr = next_line(end_scr);
912 end_scr = end_line(end_scr);
916 // "d" is on screen- find out which row
918 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
924 // find out what col "d" is on
926 while (tp < d) { // drive "co" to correct column
927 if (*tp == '\n') //vda || *tp == '\0')
930 // handle tabs like real vi
931 if (d == tp && cmd_mode) {
934 co = next_tabstop(co);
935 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
936 co++; // display as ^X, use 2 columns
942 // "co" is the column where "dot" is.
943 // The screen has "columns" columns.
944 // The currently displayed columns are 0+offset -- columns+ofset
945 // |-------------------------------------------------------------|
947 // offset | |------- columns ----------------|
949 // If "co" is already in this range then we do not have to adjust offset
950 // but, we do have to subtract the "offset" bias from "co".
951 // If "co" is outside this range then we have to change "offset".
952 // If the first char of a line is a tab the cursor will try to stay
953 // in column 7, but we have to set offset to 0.
955 if (co < 0 + offset) {
958 if (co >= columns + offset) {
959 offset = co - columns + 1;
961 // if the first char of the line is a tab, and "dot" is sitting on it
962 // force offset to 0.
963 if (d == beg_cur && *d == '\t') {
972 //----- The Colon commands -------------------------------------
973 #if ENABLE_FEATURE_VI_COLON
974 static char *get_one_address(char *p, int *addr) // get colon addr, if present
978 IF_FEATURE_VI_YANKMARK(char c;)
979 IF_FEATURE_VI_SEARCH(char *pat;)
981 *addr = -1; // assume no addr
982 if (*p == '.') { // the current line
985 *addr = count_lines(text, q);
987 #if ENABLE_FEATURE_VI_YANKMARK
988 else if (*p == '\'') { // is this a mark addr
992 if (c >= 'a' && c <= 'z') {
995 q = mark[(unsigned char) c];
996 if (q != NULL) { // is mark valid
997 *addr = count_lines(text, q);
1002 #if ENABLE_FEATURE_VI_SEARCH
1003 else if (*p == '/') { // a search pattern
1004 q = strchrnul(++p, '/');
1005 pat = xstrndup(p, q - p); // save copy of pattern
1009 q = char_search(dot, pat, (FORWARD << 1) | FULL);
1011 *addr = count_lines(text, q);
1016 else if (*p == '$') { // the last line in file
1018 q = begin_line(end - 1);
1019 *addr = count_lines(text, q);
1020 } else if (isdigit(*p)) { // specific line number
1021 sscanf(p, "%d%n", addr, &st);
1024 // unrecognized address - assume -1
1030 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
1032 //----- get the address' i.e., 1,3 'a,'b -----
1033 // get FIRST addr, if present
1035 p++; // skip over leading spaces
1036 if (*p == '%') { // alias for 1,$
1039 *e = count_lines(text, end-1);
1042 p = get_one_address(p, b);
1045 if (*p == ',') { // is there a address separator
1049 // get SECOND addr, if present
1050 p = get_one_address(p, e);
1054 p++; // skip over trailing spaces
1058 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
1059 static void setops(const char *args, const char *opname, int flg_no,
1060 const char *short_opname, int opt)
1062 const char *a = args + flg_no;
1063 int l = strlen(opname) - 1; // opname have + ' '
1065 // maybe strncmp? we had tons of erroneous strncasecmp's...
1066 if (strncasecmp(a, opname, l) == 0
1067 || strncasecmp(a, short_opname, 2) == 0
1077 #endif /* FEATURE_VI_COLON */
1079 // buf must be no longer than MAX_INPUT_LEN!
1080 static void colon(char *buf)
1082 #if !ENABLE_FEATURE_VI_COLON
1083 // Simple ":cmd" handler with minimal set of commands
1092 if (strncmp(p, "quit", cnt) == 0
1093 || strncmp(p, "q!", cnt) == 0
1095 if (modified_count && p[1] != '!') {
1096 status_line_bold("No write since last change (:%s! overrides)", p);
1102 if (strncmp(p, "write", cnt) == 0
1103 || strncmp(p, "wq", cnt) == 0
1104 || strncmp(p, "wn", cnt) == 0
1105 || (p[0] == 'x' && !p[1])
1107 if (modified_count != 0 || p[0] != 'x') {
1108 cnt = file_write(current_filename, text, end - 1);
1112 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
1115 last_modified_count = -1;
1116 status_line("'%s' %dL, %dC",
1118 count_lines(text, end - 1), cnt
1121 || p[1] == 'q' || p[1] == 'n'
1122 || p[1] == 'Q' || p[1] == 'N'
1129 if (strncmp(p, "file", cnt) == 0) {
1130 last_status_cksum = 0; // force status update
1133 if (sscanf(p, "%d", &cnt) > 0) {
1134 dot = find_line(cnt);
1141 char c, *buf1, *q, *r;
1142 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1145 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1149 // :3154 // if (-e line 3154) goto it else stay put
1150 // :4,33w! foo // write a portion of buffer to file "foo"
1151 // :w // write all of buffer to current file
1153 // :q! // quit- dont care about modified file
1154 // :'a,'z!sort -u // filter block through sort
1155 // :'f // goto mark "f"
1156 // :'fl // list literal the mark "f" line
1157 // :.r bar // read file "bar" into buffer before dot
1158 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1159 // :/xyz/ // goto the "xyz" line
1160 // :s/find/replace/ // substitute pattern "find" with "replace"
1161 // :!<cmd> // run <cmd> then return
1167 buf++; // move past the ':'
1171 q = text; // assume 1,$ for the range
1173 li = count_lines(text, end - 1);
1174 fn = current_filename;
1176 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1177 buf = get_address(buf, &b, &e);
1179 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1180 // remember orig command line
1184 // get the COMMAND into cmd[]
1186 while (*buf != '\0') {
1192 // get any ARGuments
1193 while (isblank(*buf))
1197 buf1 = last_char_is(cmd, '!');
1200 *buf1 = '\0'; // get rid of !
1203 // if there is only one addr, then the addr
1204 // is the line number of the single line the
1205 // user wants. So, reset the end
1206 // pointer to point at end of the "b" line
1207 q = find_line(b); // what line is #b
1212 // we were given two addrs. change the
1213 // end pointer to the addr given by user.
1214 r = find_line(e); // what line is #e
1218 // ------------ now look for the command ------------
1220 if (i == 0) { // :123CR goto line #123
1222 dot = find_line(b); // what line is #b
1226 # if ENABLE_FEATURE_ALLOW_EXEC
1227 else if (cmd[0] == '!') { // run a cmd
1229 // :!ls run the <cmd>
1230 go_bottom_and_clear_to_eol();
1232 retcode = system(orig_buf + 1); // run the cmd
1234 printf("\nshell returned %i\n\n", retcode);
1236 Hit_Return(); // let user see results
1239 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1240 if (b < 0) { // no addr given- use defaults
1241 b = e = count_lines(text, dot);
1243 status_line("%d", b);
1244 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1245 if (b < 0) { // no addr given- use defaults
1246 q = begin_line(dot); // assume .,. for the range
1249 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1251 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1254 // don't edit, if the current file has been modified
1255 if (modified_count && !useforce) {
1256 status_line_bold("No write since last change (:%s! overrides)", cmd);
1260 // the user supplied a file name
1262 } else if (current_filename && current_filename[0]) {
1263 // no user supplied name- use the current filename
1264 // fn = current_filename; was set by default
1266 // no user file name, no current name- punt
1267 status_line_bold("No current filename");
1271 size = init_text_buffer(fn);
1273 # if ENABLE_FEATURE_VI_YANKMARK
1274 if (Ureg >= 0 && Ureg < 28) {
1275 free(reg[Ureg]); // free orig line reg- for 'U'
1278 if (YDreg >= 0 && YDreg < 28) {
1279 free(reg[YDreg]); // free default yank/delete register
1283 // how many lines in text[]?
1284 li = count_lines(text, end - 1);
1285 status_line("'%s'%s"
1286 IF_FEATURE_VI_READONLY("%s")
1289 (size < 0 ? " [New file]" : ""),
1290 IF_FEATURE_VI_READONLY(
1291 ((readonly_mode) ? " [Readonly]" : ""),
1293 li, (int)(end - text)
1295 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1296 if (b != -1 || e != -1) {
1297 status_line_bold("No address allowed on this command");
1301 // user wants a new filename
1302 free(current_filename);
1303 current_filename = xstrdup(args);
1305 // user wants file status info
1306 last_status_cksum = 0; // force status update
1308 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1309 // print out values of all features
1310 go_bottom_and_clear_to_eol();
1315 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1316 if (b < 0) { // no addr given- use defaults
1317 q = begin_line(dot); // assume .,. for the range
1320 go_bottom_and_clear_to_eol();
1322 for (; q <= r; q++) {
1326 c_is_no_print = (c & 0x80) && !Isprint(c);
1327 if (c_is_no_print) {
1333 } else if (c < ' ' || c == 127) {
1345 } else if (strncmp(cmd, "quit", i) == 0 // quit
1346 || strncmp(cmd, "next", i) == 0 // edit next file
1347 || strncmp(cmd, "prev", i) == 0 // edit previous file
1352 // force end of argv list
1358 // don't exit if the file been modified
1359 if (modified_count) {
1360 status_line_bold("No write since last change (:%s! overrides)", cmd);
1363 // are there other file to edit
1364 n = save_argc - optind - 1;
1365 if (*cmd == 'q' && n > 0) {
1366 status_line_bold("%d more file(s) to edit", n);
1369 if (*cmd == 'n' && n <= 0) {
1370 status_line_bold("No more files to edit");
1374 // are there previous files to edit
1376 status_line_bold("No previous files to edit");
1382 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1387 status_line_bold("No filename given");
1390 if (b < 0) { // no addr given- use defaults
1391 q = begin_line(dot); // assume "dot"
1393 // read after current line- unless user said ":0r foo"
1396 // read after last line
1400 { // dance around potentially-reallocated text[]
1401 uintptr_t ofs = q - text;
1402 size = file_insert(fn, q, 0);
1406 goto ret; // nothing was inserted
1407 // how many lines in text[]?
1408 li = count_lines(q, q + size - 1);
1410 IF_FEATURE_VI_READONLY("%s")
1413 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1417 // if the insert is before "dot" then we need to update
1421 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1422 if (modified_count && !useforce) {
1423 status_line_bold("No write since last change (:%s! overrides)", cmd);
1425 // reset the filenames to edit
1426 optind = -1; // start from 0th file
1429 # if ENABLE_FEATURE_VI_SET
1430 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1431 # if ENABLE_FEATURE_VI_SETOPTS
1434 i = 0; // offset into args
1435 // only blank is regarded as args delimiter. What about tab '\t'?
1436 if (!args[0] || strcasecmp(args, "all") == 0) {
1437 // print out values of all options
1438 # if ENABLE_FEATURE_VI_SETOPTS
1445 autoindent ? "" : "no",
1446 err_method ? "" : "no",
1447 ignorecase ? "" : "no",
1448 showmatch ? "" : "no",
1454 # if ENABLE_FEATURE_VI_SETOPTS
1457 if (strncmp(argp, "no", 2) == 0)
1458 i = 2; // ":set noautoindent"
1459 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1460 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1461 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1462 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1463 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1465 sscanf(argp + i+8, "%u", &t);
1466 if (t > 0 && t <= MAX_TABSTOP)
1469 argp = skip_non_whitespace(argp);
1470 argp = skip_whitespace(argp);
1472 # endif /* FEATURE_VI_SETOPTS */
1473 # endif /* FEATURE_VI_SET */
1475 # if ENABLE_FEATURE_VI_SEARCH
1476 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1477 char *F, *R, *flags;
1478 size_t len_F, len_R;
1479 int gflag; // global replace flag
1480 # if ENABLE_FEATURE_VI_UNDO
1481 int dont_chain_first_item = ALLOW_UNDO;
1484 // F points to the "find" pattern
1485 // R points to the "replace" pattern
1486 // replace the cmd line delimiters "/" with NULs
1487 c = orig_buf[1]; // what is the delimiter
1488 F = orig_buf + 2; // start of "find"
1489 R = strchr(F, c); // middle delimiter
1493 *R++ = '\0'; // terminate "find"
1494 flags = strchr(R, c);
1498 *flags++ = '\0'; // terminate "replace"
1502 if (b < 0) { // maybe :s/foo/bar/
1503 q = begin_line(dot); // start with cur line
1504 b = count_lines(text, q); // cur line number
1507 e = b; // maybe :.s/foo/bar/
1509 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1510 char *ls = q; // orig line start
1513 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
1516 // we found the "find" pattern - delete it
1517 // For undo support, the first item should not be chained
1518 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1519 # if ENABLE_FEATURE_VI_UNDO
1520 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1522 // insert the "replace" patern
1523 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1526 /*q += bias; - recalculated anyway */
1527 // check for "global" :s/foo/bar/g
1529 if ((found + len_R) < end_line(ls)) {
1531 goto vc4; // don't let q move past cur line
1537 # endif /* FEATURE_VI_SEARCH */
1538 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1539 status_line(BB_VER);
1540 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1541 || strncmp(cmd, "wq", i) == 0
1542 || strncmp(cmd, "wn", i) == 0
1543 || (cmd[0] == 'x' && !cmd[1])
1546 //int forced = FALSE;
1548 // is there a file name to write to?
1552 # if ENABLE_FEATURE_VI_READONLY
1553 if (readonly_mode && !useforce) {
1554 status_line_bold("'%s' is read only", fn);
1559 // if "fn" is not write-able, chmod u+w
1560 // sprintf(syscmd, "chmod u+w %s", fn);
1564 if (modified_count != 0 || cmd[0] != 'x') {
1566 l = file_write(fn, q, r);
1571 //if (useforce && forced) {
1573 // sprintf(syscmd, "chmod u-w %s", fn);
1579 status_line_bold_errno(fn);
1581 // how many lines written
1582 li = count_lines(q, q + l - 1);
1583 status_line("'%s' %dL, %dC", fn, li, l);
1585 if (q == text && q + l == end) {
1587 last_modified_count = -1;
1590 || cmd[1] == 'q' || cmd[1] == 'n'
1591 || cmd[1] == 'Q' || cmd[1] == 'N'
1597 # if ENABLE_FEATURE_VI_YANKMARK
1598 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1599 if (b < 0) { // no addr given- use defaults
1600 q = begin_line(dot); // assume .,. for the range
1603 text_yank(q, r, YDreg);
1604 li = count_lines(q, r);
1605 status_line("Yank %d lines (%d chars) into [%c]",
1606 li, strlen(reg[YDreg]), what_reg());
1610 not_implemented(cmd);
1613 dot = bound_dot(dot); // make sure "dot" is valid
1615 # if ENABLE_FEATURE_VI_SEARCH
1617 status_line(":s expression missing delimiters");
1619 #endif /* FEATURE_VI_COLON */
1622 static void Hit_Return(void)
1627 write1("[Hit return to continue]");
1629 while ((c = get_one_char()) != '\n' && c != '\r')
1631 redraw(TRUE); // force redraw all
1634 //----- Dot Movement Routines ----------------------------------
1635 static void dot_left(void)
1637 undo_queue_commit();
1638 if (dot > text && dot[-1] != '\n')
1642 static void dot_right(void)
1644 undo_queue_commit();
1645 if (dot < end - 1 && *dot != '\n')
1649 static void dot_begin(void)
1651 undo_queue_commit();
1652 dot = begin_line(dot); // return pointer to first char cur line
1655 static void dot_end(void)
1657 undo_queue_commit();
1658 dot = end_line(dot); // return pointer to last char cur line
1661 static char *move_to_col(char *p, int l)
1667 while (co < l && p < end) {
1668 if (*p == '\n') //vda || *p == '\0')
1671 co = next_tabstop(co);
1672 } else if (*p < ' ' || *p == 127) {
1673 co++; // display as ^X, use 2 columns
1681 static void dot_next(void)
1683 undo_queue_commit();
1684 dot = next_line(dot);
1687 static void dot_prev(void)
1689 undo_queue_commit();
1690 dot = prev_line(dot);
1693 static void dot_scroll(int cnt, int dir)
1697 undo_queue_commit();
1698 for (; cnt > 0; cnt--) {
1701 // ctrl-Y scroll up one line
1702 screenbegin = prev_line(screenbegin);
1705 // ctrl-E scroll down one line
1706 screenbegin = next_line(screenbegin);
1709 // make sure "dot" stays on the screen so we dont scroll off
1710 if (dot < screenbegin)
1712 q = end_screen(); // find new bottom line
1714 dot = begin_line(q); // is dot is below bottom line?
1718 static void dot_skip_over_ws(void)
1721 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1725 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1727 if (p >= end && end > text) {
1738 //----- Helper Utility Routines --------------------------------
1740 //----------------------------------------------------------------
1741 //----- Char Routines --------------------------------------------
1742 /* Chars that are part of a word-
1743 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1744 * Chars that are Not part of a word (stoppers)
1745 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1746 * Chars that are WhiteSpace
1747 * TAB NEWLINE VT FF RETURN SPACE
1748 * DO NOT COUNT NEWLINE AS WHITESPACE
1751 static char *new_screen(int ro, int co)
1756 screensize = ro * co + 8;
1757 screen = xmalloc(screensize);
1758 // initialize the new screen. assume this will be a empty file.
1760 // non-existent text[] lines start with a tilde (~).
1761 for (li = 1; li < ro - 1; li++) {
1762 screen[(li * co) + 0] = '~';
1767 #if ENABLE_FEATURE_VI_SEARCH
1769 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1771 // search for pattern starting at p
1772 static char *char_search(char *p, const char *pat, int dir_and_range)
1774 struct re_pattern_buffer preg;
1781 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1783 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1785 memset(&preg, 0, sizeof(preg));
1786 err = re_compile_pattern(pat, strlen(pat), &preg);
1788 status_line_bold("bad search pattern '%s': %s", pat, err);
1792 range = (dir_and_range & 1);
1793 q = end - 1; // if FULL
1794 if (range == LIMITED)
1796 if (dir_and_range < 0) { // BACK?
1798 if (range == LIMITED)
1802 // RANGE could be negative if we are searching backwards
1812 // search for the compiled pattern, preg, in p[]
1813 // range < 0: search backward
1814 // range > 0: search forward
1816 // re_search() < 0: not found or error
1817 // re_search() >= 0: index of found pattern
1818 // struct pattern char int int int struct reg
1819 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1820 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1824 if (dir_and_range > 0) // FORWARD?
1833 # if ENABLE_FEATURE_VI_SETOPTS
1834 static int mycmp(const char *s1, const char *s2, int len)
1837 return strncasecmp(s1, s2, len);
1839 return strncmp(s1, s2, len);
1842 # define mycmp strncmp
1845 static char *char_search(char *p, const char *pat, int dir_and_range)
1852 range = (dir_and_range & 1);
1853 if (dir_and_range > 0) { //FORWARD?
1854 stop = end - 1; // assume range is p..end-1
1855 if (range == LIMITED)
1856 stop = next_line(p); // range is to next line
1857 for (start = p; start < stop; start++) {
1858 if (mycmp(start, pat, len) == 0) {
1863 stop = text; // assume range is text..p
1864 if (range == LIMITED)
1865 stop = prev_line(p); // range is to prev line
1866 for (start = p - len; start >= stop; start--) {
1867 if (mycmp(start, pat, len) == 0) {
1872 // pattern not found
1878 #endif /* FEATURE_VI_SEARCH */
1880 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1882 if (c == 22) { // Is this an ctrl-V?
1883 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1884 refresh(FALSE); // show the ^
1887 #if ENABLE_FEATURE_VI_UNDO
1888 undo_push_insert(p, 1, undo);
1891 #endif /* ENABLE_FEATURE_VI_UNDO */
1893 } else if (c == 27) { // Is this an ESC?
1895 undo_queue_commit();
1897 end_cmd_q(); // stop adding to q
1898 last_status_cksum = 0; // force status update
1899 if ((p[-1] != '\n') && (dot > text)) {
1902 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1905 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1908 // insert a char into text[]
1910 c = '\n'; // translate \r to \n
1911 #if ENABLE_FEATURE_VI_UNDO
1912 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1914 undo_queue_commit();
1916 undo_push_insert(p, 1, undo);
1919 #endif /* ENABLE_FEATURE_VI_UNDO */
1920 p += 1 + stupid_insert(p, c); // insert the char
1921 #if ENABLE_FEATURE_VI_SETOPTS
1922 if (showmatch && strchr(")]}", c) != NULL) {
1923 showmatching(p - 1);
1925 if (autoindent && c == '\n') { // auto indent the new line
1928 q = prev_line(p); // use prev line as template
1929 len = strspn(q, " \t"); // space or tab
1932 bias = text_hole_make(p, len);
1935 #if ENABLE_FEATURE_VI_UNDO
1936 undo_push_insert(p, len, undo);
1947 // might reallocate text[]! use p += stupid_insert(p, ...),
1948 // and be careful to not use pointers into potentially freed text[]!
1949 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1952 bias = text_hole_make(p, 1);
1958 static int st_test(char *p, int type, int dir, char *tested)
1968 if (type == S_BEFORE_WS) {
1970 test = (!isspace(c) || c == '\n');
1972 if (type == S_TO_WS) {
1974 test = (!isspace(c) || c == '\n');
1976 if (type == S_OVER_WS) {
1980 if (type == S_END_PUNCT) {
1984 if (type == S_END_ALNUM) {
1986 test = (isalnum(c) || c == '_');
1992 static char *skip_thing(char *p, int linecnt, int dir, int type)
1996 while (st_test(p, type, dir, &c)) {
1997 // make sure we limit search to correct number of lines
1998 if (c == '\n' && --linecnt < 1)
2000 if (dir >= 0 && p >= end - 1)
2002 if (dir < 0 && p <= text)
2004 p += dir; // move to next char
2009 // find matching char of pair () [] {}
2010 // will crash if c is not one of these
2011 static char *find_pair(char *p, const char c)
2013 const char *braces = "()[]{}";
2017 dir = strchr(braces, c) - braces;
2019 match = braces[dir];
2020 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
2022 // look for match, count levels of pairs (( ))
2026 if (p < text || p >= end)
2029 level++; // increase pair levels
2031 level--; // reduce pair level
2033 return p; // found matching pair
2038 #if ENABLE_FEATURE_VI_SETOPTS
2039 // show the matching char of a pair, () [] {}
2040 static void showmatching(char *p)
2044 // we found half of a pair
2045 q = find_pair(p, *p); // get loc of matching char
2047 indicate_error(); // no matching char
2049 // "q" now points to matching pair
2050 save_dot = dot; // remember where we are
2051 dot = q; // go to new loc
2052 refresh(FALSE); // let the user see it
2053 mysleep(40); // give user some time
2054 dot = save_dot; // go back to old loc
2058 #endif /* FEATURE_VI_SETOPTS */
2060 #if ENABLE_FEATURE_VI_UNDO
2061 static void flush_undo_data(void)
2063 struct undo_object *undo_entry;
2065 while (undo_stack_tail) {
2066 undo_entry = undo_stack_tail;
2067 undo_stack_tail = undo_entry->prev;
2072 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2073 // Add to the undo stack
2074 static void undo_push(char *src, unsigned length, uint8_t u_type)
2076 struct undo_object *undo_entry;
2079 // UNDO_INS: insertion, undo will remove from buffer
2080 // UNDO_DEL: deleted text, undo will restore to buffer
2081 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2082 // The CHAIN operations are for handling multiple operations that the user
2083 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2084 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2085 // for the INS/DEL operation. The raw values should be equal to the values of
2086 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2088 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2089 // This undo queuing functionality groups multiple character typing or backspaces
2090 // into a single large undo object. This greatly reduces calls to malloc() for
2091 // single-character operations while typing and has the side benefit of letting
2092 // an undo operation remove chunks of text rather than a single character.
2094 case UNDO_EMPTY: // Just in case this ever happens...
2096 case UNDO_DEL_QUEUED:
2098 return; // Only queue single characters
2099 switch (undo_queue_state) {
2101 undo_queue_state = UNDO_DEL;
2103 undo_queue_spos = src;
2105 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2106 // If queue is full, dump it into an object
2107 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2108 undo_queue_commit();
2111 // Switch from storing inserted text to deleted text
2112 undo_queue_commit();
2113 undo_push(src, length, UNDO_DEL_QUEUED);
2117 case UNDO_INS_QUEUED:
2120 switch (undo_queue_state) {
2122 undo_queue_state = UNDO_INS;
2123 undo_queue_spos = src;
2126 undo_q++; // Don't need to save any data for insertions
2127 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2128 undo_queue_commit();
2132 // Switch from storing deleted text to inserted text
2133 undo_queue_commit();
2134 undo_push(src, length, UNDO_INS_QUEUED);
2140 // If undo queuing is disabled, ignore the queuing flag entirely
2141 u_type = u_type & ~UNDO_QUEUED_FLAG;
2144 // Allocate a new undo object
2145 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2146 // For UNDO_DEL objects, save deleted text
2147 if ((text + length) == end)
2149 // If this deletion empties text[], strip the newline. When the buffer becomes
2150 // zero-length, a newline is added back, which requires this to compensate.
2151 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2152 memcpy(undo_entry->undo_text, src, length);
2154 undo_entry = xzalloc(sizeof(*undo_entry));
2156 undo_entry->length = length;
2157 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2158 if ((u_type & UNDO_USE_SPOS) != 0) {
2159 undo_entry->start = undo_queue_spos - text; // use start position from queue
2161 undo_entry->start = src - text; // use offset from start of text buffer
2163 u_type = (u_type & ~UNDO_USE_SPOS);
2165 undo_entry->start = src - text;
2167 undo_entry->u_type = u_type;
2169 // Push it on undo stack
2170 undo_entry->prev = undo_stack_tail;
2171 undo_stack_tail = undo_entry;
2175 static void undo_push_insert(char *p, int len, int undo)
2179 undo_push(p, len, UNDO_INS);
2181 case ALLOW_UNDO_CHAIN:
2182 undo_push(p, len, UNDO_INS_CHAIN);
2184 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2185 case ALLOW_UNDO_QUEUED:
2186 undo_push(p, len, UNDO_INS_QUEUED);
2192 // Undo the last operation
2193 static void undo_pop(void)
2196 char *u_start, *u_end;
2197 struct undo_object *undo_entry;
2199 // Commit pending undo queue before popping (should be unnecessary)
2200 undo_queue_commit();
2202 undo_entry = undo_stack_tail;
2203 // Check for an empty undo stack
2205 status_line("Already at oldest change");
2209 switch (undo_entry->u_type) {
2211 case UNDO_DEL_CHAIN:
2212 // make hole and put in text that was deleted; deallocate text
2213 u_start = text + undo_entry->start;
2214 text_hole_make(u_start, undo_entry->length);
2215 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2216 status_line("Undo [%d] %s %d chars at position %d",
2217 modified_count, "restored",
2218 undo_entry->length, undo_entry->start
2222 case UNDO_INS_CHAIN:
2223 // delete what was inserted
2224 u_start = undo_entry->start + text;
2225 u_end = u_start - 1 + undo_entry->length;
2226 text_hole_delete(u_start, u_end, NO_UNDO);
2227 status_line("Undo [%d] %s %d chars at position %d",
2228 modified_count, "deleted",
2229 undo_entry->length, undo_entry->start
2234 switch (undo_entry->u_type) {
2235 // If this is the end of a chain, lower modification count and refresh display
2238 dot = (text + undo_entry->start);
2241 case UNDO_DEL_CHAIN:
2242 case UNDO_INS_CHAIN:
2246 // Deallocate the undo object we just processed
2247 undo_stack_tail = undo_entry->prev;
2250 // For chained operations, continue popping all the way down the chain.
2252 undo_pop(); // Follow the undo chain if one exists
2256 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2257 // Flush any queued objects to the undo stack
2258 static void undo_queue_commit(void)
2260 // Pushes the queue object onto the undo stack
2262 // Deleted character undo events grow from the end
2263 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2265 (undo_queue_state | UNDO_USE_SPOS)
2267 undo_queue_state = UNDO_EMPTY;
2273 #endif /* ENABLE_FEATURE_VI_UNDO */
2275 // open a hole in text[]
2276 // might reallocate text[]! use p += text_hole_make(p, ...),
2277 // and be careful to not use pointers into potentially freed text[]!
2278 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2284 end += size; // adjust the new END
2285 if (end >= (text + text_size)) {
2287 text_size += end - (text + text_size) + 10240;
2288 new_text = xrealloc(text, text_size);
2289 bias = (new_text - text);
2290 screenbegin += bias;
2294 #if ENABLE_FEATURE_VI_YANKMARK
2297 for (i = 0; i < ARRAY_SIZE(mark); i++)
2304 memmove(p + size, p, end - size - p);
2305 memset(p, ' ', size); // clear new hole
2309 // close a hole in text[]
2310 // "undo" value indicates if this operation should be undo-able
2311 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2316 // move forwards, from beginning
2320 if (q < p) { // they are backward- swap them
2324 hole_size = q - p + 1;
2326 #if ENABLE_FEATURE_VI_UNDO
2331 undo_push(p, hole_size, UNDO_DEL);
2333 case ALLOW_UNDO_CHAIN:
2334 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2336 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2337 case ALLOW_UNDO_QUEUED:
2338 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2344 if (src < text || src > end)
2346 if (dest < text || dest >= end)
2350 goto thd_atend; // just delete the end of the buffer
2351 memmove(dest, src, cnt);
2353 end = end - hole_size; // adjust the new END
2355 dest = end - 1; // make sure dest in below end-1
2357 dest = end = text; // keep pointers valid
2362 // copy text into register, then delete text.
2363 // if dist <= 0, do not include, or go past, a NewLine
2365 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2369 // make sure start <= stop
2371 // they are backwards, reverse them
2377 // we cannot cross NL boundaries
2381 // dont go past a NewLine
2382 for (; p + 1 <= stop; p++) {
2384 stop = p; // "stop" just before NewLine
2390 #if ENABLE_FEATURE_VI_YANKMARK
2391 text_yank(start, stop, YDreg);
2393 if (yf == YANKDEL) {
2394 p = text_hole_delete(start, stop, undo);
2399 #if ENABLE_FEATURE_VI_DOT_CMD
2400 static void start_new_cmd_q(char c)
2402 // get buffer for new cmd
2403 // if there is a current cmd count put it in the buffer first
2405 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2406 } else { // just save char c onto queue
2407 last_modifying_cmd[0] = c;
2413 static void end_cmd_q(void)
2415 #if ENABLE_FEATURE_VI_YANKMARK
2416 YDreg = 26; // go back to default Yank/Delete reg
2420 #endif /* FEATURE_VI_DOT_CMD */
2422 #if ENABLE_FEATURE_VI_YANKMARK \
2423 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2424 || ENABLE_FEATURE_VI_CRASHME
2425 // might reallocate text[]! use p += string_insert(p, ...),
2426 // and be careful to not use pointers into potentially freed text[]!
2427 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2433 #if ENABLE_FEATURE_VI_UNDO
2434 undo_push_insert(p, i, undo);
2436 bias = text_hole_make(p, i);
2439 #if ENABLE_FEATURE_VI_YANKMARK
2442 for (cnt = 0; *s != '\0'; s++) {
2446 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2453 #if ENABLE_FEATURE_VI_YANKMARK
2454 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2457 if (cnt < 0) { // they are backwards- reverse them
2461 free(reg[dest]); // if already a yank register, free it
2462 reg[dest] = xstrndup(p, cnt + 1);
2466 static char what_reg(void)
2470 c = 'D'; // default to D-reg
2471 if (0 <= YDreg && YDreg <= 25)
2472 c = 'a' + (char) YDreg;
2480 static void check_context(char cmd)
2482 // A context is defined to be "modifying text"
2483 // Any modifying command establishes a new context.
2485 if (dot < context_start || dot > context_end) {
2486 if (strchr(modifying_cmds, cmd) != NULL) {
2487 // we are trying to modify text[]- make this the current context
2488 mark[27] = mark[26]; // move cur to prev
2489 mark[26] = dot; // move local to cur
2490 context_start = prev_line(prev_line(dot));
2491 context_end = next_line(next_line(dot));
2492 //loiter= start_loiter= now;
2497 static char *swap_context(char *p) // goto new context for '' command make this the current context
2501 // the current context is in mark[26]
2502 // the previous context is in mark[27]
2503 // only swap context if other context is valid
2504 if (text <= mark[27] && mark[27] <= end - 1) {
2508 context_start = prev_line(prev_line(prev_line(p)));
2509 context_end = next_line(next_line(next_line(p)));
2513 #endif /* FEATURE_VI_YANKMARK */
2515 //----- IO Routines --------------------------------------------
2516 static int readit(void) // read (maybe cursor) key from stdin
2522 // Wait for input. TIMEOUT = -1 makes read_key wait even
2523 // on nonblocking stdin.
2524 // Note: read_key sets errno to 0 on success.
2526 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2527 if (c == -1) { // EOF/error
2528 if (errno == EAGAIN) // paranoia
2530 go_bottom_and_clear_to_eol();
2531 cookmode(); // terminal to "cooked"
2532 bb_error_msg_and_die("can't read user input");
2537 //----- IO Routines --------------------------------------------
2538 static int get_one_char(void)
2542 #if ENABLE_FEATURE_VI_DOT_CMD
2544 // we are not adding to the q.
2545 // but, we may be reading from a q
2547 // there is no current q, read from STDIN
2548 c = readit(); // get the users input
2550 // there is a queue to get chars from first
2551 // careful with correct sign expansion!
2552 c = (unsigned char)*ioq++;
2554 // the end of the q, read from STDIN
2556 ioq_start = ioq = 0;
2557 c = readit(); // get the users input
2561 // adding STDIN chars to q
2562 c = readit(); // get the users input
2563 if (lmc_len >= MAX_INPUT_LEN - 1) {
2564 status_line_bold("last_modifying_cmd overrun");
2566 // add new char to q
2567 last_modifying_cmd[lmc_len++] = c;
2571 c = readit(); // get the users input
2572 #endif /* FEATURE_VI_DOT_CMD */
2576 // Get input line (uses "status line" area)
2577 static char *get_input_line(const char *prompt)
2579 // char [MAX_INPUT_LEN]
2580 #define buf get_input_line__buf
2585 strcpy(buf, prompt);
2586 last_status_cksum = 0; // force status update
2587 go_bottom_and_clear_to_eol();
2588 write1(prompt); // write out the :, /, or ? prompt
2591 while (i < MAX_INPUT_LEN) {
2593 if (c == '\n' || c == '\r' || c == 27)
2594 break; // this is end of input
2595 if (c == erase_char || c == 8 || c == 127) {
2596 // user wants to erase prev char
2598 write1("\b \b"); // erase char on screen
2599 if (i <= 0) // user backs up before b-o-l, exit
2601 } else if (c > 0 && c < 256) { // exclude Unicode
2602 // (TODO: need to handle Unicode)
2613 // might reallocate text[]!
2614 static int file_insert(const char *fn, char *p, int initial)
2618 struct stat statbuf;
2625 fd = open(fn, O_RDONLY);
2628 status_line_bold_errno(fn);
2633 if (fstat(fd, &statbuf) < 0) {
2634 status_line_bold_errno(fn);
2637 if (!S_ISREG(statbuf.st_mode)) {
2638 status_line_bold("'%s' is not a regular file", fn);
2641 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2642 p += text_hole_make(p, size);
2643 cnt = full_read(fd, p, size);
2645 status_line_bold_errno(fn);
2646 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2647 } else if (cnt < size) {
2648 // There was a partial read, shrink unused space
2649 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2650 status_line_bold("can't read '%s'", fn);
2655 #if ENABLE_FEATURE_VI_READONLY
2657 && ((access(fn, W_OK) < 0) ||
2658 // root will always have access()
2659 // so we check fileperms too
2660 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2663 SET_READONLY_FILE(readonly_mode);
2669 static int file_write(char *fn, char *first, char *last)
2671 int fd, cnt, charcnt;
2674 status_line_bold("No current filename");
2677 // By popular request we do not open file with O_TRUNC,
2678 // but instead ftruncate() it _after_ successful write.
2679 // Might reduce amount of data lost on power fail etc.
2680 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2683 cnt = last - first + 1;
2684 charcnt = full_write(fd, first, cnt);
2685 ftruncate(fd, charcnt);
2686 if (charcnt == cnt) {
2688 //modified_count = FALSE;
2696 //----- Flash the screen --------------------------------------
2697 static void flash(int h)
2706 static void indicate_error(void)
2708 #if ENABLE_FEATURE_VI_CRASHME
2719 static int bufsum(char *buf, int count)
2722 char *e = buf + count;
2725 sum += (unsigned char) *buf++;
2729 //----- Draw the status line at bottom of the screen -------------
2730 static void show_status_line(void)
2732 int cnt = 0, cksum = 0;
2734 // either we already have an error or status message, or we
2736 if (!have_status_msg) {
2737 cnt = format_edit_status();
2738 cksum = bufsum(status_buffer, cnt);
2740 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2741 last_status_cksum = cksum; // remember if we have seen this line
2742 go_bottom_and_clear_to_eol();
2743 write1(status_buffer);
2744 if (have_status_msg) {
2745 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2747 have_status_msg = 0;
2750 have_status_msg = 0;
2752 place_cursor(crow, ccol); // put cursor back in correct place
2757 //----- format the status buffer, the bottom line of screen ------
2758 // format status buffer, with STANDOUT mode
2759 static void status_line_bold(const char *format, ...)
2763 va_start(args, format);
2764 strcpy(status_buffer, ESC_BOLD_TEXT);
2765 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
2766 strcat(status_buffer, ESC_NORM_TEXT);
2769 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
2772 static void status_line_bold_errno(const char *fn)
2774 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
2777 // format status buffer
2778 static void status_line(const char *format, ...)
2782 va_start(args, format);
2783 vsprintf(status_buffer, format, args);
2786 have_status_msg = 1;
2789 // copy s to buf, convert unprintable
2790 static void print_literal(char *buf, const char *s)
2804 c_is_no_print = (c & 0x80) && !Isprint(c);
2805 if (c_is_no_print) {
2806 strcpy(d, ESC_NORM_TEXT);
2807 d += sizeof(ESC_NORM_TEXT)-1;
2810 if (c < ' ' || c == 0x7f) {
2818 if (c_is_no_print) {
2819 strcpy(d, ESC_BOLD_TEXT);
2820 d += sizeof(ESC_BOLD_TEXT)-1;
2826 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2831 static void not_implemented(const char *s)
2833 char buf[MAX_INPUT_LEN];
2835 print_literal(buf, s);
2836 status_line_bold("\'%s\' is not implemented", buf);
2839 // show file status on status line
2840 static int format_edit_status(void)
2842 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2844 #define tot format_edit_status__tot
2846 int cur, percent, ret, trunc_at;
2848 // modified_count is now a counter rather than a flag. this
2849 // helps reduce the amount of line counting we need to do.
2850 // (this will cause a mis-reporting of modified status
2851 // once every MAXINT editing operations.)
2853 // it would be nice to do a similar optimization here -- if
2854 // we haven't done a motion that could have changed which line
2855 // we're on, then we shouldn't have to do this count_lines()
2856 cur = count_lines(text, dot);
2858 // count_lines() is expensive.
2859 // Call it only if something was changed since last time
2861 if (modified_count != last_modified_count) {
2862 tot = cur + count_lines(dot, end - 1) - 1;
2863 last_modified_count = modified_count;
2866 // current line percent
2867 // ------------- ~~ ----------
2870 percent = (100 * cur) / tot;
2876 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2877 columns : STATUS_BUFFER_LEN-1;
2879 ret = snprintf(status_buffer, trunc_at+1,
2880 #if ENABLE_FEATURE_VI_READONLY
2881 "%c %s%s%s %d/%d %d%%",
2883 "%c %s%s %d/%d %d%%",
2885 cmd_mode_indicator[cmd_mode & 3],
2886 (current_filename != NULL ? current_filename : "No file"),
2887 #if ENABLE_FEATURE_VI_READONLY
2888 (readonly_mode ? " [Readonly]" : ""),
2890 (modified_count ? " [Modified]" : ""),
2893 if (ret >= 0 && ret < trunc_at)
2894 return ret; // it all fit
2896 return trunc_at; // had to truncate
2900 //----- Force refresh of all Lines -----------------------------
2901 static void redraw(int full_screen)
2903 // cursor to top,left; clear to the end of screen
2904 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
2905 screen_erase(); // erase the internal screen buffer
2906 last_status_cksum = 0; // force status update
2907 refresh(full_screen); // this will redraw the entire display
2911 //----- Format a text[] line into a buffer ---------------------
2912 static char* format_line(char *src /*, int li*/)
2917 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2919 c = '~'; // char in col 0 in non-existent lines is '~'
2921 while (co < columns + tabstop) {
2922 // have we gone past the end?
2927 if ((c & 0x80) && !Isprint(c)) {
2930 if (c < ' ' || c == 0x7f) {
2934 while ((co % tabstop) != (tabstop - 1)) {
2942 c += '@'; // Ctrl-X -> 'X'
2947 // discard scrolled-off-to-the-left portion,
2948 // in tabstop-sized pieces
2949 if (ofs >= tabstop && co >= tabstop) {
2950 memmove(dest, dest + tabstop, co);
2957 // check "short line, gigantic offset" case
2960 // discard last scrolled off part
2963 // fill the rest with spaces
2965 memset(&dest[co], ' ', columns - co);
2969 //----- Refresh the changed screen lines -----------------------
2970 // Copy the source line from text[] into the buffer and note
2971 // if the current screenline is different from the new buffer.
2972 // If they differ then that line needs redrawing on the terminal.
2974 static void refresh(int full_screen)
2976 #define old_offset refresh__old_offset
2979 char *tp, *sp; // pointer into text[] and screen[]
2981 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2982 unsigned c = columns, r = rows;
2983 query_screen_dimensions();
2984 #if ENABLE_FEATURE_VI_USE_SIGNALS
2985 full_screen |= (c - columns) | (r - rows);
2987 if (c != columns || r != rows) {
2989 // update screen memory since SIGWINCH won't have done it
2990 new_screen(rows, columns);
2994 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2995 tp = screenbegin; // index into text[] of top line
2997 // compare text[] to screen[] and mark screen[] lines that need updating
2998 for (li = 0; li < rows - 1; li++) {
2999 int cs, ce; // column start & end
3001 // format current text line
3002 out_buf = format_line(tp /*, li*/);
3004 // skip to the end of the current text[] line
3006 char *t = memchr(tp, '\n', end - tp);
3007 if (!t) t = end - 1;
3011 // see if there are any changes between virtual screen and out_buf
3012 changed = FALSE; // assume no change
3015 sp = &screen[li * columns]; // start of screen line
3017 // force re-draw of every single column from 0 - columns-1
3020 // compare newly formatted buffer with virtual screen
3021 // look forward for first difference between buf and screen
3022 for (; cs <= ce; cs++) {
3023 if (out_buf[cs] != sp[cs]) {
3024 changed = TRUE; // mark for redraw
3029 // look backward for last difference between out_buf and screen
3030 for (; ce >= cs; ce--) {
3031 if (out_buf[ce] != sp[ce]) {
3032 changed = TRUE; // mark for redraw
3036 // now, cs is index of first diff, and ce is index of last diff
3038 // if horz offset has changed, force a redraw
3039 if (offset != old_offset) {
3044 // make a sanity check of columns indexes
3046 if (ce > columns - 1) ce = columns - 1;
3047 if (cs > ce) { cs = 0; ce = columns - 1; }
3048 // is there a change between virtual screen and out_buf
3050 // copy changed part of buffer to virtual screen
3051 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3052 place_cursor(li, cs);
3053 // write line out to terminal
3054 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3058 place_cursor(crow, ccol);
3060 old_offset = offset;
3064 #if ENABLE_FEATURE_VI_USE_SIGNALS
3065 static void winch_handler(int sig UNUSED_PARAM)
3067 int save_errno = errno;
3068 // FIXME: do it in main loop!!!
3069 signal(SIGWINCH, winch_handler);
3070 query_screen_dimensions();
3071 new_screen(rows, columns); // get memory for virtual screen
3072 redraw(TRUE); // re-draw the screen
3075 static void tstp_handler(int sig UNUSED_PARAM)
3077 int save_errno = errno;
3079 // ioctl inside cookmode() was seen to generate SIGTTOU,
3080 // stopping us too early. Prevent that:
3081 signal(SIGTTOU, SIG_IGN);
3083 go_bottom_and_clear_to_eol();
3084 cookmode(); // terminal to "cooked"
3087 //signal(SIGTSTP, SIG_DFL);
3089 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
3090 //signal(SIGTSTP, tstp_handler);
3092 // we have been "continued" with SIGCONT, restore screen and termios
3093 rawmode(); // terminal to "raw"
3094 last_status_cksum = 0; // force status update
3095 redraw(TRUE); // re-draw the screen
3099 static void int_handler(int sig)
3101 signal(SIGINT, int_handler);
3102 siglongjmp(restart, sig);
3104 #endif /* FEATURE_VI_USE_SIGNALS */
3106 static void do_cmd(int c);
3108 static int find_range(char **start, char **stop, char c)
3110 char *save_dot, *p, *q, *t;
3111 int cnt, multiline = 0;
3116 if (strchr("cdy><", c)) {
3117 // these cmds operate on whole lines
3118 p = q = begin_line(p);
3119 for (cnt = 1; cnt < cmdcnt; cnt++) {
3123 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3124 // These cmds operate on char positions
3125 do_cmd(c); // execute movement cmd
3127 } else if (strchr("wW", c)) {
3128 do_cmd(c); // execute movement cmd
3129 // if we are at the next word's first char
3130 // step back one char
3131 // but check the possibilities when it is true
3132 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3133 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3134 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3135 dot--; // move back off of next word
3136 if (dot > text && *dot == '\n')
3137 dot--; // stay off NL
3139 } else if (strchr("H-k{", c)) {
3140 // these operate on multi-lines backwards
3141 q = end_line(dot); // find NL
3142 do_cmd(c); // execute movement cmd
3145 } else if (strchr("L+j}\r\n", c)) {
3146 // these operate on multi-lines forwards
3147 p = begin_line(dot);
3148 do_cmd(c); // execute movement cmd
3149 dot_end(); // find NL
3152 // nothing -- this causes any other values of c to
3153 // represent the one-character range under the
3154 // cursor. this is correct for ' ' and 'l', but
3155 // perhaps no others.
3164 // backward char movements don't include start position
3165 if (q > p && strchr("^0bBh\b\177", c)) q--;
3168 for (t = p; t <= q; t++) {
3181 //---------------------------------------------------------------------
3182 //----- the Ascii Chart -----------------------------------------------
3183 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3184 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3185 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3186 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3187 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3188 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3189 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3190 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3191 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3192 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3193 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3194 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3195 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3196 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3197 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3198 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3199 //---------------------------------------------------------------------
3201 //----- Execute a Vi Command -----------------------------------
3202 static void do_cmd(int c)
3204 char *p, *q, *save_dot;
3210 // c1 = c; // quiet the compiler
3211 // cnt = yf = 0; // quiet the compiler
3212 // p = q = save_dot = buf; // quiet the compiler
3213 memset(buf, '\0', sizeof(buf));
3217 // if this is a cursor key, skip these checks
3225 case KEYCODE_PAGEUP:
3226 case KEYCODE_PAGEDOWN:
3227 case KEYCODE_DELETE:
3231 if (cmd_mode == 2) {
3232 // flip-flop Insert/Replace mode
3233 if (c == KEYCODE_INSERT)
3235 // we are 'R'eplacing the current *dot with new char
3237 // don't Replace past E-o-l
3238 cmd_mode = 1; // convert to insert
3239 undo_queue_commit();
3241 if (1 <= c || Isprint(c)) {
3243 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3244 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3249 if (cmd_mode == 1) {
3250 // hitting "Insert" twice means "R" replace mode
3251 if (c == KEYCODE_INSERT) goto dc5;
3252 // insert the char c at "dot"
3253 if (1 <= c || Isprint(c)) {
3254 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3269 #if ENABLE_FEATURE_VI_CRASHME
3270 case 0x14: // dc4 ctrl-T
3271 crashme = (crashme == 0) ? 1 : 0;
3301 default: // unrecognized command
3304 not_implemented(buf);
3305 end_cmd_q(); // stop adding to q
3306 case 0x00: // nul- ignore
3308 case 2: // ctrl-B scroll up full screen
3309 case KEYCODE_PAGEUP: // Cursor Key Page Up
3310 dot_scroll(rows - 2, -1);
3312 case 4: // ctrl-D scroll down half screen
3313 dot_scroll((rows - 2) / 2, 1);
3315 case 5: // ctrl-E scroll down one line
3318 case 6: // ctrl-F scroll down full screen
3319 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3320 dot_scroll(rows - 2, 1);
3322 case 7: // ctrl-G show current status
3323 last_status_cksum = 0; // force status update
3325 case 'h': // h- move left
3326 case KEYCODE_LEFT: // cursor key Left
3327 case 8: // ctrl-H- move left (This may be ERASE char)
3328 case 0x7f: // DEL- move left (This may be ERASE char)
3331 } while (--cmdcnt > 0);
3333 case 10: // Newline ^J
3334 case 'j': // j- goto next line, same col
3335 case KEYCODE_DOWN: // cursor key Down
3337 dot_next(); // go to next B-o-l
3338 // try stay in same col
3339 dot = move_to_col(dot, ccol + offset);
3340 } while (--cmdcnt > 0);
3342 case 12: // ctrl-L force redraw whole screen
3343 case 18: // ctrl-R force redraw
3344 redraw(TRUE); // this will redraw the entire display
3346 case 13: // Carriage Return ^M
3347 case '+': // +- goto next line
3351 } while (--cmdcnt > 0);
3353 case 21: // ctrl-U scroll up half screen
3354 dot_scroll((rows - 2) / 2, -1);
3356 case 25: // ctrl-Y scroll up one line
3362 cmd_mode = 0; // stop insrting
3363 undo_queue_commit();
3365 last_status_cksum = 0; // force status update
3367 case ' ': // move right
3368 case 'l': // move right
3369 case KEYCODE_RIGHT: // Cursor Key Right
3372 } while (--cmdcnt > 0);
3374 #if ENABLE_FEATURE_VI_YANKMARK
3375 case '"': // "- name a register to use for Delete/Yank
3376 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3377 if ((unsigned)c1 <= 25) { // a-z?
3383 case '\'': // '- goto a specific mark
3384 c1 = (get_one_char() | 0x20);
3385 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3389 if (text <= q && q < end) {
3391 dot_begin(); // go to B-o-l
3394 } else if (c1 == '\'') { // goto previous context
3395 dot = swap_context(dot); // swap current and previous context
3396 dot_begin(); // go to B-o-l
3402 case 'm': // m- Mark a line
3403 // this is really stupid. If there are any inserts or deletes
3404 // between text[0] and dot then this mark will not point to the
3405 // correct location! It could be off by many lines!
3406 // Well..., at least its quick and dirty.
3407 c1 = (get_one_char() | 0x20) - 'a';
3408 if ((unsigned)c1 <= 25) { // a-z?
3409 // remember the line
3415 case 'P': // P- Put register before
3416 case 'p': // p- put register after
3419 status_line_bold("Nothing in register %c", what_reg());
3422 // are we putting whole lines or strings
3423 if (strchr(p, '\n') != NULL) {
3425 dot_begin(); // putting lines- Put above
3428 // are we putting after very last line?
3429 if (end_line(dot) == (end - 1)) {
3430 dot = end; // force dot to end of text[]
3432 dot_next(); // next line, then put before
3437 dot_right(); // move to right, can move to NL
3439 string_insert(dot, p, ALLOW_UNDO); // insert the string
3440 end_cmd_q(); // stop adding to q
3442 case 'U': // U- Undo; replace current line with original version
3443 if (reg[Ureg] != NULL) {
3444 p = begin_line(dot);
3446 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3447 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3452 #endif /* FEATURE_VI_YANKMARK */
3453 #if ENABLE_FEATURE_VI_UNDO
3454 case 'u': // u- undo last operation
3458 case '$': // $- goto end of line
3459 case KEYCODE_END: // Cursor Key End
3461 dot = end_line(dot);
3467 case '%': // %- find matching char of pair () [] {}
3468 for (q = dot; q < end && *q != '\n'; q++) {
3469 if (strchr("()[]{}", *q) != NULL) {
3470 // we found half of a pair
3471 p = find_pair(q, *q);
3483 case 'f': // f- forward to a user specified char
3484 last_forward_char = get_one_char(); // get the search char
3486 // dont separate these two commands. 'f' depends on ';'
3488 //**** fall through to ... ';'
3489 case ';': // ;- look at rest of line for last forward char
3491 if (last_forward_char == 0)
3494 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3497 if (*q == last_forward_char)
3499 } while (--cmdcnt > 0);
3501 case ',': // repeat latest 'f' in opposite direction
3502 if (last_forward_char == 0)
3506 while (q >= text && *q != '\n' && *q != last_forward_char) {
3509 if (q >= text && *q == last_forward_char)
3511 } while (--cmdcnt > 0);
3514 case '-': // -- goto prev line
3518 } while (--cmdcnt > 0);
3520 #if ENABLE_FEATURE_VI_DOT_CMD
3521 case '.': // .- repeat the last modifying command
3522 // Stuff the last_modifying_cmd back into stdin
3523 // and let it be re-executed.
3525 last_modifying_cmd[lmc_len] = 0;
3526 ioq = ioq_start = xstrdup(last_modifying_cmd);
3530 #if ENABLE_FEATURE_VI_SEARCH
3531 case '?': // /- search for a pattern
3532 case '/': // /- search for a pattern
3535 q = get_input_line(buf); // get input line- use "status line"
3536 if (q[0] && !q[1]) {
3537 if (last_search_pattern[0])
3538 last_search_pattern[0] = c;
3539 goto dc3; // if no pat re-use old pat
3541 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3542 // there is a new pat
3543 free(last_search_pattern);
3544 last_search_pattern = xstrdup(q);
3545 goto dc3; // now find the pattern
3547 // user changed mind and erased the "/"- do nothing
3549 case 'N': // N- backward search for last pattern
3550 dir = BACK; // assume BACKWARD search
3552 if (last_search_pattern[0] == '?') {
3556 goto dc4; // now search for pattern
3558 case 'n': // n- repeat search for last pattern
3559 // search rest of text[] starting at next char
3560 // if search fails return orignal "p" not the "p+1" address
3564 dir = FORWARD; // assume FORWARD search
3566 if (last_search_pattern[0] == '?') {
3571 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3573 dot = q; // good search, update "dot"
3577 // no pattern found between "dot" and "end"- continue at top
3582 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3583 if (q != NULL) { // found something
3584 dot = q; // found new pattern- goto it
3585 msg = "search hit BOTTOM, continuing at TOP";
3587 msg = "search hit TOP, continuing at BOTTOM";
3590 msg = "Pattern not found";
3594 status_line_bold("%s", msg);
3595 } while (--cmdcnt > 0);
3597 case '{': // {- move backward paragraph
3598 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3599 if (q != NULL) { // found blank line
3600 dot = next_line(q); // move to next blank line
3603 case '}': // }- move forward paragraph
3604 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3605 if (q != NULL) { // found blank line
3606 dot = next_line(q); // move to next blank line
3609 #endif /* FEATURE_VI_SEARCH */
3610 case '0': // 0- goto beginning of line
3620 if (c == '0' && cmdcnt < 1) {
3621 dot_begin(); // this was a standalone zero
3623 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3626 case ':': // :- the colon mode commands
3627 p = get_input_line(":"); // get input line- use "status line"
3628 colon(p); // execute the command
3630 case '<': // <- Left shift something
3631 case '>': // >- Right shift something
3632 cnt = count_lines(text, dot); // remember what line we are on
3633 c1 = get_one_char(); // get the type of thing to delete
3634 find_range(&p, &q, c1);
3635 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3638 i = count_lines(p, q); // # of lines we are shifting
3639 for ( ; i > 0; i--, p = next_line(p)) {
3641 // shift left- remove tab or 8 spaces
3643 // shrink buffer 1 char
3644 text_hole_delete(p, p, NO_UNDO);
3645 } else if (*p == ' ') {
3646 // we should be calculating columns, not just SPACE
3647 for (j = 0; *p == ' ' && j < tabstop; j++) {
3648 text_hole_delete(p, p, NO_UNDO);
3651 } else if (c == '>') {
3652 // shift right -- add tab or 8 spaces
3653 char_insert(p, '\t', ALLOW_UNDO);
3656 dot = find_line(cnt); // what line were we on
3658 end_cmd_q(); // stop adding to q
3660 case 'A': // A- append at e-o-l
3661 dot_end(); // go to e-o-l
3662 //**** fall through to ... 'a'
3663 case 'a': // a- append after current char
3668 case 'B': // B- back a blank-delimited Word
3669 case 'E': // E- end of a blank-delimited word
3670 case 'W': // W- forward a blank-delimited word
3675 if (c == 'W' || isspace(dot[dir])) {
3676 dot = skip_thing(dot, 1, dir, S_TO_WS);
3677 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3680 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3681 } while (--cmdcnt > 0);
3683 case 'C': // C- Change to e-o-l
3684 case 'D': // D- delete to e-o-l
3686 dot = dollar_line(dot); // move to before NL
3687 // copy text into a register and delete
3688 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3690 goto dc_i; // start inserting
3691 #if ENABLE_FEATURE_VI_DOT_CMD
3693 end_cmd_q(); // stop adding to q
3696 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3697 c1 = get_one_char();
3700 // c1 < 0 if the key was special. Try "g<up-arrow>"
3701 // TODO: if Unicode?
3702 buf[1] = (c1 >= 0 ? c1 : '*');
3704 not_implemented(buf);
3710 case 'G': // G- goto to a line number (default= E-O-F)
3711 dot = end - 1; // assume E-O-F
3713 dot = find_line(cmdcnt); // what line is #cmdcnt
3717 case 'H': // H- goto top line on screen
3719 if (cmdcnt > (rows - 1)) {
3720 cmdcnt = (rows - 1);
3727 case 'I': // I- insert before first non-blank
3730 //**** fall through to ... 'i'
3731 case 'i': // i- insert before current char
3732 case KEYCODE_INSERT: // Cursor Key Insert
3734 cmd_mode = 1; // start inserting
3735 undo_queue_commit(); // commit queue when cmd_mode changes
3737 case 'J': // J- join current and next lines together
3739 dot_end(); // move to NL
3740 if (dot < end - 1) { // make sure not last char in text[]
3741 #if ENABLE_FEATURE_VI_UNDO
3742 undo_push(dot, 1, UNDO_DEL);
3743 *dot++ = ' '; // replace NL with space
3744 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3749 while (isblank(*dot)) { // delete leading WS
3750 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3753 } while (--cmdcnt > 0);
3754 end_cmd_q(); // stop adding to q
3756 case 'L': // L- goto bottom line on screen
3758 if (cmdcnt > (rows - 1)) {
3759 cmdcnt = (rows - 1);
3767 case 'M': // M- goto middle line on screen
3769 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3770 dot = next_line(dot);
3772 case 'O': // O- open a empty line above
3774 p = begin_line(dot);
3775 if (p[-1] == '\n') {
3777 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3779 dot = char_insert(dot, '\n', ALLOW_UNDO);
3782 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
3787 case 'R': // R- continuous Replace char
3790 undo_queue_commit();
3792 case KEYCODE_DELETE:
3794 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3796 case 'X': // X- delete char before dot
3797 case 'x': // x- delete the current char
3798 case 's': // s- substitute the current char
3803 if (dot[dir] != '\n') {
3805 dot--; // delete prev char
3806 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3808 } while (--cmdcnt > 0);
3809 end_cmd_q(); // stop adding to q
3811 goto dc_i; // start inserting
3813 case 'Z': // Z- if modified, {write}; exit
3814 // ZZ means to save file (if necessary), then exit
3815 c1 = get_one_char();
3820 if (modified_count) {
3821 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3822 status_line_bold("'%s' is read only", current_filename);
3825 cnt = file_write(current_filename, text, end - 1);
3828 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3829 } else if (cnt == (end - 1 - text + 1)) {
3836 case '^': // ^- move to first non-blank on line
3840 case 'b': // b- back a word
3841 case 'e': // e- end of word
3846 if ((dot + dir) < text || (dot + dir) > end - 1)
3849 if (isspace(*dot)) {
3850 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3852 if (isalnum(*dot) || *dot == '_') {
3853 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3854 } else if (ispunct(*dot)) {
3855 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3857 } while (--cmdcnt > 0);
3859 case 'c': // c- change something
3860 case 'd': // d- delete something
3861 #if ENABLE_FEATURE_VI_YANKMARK
3862 case 'y': // y- yank something
3863 case 'Y': // Y- Yank a line
3866 int yf, ml, whole = 0;
3867 yf = YANKDEL; // assume either "c" or "d"
3868 #if ENABLE_FEATURE_VI_YANKMARK
3869 if (c == 'y' || c == 'Y')
3874 c1 = get_one_char(); // get the type of thing to delete
3875 // determine range, and whether it spans lines
3876 ml = find_range(&p, &q, c1);
3878 if (c1 == 27) { // ESC- user changed mind and wants out
3879 c = c1 = 27; // Escape- do nothing
3880 } else if (strchr("wW", c1)) {
3882 // don't include trailing WS as part of word
3883 while (isblank(*q)) {
3884 if (q <= text || q[-1] == '\n')
3889 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3890 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3891 // partial line copy text into a register and delete
3892 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
3893 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3894 // whole line copy text into a register and delete
3895 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
3898 // could not recognize object
3899 c = c1 = 27; // error-
3905 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3906 // on the last line of file don't move to prev line
3907 if (whole && dot != (end-1)) {
3910 } else if (c == 'd') {
3916 // if CHANGING, not deleting, start inserting after the delete
3918 strcpy(buf, "Change");
3919 goto dc_i; // start inserting
3922 strcpy(buf, "Delete");
3924 #if ENABLE_FEATURE_VI_YANKMARK
3925 if (c == 'y' || c == 'Y') {
3926 strcpy(buf, "Yank");
3930 for (cnt = 0; p <= q; p++) {
3934 status_line("%s %u lines (%u chars) using [%c]",
3935 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3937 end_cmd_q(); // stop adding to q
3941 case 'k': // k- goto prev line, same col
3942 case KEYCODE_UP: // cursor key Up
3945 dot = move_to_col(dot, ccol + offset); // try stay in same col
3946 } while (--cmdcnt > 0);
3948 case 'r': // r- replace the current char with user input
3949 c1 = get_one_char(); // get the replacement char
3951 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3952 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3955 end_cmd_q(); // stop adding to q
3957 case 't': // t- move to char prior to next x
3958 last_forward_char = get_one_char();
3960 if (*dot == last_forward_char)
3962 last_forward_char = 0;
3964 case 'w': // w- forward a word
3966 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3967 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3968 } else if (ispunct(*dot)) { // we are on PUNCT
3969 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3972 dot++; // move over word
3973 if (isspace(*dot)) {
3974 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3976 } while (--cmdcnt > 0);
3979 c1 = get_one_char(); // get the replacement char
3982 cnt = (rows - 2) / 2; // put dot at center
3984 cnt = rows - 2; // put dot at bottom
3985 screenbegin = begin_line(dot); // start dot at top
3986 dot_scroll(cnt, -1);
3988 case '|': // |- move to column "cmdcnt"
3989 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3991 case '~': // ~- flip the case of letters a-z -> A-Z
3993 #if ENABLE_FEATURE_VI_UNDO
3994 if (islower(*dot)) {
3995 undo_push(dot, 1, UNDO_DEL);
3996 *dot = toupper(*dot);
3997 undo_push(dot, 1, UNDO_INS_CHAIN);
3998 } else if (isupper(*dot)) {
3999 undo_push(dot, 1, UNDO_DEL);
4000 *dot = tolower(*dot);
4001 undo_push(dot, 1, UNDO_INS_CHAIN);
4004 if (islower(*dot)) {
4005 *dot = toupper(*dot);
4007 } else if (isupper(*dot)) {
4008 *dot = tolower(*dot);
4013 } while (--cmdcnt > 0);
4014 end_cmd_q(); // stop adding to q
4016 //----- The Cursor and Function Keys -----------------------------
4017 case KEYCODE_HOME: // Cursor Key Home
4020 // The Fn keys could point to do_macro which could translate them
4022 case KEYCODE_FUN1: // Function Key F1
4023 case KEYCODE_FUN2: // Function Key F2
4024 case KEYCODE_FUN3: // Function Key F3
4025 case KEYCODE_FUN4: // Function Key F4
4026 case KEYCODE_FUN5: // Function Key F5
4027 case KEYCODE_FUN6: // Function Key F6
4028 case KEYCODE_FUN7: // Function Key F7
4029 case KEYCODE_FUN8: // Function Key F8
4030 case KEYCODE_FUN9: // Function Key F9
4031 case KEYCODE_FUN10: // Function Key F10
4032 case KEYCODE_FUN11: // Function Key F11
4033 case KEYCODE_FUN12: // Function Key F12
4039 // if text[] just became empty, add back an empty line
4041 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4044 // it is OK for dot to exactly equal to end, otherwise check dot validity
4046 dot = bound_dot(dot); // make sure "dot" is valid
4048 #if ENABLE_FEATURE_VI_YANKMARK
4049 check_context(c); // update the current context
4053 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4054 cnt = dot - begin_line(dot);
4055 // Try to stay off of the Newline
4056 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4060 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4061 #if ENABLE_FEATURE_VI_CRASHME
4062 static int totalcmds = 0;
4063 static int Mp = 85; // Movement command Probability
4064 static int Np = 90; // Non-movement command Probability
4065 static int Dp = 96; // Delete command Probability
4066 static int Ip = 97; // Insert command Probability
4067 static int Yp = 98; // Yank command Probability
4068 static int Pp = 99; // Put command Probability
4069 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4070 static const char chars[20] = "\t012345 abcdABCD-=.$";
4071 static const char *const words[20] = {
4072 "this", "is", "a", "test",
4073 "broadcast", "the", "emergency", "of",
4074 "system", "quick", "brown", "fox",
4075 "jumped", "over", "lazy", "dogs",
4076 "back", "January", "Febuary", "March"
4078 static const char *const lines[20] = {
4079 "You should have received a copy of the GNU General Public License\n",
4080 "char c, cm, *cmd, *cmd1;\n",
4081 "generate a command by percentages\n",
4082 "Numbers may be typed as a prefix to some commands.\n",
4083 "Quit, discarding changes!\n",
4084 "Forced write, if permission originally not valid.\n",
4085 "In general, any ex or ed command (such as substitute or delete).\n",
4086 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4087 "Please get w/ me and I will go over it with you.\n",
4088 "The following is a list of scheduled, committed changes.\n",
4089 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4090 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4091 "Any question about transactions please contact Sterling Huxley.\n",
4092 "I will try to get back to you by Friday, December 31.\n",
4093 "This Change will be implemented on Friday.\n",
4094 "Let me know if you have problems accessing this;\n",
4095 "Sterling Huxley recently added you to the access list.\n",
4096 "Would you like to go to lunch?\n",
4097 "The last command will be automatically run.\n",
4098 "This is too much english for a computer geek.\n",
4100 static char *multilines[20] = {
4101 "You should have received a copy of the GNU General Public License\n",
4102 "char c, cm, *cmd, *cmd1;\n",
4103 "generate a command by percentages\n",
4104 "Numbers may be typed as a prefix to some commands.\n",
4105 "Quit, discarding changes!\n",
4106 "Forced write, if permission originally not valid.\n",
4107 "In general, any ex or ed command (such as substitute or delete).\n",
4108 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4109 "Please get w/ me and I will go over it with you.\n",
4110 "The following is a list of scheduled, committed changes.\n",
4111 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4112 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4113 "Any question about transactions please contact Sterling Huxley.\n",
4114 "I will try to get back to you by Friday, December 31.\n",
4115 "This Change will be implemented on Friday.\n",
4116 "Let me know if you have problems accessing this;\n",
4117 "Sterling Huxley recently added you to the access list.\n",
4118 "Would you like to go to lunch?\n",
4119 "The last command will be automatically run.\n",
4120 "This is too much english for a computer geek.\n",
4123 // create a random command to execute
4124 static void crash_dummy()
4126 static int sleeptime; // how long to pause between commands
4127 char c, cm, *cmd, *cmd1;
4128 int i, cnt, thing, rbi, startrbi, percent;
4130 // "dot" movement commands
4131 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4133 // is there already a command running?
4134 if (readbuffer[0] > 0)
4137 readbuffer[0] = 'X';
4139 sleeptime = 0; // how long to pause between commands
4140 memset(readbuffer, '\0', sizeof(readbuffer));
4141 // generate a command by percentages
4142 percent = (int) lrand48() % 100; // get a number from 0-99
4143 if (percent < Mp) { // Movement commands
4144 // available commands
4147 } else if (percent < Np) { // non-movement commands
4148 cmd = "mz<>\'\""; // available commands
4150 } else if (percent < Dp) { // Delete commands
4151 cmd = "dx"; // available commands
4153 } else if (percent < Ip) { // Inset commands
4154 cmd = "iIaAsrJ"; // available commands
4156 } else if (percent < Yp) { // Yank commands
4157 cmd = "yY"; // available commands
4159 } else if (percent < Pp) { // Put commands
4160 cmd = "pP"; // available commands
4163 // We do not know how to handle this command, try again
4167 // randomly pick one of the available cmds from "cmd[]"
4168 i = (int) lrand48() % strlen(cmd);
4170 if (strchr(":\024", cm))
4171 goto cd0; // dont allow colon or ctrl-T commands
4172 readbuffer[rbi++] = cm; // put cmd into input buffer
4174 // now we have the command-
4175 // there are 1, 2, and multi char commands
4176 // find out which and generate the rest of command as necessary
4177 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4178 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4179 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4180 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4182 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4184 readbuffer[rbi++] = c; // add movement to input buffer
4186 if (strchr("iIaAsc", cm)) { // multi-char commands
4188 // change some thing
4189 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4191 readbuffer[rbi++] = c; // add movement to input buffer
4193 thing = (int) lrand48() % 4; // what thing to insert
4194 cnt = (int) lrand48() % 10; // how many to insert
4195 for (i = 0; i < cnt; i++) {
4196 if (thing == 0) { // insert chars
4197 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4198 } else if (thing == 1) { // insert words
4199 strcat(readbuffer, words[(int) lrand48() % 20]);
4200 strcat(readbuffer, " ");
4201 sleeptime = 0; // how fast to type
4202 } else if (thing == 2) { // insert lines
4203 strcat(readbuffer, lines[(int) lrand48() % 20]);
4204 sleeptime = 0; // how fast to type
4205 } else { // insert multi-lines
4206 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4207 sleeptime = 0; // how fast to type
4210 strcat(readbuffer, ESC);
4212 readbuffer[0] = strlen(readbuffer + 1);
4216 mysleep(sleeptime); // sleep 1/100 sec
4219 // test to see if there are any errors
4220 static void crash_test()
4222 static time_t oldtim;
4229 strcat(msg, "end<text ");
4231 if (end > textend) {
4232 strcat(msg, "end>textend ");
4235 strcat(msg, "dot<text ");
4238 strcat(msg, "dot>end ");
4240 if (screenbegin < text) {
4241 strcat(msg, "screenbegin<text ");
4243 if (screenbegin > end - 1) {
4244 strcat(msg, "screenbegin>end-1 ");
4248 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4249 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4251 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4252 if (d[0] == '\n' || d[0] == '\r')
4257 if (tim >= (oldtim + 3)) {
4258 sprintf(status_buffer,
4259 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4260 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4266 static void edit_file(char *fn)
4268 #if ENABLE_FEATURE_VI_YANKMARK
4269 #define cur_line edit_file__cur_line
4272 #if ENABLE_FEATURE_VI_USE_SIGNALS
4276 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4280 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4281 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4282 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4284 write1(ESC"[999;999H" ESC"[6n");
4286 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4287 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4288 uint32_t rc = (k >> 32);
4289 columns = (rc & 0x7fff);
4290 if (columns > MAX_SCR_COLS)
4291 columns = MAX_SCR_COLS;
4292 rows = ((rc >> 16) & 0x7fff);
4293 if (rows > MAX_SCR_ROWS)
4294 rows = MAX_SCR_ROWS;
4298 new_screen(rows, columns); // get memory for virtual screen
4299 init_text_buffer(fn);
4301 #if ENABLE_FEATURE_VI_YANKMARK
4302 YDreg = 26; // default Yank/Delete reg
4303 // Ureg = 27; - const // hold orig line for "U" cmd
4304 mark[26] = mark[27] = text; // init "previous context"
4307 last_forward_char = last_input_char = '\0';
4311 #if ENABLE_FEATURE_VI_USE_SIGNALS
4312 signal(SIGWINCH, winch_handler);
4313 signal(SIGTSTP, tstp_handler);
4314 sig = sigsetjmp(restart, 1);
4316 screenbegin = dot = text;
4318 // int_handler() can jump to "restart",
4319 // must install handler *after* initializing "restart"
4320 signal(SIGINT, int_handler);
4323 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4326 offset = 0; // no horizontal offset
4328 #if ENABLE_FEATURE_VI_DOT_CMD
4330 ioq = ioq_start = NULL;
4335 #if ENABLE_FEATURE_VI_COLON
4340 while ((p = initial_cmds[n]) != NULL) {
4343 p = strchr(q, '\n');
4350 free(initial_cmds[n]);
4351 initial_cmds[n] = NULL;
4356 redraw(FALSE); // dont force every col re-draw
4357 //------This is the main Vi cmd handling loop -----------------------
4358 while (editing > 0) {
4359 #if ENABLE_FEATURE_VI_CRASHME
4361 if ((end - text) > 1) {
4362 crash_dummy(); // generate a random command
4365 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
4371 last_input_char = c = get_one_char(); // get a cmd from user
4372 #if ENABLE_FEATURE_VI_YANKMARK
4373 // save a copy of the current line- for the 'U" command
4374 if (begin_line(dot) != cur_line) {
4375 cur_line = begin_line(dot);
4376 text_yank(begin_line(dot), end_line(dot), Ureg);
4379 #if ENABLE_FEATURE_VI_DOT_CMD
4380 // These are commands that change text[].
4381 // Remember the input for the "." command
4382 if (!adding2q && ioq_start == NULL
4383 && cmd_mode == 0 // command mode
4384 && c > '\0' // exclude NUL and non-ASCII chars
4385 && c < 0x7f // (Unicode and such)
4386 && strchr(modifying_cmds, c)
4391 do_cmd(c); // execute the user command
4393 // poll to see if there is input already waiting. if we are
4394 // not able to display output fast enough to keep up, skip
4395 // the display update until we catch up with input.
4396 if (!readbuffer[0] && mysleep(0) == 0) {
4397 // no input pending - so update output
4401 #if ENABLE_FEATURE_VI_CRASHME
4403 crash_test(); // test editor variables
4406 //-------------------------------------------------------------------
4408 go_bottom_and_clear_to_eol();
4413 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4414 int vi_main(int argc, char **argv)
4420 #if ENABLE_FEATURE_VI_UNDO
4421 /* undo_stack_tail = NULL; - already is */
4422 #if ENABLE_FEATURE_VI_UNDO_QUEUE
4423 undo_queue_state = UNDO_EMPTY;
4424 /* undo_q = 0; - already is */
4428 #if ENABLE_FEATURE_VI_CRASHME
4429 srand((long) getpid());
4431 #ifdef NO_SUCH_APPLET_YET
4432 // if we aren't "vi", we are "view"
4433 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4434 SET_READONLY_MODE(readonly_mode);
4438 // autoindent is not default in vim 7.3
4439 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4440 // 1- process $HOME/.exrc file (not inplemented yet)
4441 // 2- process EXINIT variable from environment
4442 // 3- process command line args
4443 #if ENABLE_FEATURE_VI_COLON
4445 char *p = getenv("EXINIT");
4447 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4450 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4452 #if ENABLE_FEATURE_VI_CRASHME
4457 #if ENABLE_FEATURE_VI_READONLY
4458 case 'R': // Read-only flag
4459 SET_READONLY_MODE(readonly_mode);
4462 #if ENABLE_FEATURE_VI_COLON
4463 case 'c': // cmd line vi command
4465 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4477 // The argv array can be used by the ":next" and ":rewind" commands
4481 //----- This is the main file handling loop --------------
4484 // "Save cursor, use alternate screen buffer, clear screen"
4485 write1(ESC"[?1049h");
4487 edit_file(argv[optind]); // param might be NULL
4488 if (++optind >= argc)
4491 // "Use normal screen buffer, restore cursor"
4492 write1(ESC"[?1049l");
4493 //-----------------------------------------------------------