Busybox: Upgrade to 1.21.1 (stable). lsof active.
[tomato.git] / release / src / router / busybox / editors / vi.c
blob5b5e2b0bfff64a7e912aa3e3d1cd80122d7d1b49
1 /* vi: set sw=4 ts=4: */
2 /*
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.
7 */
9 /*
10 * Things To Do:
11 * EXINIT
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
14 * add :help command
15 * :map macros
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
24 //config:config VI
25 //config: bool "vi"
26 //config: default y
27 //config: help
28 //config: 'vi' is a text editor. More specifically, it is the One True
29 //config: text editor <grin>. It does, however, have a rather steep
30 //config: learning curve. If you are not already comfortable with 'vi'
31 //config: you may wish to use something else.
32 //config:
33 //config:config FEATURE_VI_MAX_LEN
34 //config: int "Maximum screen width in vi"
35 //config: range 256 16384
36 //config: default 4096
37 //config: depends on VI
38 //config: help
39 //config: Contrary to what you may think, this is not eating much.
40 //config: Make it smaller than 4k only if you are very limited on memory.
41 //config:
42 //config:config FEATURE_VI_8BIT
43 //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
44 //config: default n
45 //config: depends on VI
46 //config: help
47 //config: If your terminal can display characters with high bit set,
48 //config: you may want to enable this. Note: vi is not Unicode-capable.
49 //config: If your terminal combines several 8-bit bytes into one character
50 //config: (as in Unicode mode), this will not work properly.
51 //config:
52 //config:config FEATURE_VI_COLON
53 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
54 //config: default y
55 //config: depends on VI
56 //config: help
57 //config: Enable a limited set of colon commands for vi. This does not
58 //config: provide an "ex" mode.
59 //config:
60 //config:config FEATURE_VI_YANKMARK
61 //config: bool "Enable yank/put commands and mark cmds"
62 //config: default y
63 //config: depends on VI
64 //config: help
65 //config: This will enable you to use yank and put, as well as mark in
66 //config: busybox vi.
67 //config:
68 //config:config FEATURE_VI_SEARCH
69 //config: bool "Enable search and replace cmds"
70 //config: default y
71 //config: depends on VI
72 //config: help
73 //config: Select this if you wish to be able to do search and replace in
74 //config: busybox vi.
75 //config:
76 //config:config FEATURE_VI_REGEX_SEARCH
77 //config: bool "Enable regex in search and replace"
78 //config: default n # Uses GNU regex, which may be unavailable. FIXME
79 //config: depends on FEATURE_VI_SEARCH
80 //config: help
81 //config: Use extended regex search.
82 //config:
83 //config:config FEATURE_VI_USE_SIGNALS
84 //config: bool "Catch signals"
85 //config: default y
86 //config: depends on VI
87 //config: help
88 //config: Selecting this option will make busybox vi signal aware. This will
89 //config: make busybox vi support SIGWINCH to deal with Window Changes, catch
90 //config: Ctrl-Z and Ctrl-C and alarms.
91 //config:
92 //config:config FEATURE_VI_DOT_CMD
93 //config: bool "Remember previous cmd and \".\" cmd"
94 //config: default y
95 //config: depends on VI
96 //config: help
97 //config: Make busybox vi remember the last command and be able to repeat it.
98 //config:
99 //config:config FEATURE_VI_READONLY
100 //config: bool "Enable -R option and \"view\" mode"
101 //config: default y
102 //config: depends on VI
103 //config: help
104 //config: Enable the read-only command line option, which allows the user to
105 //config: open a file in read-only mode.
106 //config:
107 //config:config FEATURE_VI_SETOPTS
108 //config: bool "Enable set-able options, ai ic showmatch"
109 //config: default y
110 //config: depends on VI
111 //config: help
112 //config: Enable the editor to set some (ai, ic, showmatch) options.
113 //config:
114 //config:config FEATURE_VI_SET
115 //config: bool "Support for :set"
116 //config: default y
117 //config: depends on VI
118 //config: help
119 //config: Support for ":set".
120 //config:
121 //config:config FEATURE_VI_WIN_RESIZE
122 //config: bool "Handle window resize"
123 //config: default y
124 //config: depends on VI
125 //config: help
126 //config: Make busybox vi behave nicely with terminals that get resized.
127 //config:
128 //config:config FEATURE_VI_ASK_TERMINAL
129 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
130 //config: default y
131 //config: depends on VI
132 //config: help
133 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
134 //config: this option makes vi perform a last-ditch effort to find it:
135 //config: position cursor to 999,999 and ask terminal to report real
136 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
137 //config:
138 //config: This is not clean but helps a lot on serial lines and such.
140 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
142 //kbuild:lib-$(CONFIG_VI) += vi.o
144 //usage:#define vi_trivial_usage
145 //usage: "[OPTIONS] [FILE]..."
146 //usage:#define vi_full_usage "\n\n"
147 //usage: "Edit FILE\n"
148 //usage: IF_FEATURE_VI_COLON(
149 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
150 //usage: )
151 //usage: IF_FEATURE_VI_READONLY(
152 //usage: "\n -R Read-only"
153 //usage: )
154 //usage: "\n -H List available features"
156 #include "libbb.h"
157 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
158 #if ENABLE_FEATURE_VI_REGEX_SEARCH
159 # include <regex.h>
160 #endif
162 /* the CRASHME code is unmaintained, and doesn't currently build */
163 #define ENABLE_FEATURE_VI_CRASHME 0
166 #if ENABLE_LOCALE_SUPPORT
168 #if ENABLE_FEATURE_VI_8BIT
169 //FIXME: this does not work properly for Unicode anyway
170 # define Isprint(c) (isprint)(c)
171 #else
172 # define Isprint(c) isprint_asciionly(c)
173 #endif
175 #else
177 /* 0x9b is Meta-ESC */
178 #if ENABLE_FEATURE_VI_8BIT
179 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
180 #else
181 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
182 #endif
184 #endif
187 enum {
188 MAX_TABSTOP = 32, // sanity limit
189 // User input len. Need not be extra big.
190 // Lines in file being edited *can* be bigger than this.
191 MAX_INPUT_LEN = 128,
192 // Sanity limits. We have only one buffer of this size.
193 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
194 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
197 /* VT102 ESC sequences.
198 * See "Xterm Control Sequences"
199 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
201 /* Inverse/Normal text */
202 #define ESC_BOLD_TEXT "\033[7m"
203 #define ESC_NORM_TEXT "\033[0m"
204 /* Bell */
205 #define ESC_BELL "\007"
206 /* Clear-to-end-of-line */
207 #define ESC_CLEAR2EOL "\033[K"
208 /* Clear-to-end-of-screen.
209 * (We use default param here.
210 * Full sequence is "ESC [ <num> J",
211 * <num> is 0/1/2 = "erase below/above/all".)
213 #define ESC_CLEAR2EOS "\033[J"
214 /* Cursor to given coordinate (1,1: top left) */
215 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
216 //UNUSED
217 ///* Cursor up and down */
218 //#define ESC_CURSOR_UP "\033[A"
219 //#define ESC_CURSOR_DOWN "\n"
221 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
222 // cmds modifying text[]
223 // vda: removed "aAiIs" as they switch us into insert mode
224 // and remembering input for replay after them makes no sense
225 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
226 #endif
228 enum {
229 YANKONLY = FALSE,
230 YANKDEL = TRUE,
231 FORWARD = 1, // code depends on "1" for array index
232 BACK = -1, // code depends on "-1" for array index
233 LIMITED = 0, // how much of text[] in char_search
234 FULL = 1, // how much of text[] in char_search
236 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
237 S_TO_WS = 2, // used in skip_thing() for moving "dot"
238 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
239 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
240 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
244 /* vi.c expects chars to be unsigned. */
245 /* busybox build system provides that, but it's better */
246 /* to audit and fix the source */
248 struct globals {
249 /* many references - keep near the top of globals */
250 char *text, *end; // pointers to the user data in memory
251 char *dot; // where all the action takes place
252 int text_size; // size of the allocated buffer
254 /* the rest */
255 smallint vi_setops;
256 #define VI_AUTOINDENT 1
257 #define VI_SHOWMATCH 2
258 #define VI_IGNORECASE 4
259 #define VI_ERR_METHOD 8
260 #define autoindent (vi_setops & VI_AUTOINDENT)
261 #define showmatch (vi_setops & VI_SHOWMATCH )
262 #define ignorecase (vi_setops & VI_IGNORECASE)
263 /* indicate error with beep or flash */
264 #define err_method (vi_setops & VI_ERR_METHOD)
266 #if ENABLE_FEATURE_VI_READONLY
267 smallint readonly_mode;
268 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
269 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
270 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
271 #else
272 #define SET_READONLY_FILE(flags) ((void)0)
273 #define SET_READONLY_MODE(flags) ((void)0)
274 #define UNSET_READONLY_FILE(flags) ((void)0)
275 #endif
277 smallint editing; // >0 while we are editing a file
278 // [code audit says "can be 0, 1 or 2 only"]
279 smallint cmd_mode; // 0=command 1=insert 2=replace
280 int file_modified; // buffer contents changed (counter, not flag!)
281 int last_file_modified; // = -1;
282 int save_argc; // how many file names on cmd line
283 int cmdcnt; // repetition count
284 unsigned rows, columns; // the terminal screen is this size
285 #if ENABLE_FEATURE_VI_ASK_TERMINAL
286 int get_rowcol_error;
287 #endif
288 int crow, ccol; // cursor is on Crow x Ccol
289 int offset; // chars scrolled off the screen to the left
290 int have_status_msg; // is default edit status needed?
291 // [don't make smallint!]
292 int last_status_cksum; // hash of current status line
293 char *current_filename;
294 char *screenbegin; // index into text[], of top line on the screen
295 char *screen; // pointer to the virtual screen buffer
296 int screensize; // and its size
297 int tabstop;
298 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
299 char erase_char; // the users erase character
300 char last_input_char; // last char read from user
302 #if ENABLE_FEATURE_VI_DOT_CMD
303 smallint adding2q; // are we currently adding user input to q
304 int lmc_len; // length of last_modifying_cmd
305 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
306 #endif
307 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
308 int my_pid;
309 #endif
310 #if ENABLE_FEATURE_VI_SEARCH
311 char *last_search_pattern; // last pattern from a '/' or '?' search
312 #endif
314 /* former statics */
315 #if ENABLE_FEATURE_VI_YANKMARK
316 char *edit_file__cur_line;
317 #endif
318 int refresh__old_offset;
319 int format_edit_status__tot;
321 /* a few references only */
322 #if ENABLE_FEATURE_VI_YANKMARK
323 int YDreg, Ureg; // default delete register and orig line for "U"
324 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
325 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
326 char *context_start, *context_end;
327 #endif
328 #if ENABLE_FEATURE_VI_USE_SIGNALS
329 sigjmp_buf restart; // catch_sig()
330 #endif
331 struct termios term_orig, term_vi; // remember what the cooked mode was
332 #if ENABLE_FEATURE_VI_COLON
333 char *initial_cmds[3]; // currently 2 entries, NULL terminated
334 #endif
335 // Should be just enough to hold a key sequence,
336 // but CRASHME mode uses it as generated command buffer too
337 #if ENABLE_FEATURE_VI_CRASHME
338 char readbuffer[128];
339 #else
340 char readbuffer[KEYCODE_BUFFER_SIZE];
341 #endif
342 #define STATUS_BUFFER_LEN 200
343 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
344 #if ENABLE_FEATURE_VI_DOT_CMD
345 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
346 #endif
347 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
349 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
351 #define G (*ptr_to_globals)
352 #define text (G.text )
353 #define text_size (G.text_size )
354 #define end (G.end )
355 #define dot (G.dot )
356 #define reg (G.reg )
358 #define vi_setops (G.vi_setops )
359 #define editing (G.editing )
360 #define cmd_mode (G.cmd_mode )
361 #define file_modified (G.file_modified )
362 #define last_file_modified (G.last_file_modified )
363 #define save_argc (G.save_argc )
364 #define cmdcnt (G.cmdcnt )
365 #define rows (G.rows )
366 #define columns (G.columns )
367 #define crow (G.crow )
368 #define ccol (G.ccol )
369 #define offset (G.offset )
370 #define status_buffer (G.status_buffer )
371 #define have_status_msg (G.have_status_msg )
372 #define last_status_cksum (G.last_status_cksum )
373 #define current_filename (G.current_filename )
374 #define screen (G.screen )
375 #define screensize (G.screensize )
376 #define screenbegin (G.screenbegin )
377 #define tabstop (G.tabstop )
378 #define last_forward_char (G.last_forward_char )
379 #define erase_char (G.erase_char )
380 #define last_input_char (G.last_input_char )
381 #if ENABLE_FEATURE_VI_READONLY
382 #define readonly_mode (G.readonly_mode )
383 #else
384 #define readonly_mode 0
385 #endif
386 #define adding2q (G.adding2q )
387 #define lmc_len (G.lmc_len )
388 #define ioq (G.ioq )
389 #define ioq_start (G.ioq_start )
390 #define my_pid (G.my_pid )
391 #define last_search_pattern (G.last_search_pattern)
393 #define edit_file__cur_line (G.edit_file__cur_line)
394 #define refresh__old_offset (G.refresh__old_offset)
395 #define format_edit_status__tot (G.format_edit_status__tot)
397 #define YDreg (G.YDreg )
398 #define Ureg (G.Ureg )
399 #define mark (G.mark )
400 #define context_start (G.context_start )
401 #define context_end (G.context_end )
402 #define restart (G.restart )
403 #define term_orig (G.term_orig )
404 #define term_vi (G.term_vi )
405 #define initial_cmds (G.initial_cmds )
406 #define readbuffer (G.readbuffer )
407 #define scr_out_buf (G.scr_out_buf )
408 #define last_modifying_cmd (G.last_modifying_cmd )
409 #define get_input_line__buf (G.get_input_line__buf)
411 #define INIT_G() do { \
412 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
413 last_file_modified = -1; \
414 /* "" but has space for 2 chars: */ \
415 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
416 } while (0)
419 static int init_text_buffer(char *); // init from file or create new
420 static void edit_file(char *); // edit one file
421 static void do_cmd(int); // execute a command
422 static int next_tabstop(int);
423 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
424 static char *begin_line(char *); // return pointer to cur line B-o-l
425 static char *end_line(char *); // return pointer to cur line E-o-l
426 static char *prev_line(char *); // return pointer to prev line B-o-l
427 static char *next_line(char *); // return pointer to next line B-o-l
428 static char *end_screen(void); // get pointer to last char on screen
429 static int count_lines(char *, char *); // count line from start to stop
430 static char *find_line(int); // find begining of line #li
431 static char *move_to_col(char *, int); // move "p" to column l
432 static void dot_left(void); // move dot left- dont leave line
433 static void dot_right(void); // move dot right- dont leave line
434 static void dot_begin(void); // move dot to B-o-l
435 static void dot_end(void); // move dot to E-o-l
436 static void dot_next(void); // move dot to next line B-o-l
437 static void dot_prev(void); // move dot to prev line B-o-l
438 static void dot_scroll(int, int); // move the screen up or down
439 static void dot_skip_over_ws(void); // move dot pat WS
440 static void dot_delete(void); // delete the char at 'dot'
441 static char *bound_dot(char *); // make sure text[0] <= P < "end"
442 static char *new_screen(int, int); // malloc virtual screen memory
443 static char *char_insert(char *, char); // insert the char c at 'p'
444 // might reallocate text[]! use p += stupid_insert(p, ...),
445 // and be careful to not use pointers into potentially freed text[]!
446 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
447 static int find_range(char **, char **, char); // return pointers for an object
448 static int st_test(char *, int, int, char *); // helper for skip_thing()
449 static char *skip_thing(char *, int, int, int); // skip some object
450 static char *find_pair(char *, char); // find matching pair () [] {}
451 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
452 // might reallocate text[]! use p += text_hole_make(p, ...),
453 // and be careful to not use pointers into potentially freed text[]!
454 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
455 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
456 static void show_help(void); // display some help info
457 static void rawmode(void); // set "raw" mode on tty
458 static void cookmode(void); // return to "cooked" mode on tty
459 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
460 static int mysleep(int);
461 static int readit(void); // read (maybe cursor) key from stdin
462 static int get_one_char(void); // read 1 char from stdin
463 static int file_size(const char *); // what is the byte size of "fn"
464 #if !ENABLE_FEATURE_VI_READONLY
465 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
466 #endif
467 // file_insert might reallocate text[]!
468 static int file_insert(const char *, char *, int);
469 static int file_write(char *, char *, char *);
470 static void place_cursor(int, int);
471 static void screen_erase(void);
472 static void clear_to_eol(void);
473 static void clear_to_eos(void);
474 static void go_bottom_and_clear_to_eol(void);
475 static void standout_start(void); // send "start reverse video" sequence
476 static void standout_end(void); // send "end reverse video" sequence
477 static void flash(int); // flash the terminal screen
478 static void show_status_line(void); // put a message on the bottom line
479 static void status_line(const char *, ...); // print to status buf
480 static void status_line_bold(const char *, ...);
481 static void not_implemented(const char *); // display "Not implemented" message
482 static int format_edit_status(void); // format file status on status line
483 static void redraw(int); // force a full screen refresh
484 static char* format_line(char* /*, int*/);
485 static void refresh(int); // update the terminal from screen[]
487 static void Indicate_Error(void); // use flash or beep to indicate error
488 #define indicate_error(c) Indicate_Error()
489 static void Hit_Return(void);
491 #if ENABLE_FEATURE_VI_SEARCH
492 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
493 #endif
494 #if ENABLE_FEATURE_VI_COLON
495 static char *get_one_address(char *, int *); // get colon addr, if present
496 static char *get_address(char *, int *, int *); // get two colon addrs, if present
497 static void colon(char *); // execute the "colon" mode cmds
498 #endif
499 #if ENABLE_FEATURE_VI_USE_SIGNALS
500 static void winch_sig(int); // catch window size changes
501 static void suspend_sig(int); // catch ctrl-Z
502 static void catch_sig(int); // catch ctrl-C and alarm time-outs
503 #endif
504 #if ENABLE_FEATURE_VI_DOT_CMD
505 static void start_new_cmd_q(char); // new queue for command
506 static void end_cmd_q(void); // stop saving input chars
507 #else
508 #define end_cmd_q() ((void)0)
509 #endif
510 #if ENABLE_FEATURE_VI_SETOPTS
511 static void showmatching(char *); // show the matching pair () [] {}
512 #endif
513 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
514 // might reallocate text[]! use p += string_insert(p, ...),
515 // and be careful to not use pointers into potentially freed text[]!
516 static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
517 #endif
518 #if ENABLE_FEATURE_VI_YANKMARK
519 static char *text_yank(char *, char *, int); // save copy of "p" into a register
520 static char what_reg(void); // what is letter of current YDreg
521 static void check_context(char); // remember context for '' command
522 #endif
523 #if ENABLE_FEATURE_VI_CRASHME
524 static void crash_dummy();
525 static void crash_test();
526 static int crashme = 0;
527 #endif
530 static void write1(const char *out)
532 fputs(out, stdout);
535 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
536 int vi_main(int argc, char **argv)
538 int c;
540 INIT_G();
542 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
543 my_pid = getpid();
544 #endif
545 #if ENABLE_FEATURE_VI_CRASHME
546 srand((long) my_pid);
547 #endif
548 #ifdef NO_SUCH_APPLET_YET
549 /* If we aren't "vi", we are "view" */
550 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
551 SET_READONLY_MODE(readonly_mode);
553 #endif
555 // autoindent is not default in vim 7.3
556 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
557 // 1- process $HOME/.exrc file (not inplemented yet)
558 // 2- process EXINIT variable from environment
559 // 3- process command line args
560 #if ENABLE_FEATURE_VI_COLON
562 char *p = getenv("EXINIT");
563 if (p && *p)
564 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
566 #endif
567 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
568 switch (c) {
569 #if ENABLE_FEATURE_VI_CRASHME
570 case 'C':
571 crashme = 1;
572 break;
573 #endif
574 #if ENABLE_FEATURE_VI_READONLY
575 case 'R': // Read-only flag
576 SET_READONLY_MODE(readonly_mode);
577 break;
578 #endif
579 #if ENABLE_FEATURE_VI_COLON
580 case 'c': // cmd line vi command
581 if (*optarg)
582 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
583 break;
584 #endif
585 case 'H':
586 show_help();
587 /* fall through */
588 default:
589 bb_show_usage();
590 return 1;
594 // The argv array can be used by the ":next" and ":rewind" commands
595 argv += optind;
596 argc -= optind;
598 //----- This is the main file handling loop --------------
599 save_argc = argc;
600 optind = 0;
601 // "Save cursor, use alternate screen buffer, clear screen"
602 write1("\033[?1049h");
603 while (1) {
604 edit_file(argv[optind]); /* param might be NULL */
605 if (++optind >= argc)
606 break;
608 // "Use normal screen buffer, restore cursor"
609 write1("\033[?1049l");
610 //-----------------------------------------------------------
612 return 0;
615 /* read text from file or create an empty buf */
616 /* will also update current_filename */
617 static int init_text_buffer(char *fn)
619 int rc;
620 int size = file_size(fn); // file size. -1 means does not exist.
622 /* allocate/reallocate text buffer */
623 free(text);
624 text_size = size + 10240;
625 screenbegin = dot = end = text = xzalloc(text_size);
627 if (fn != current_filename) {
628 free(current_filename);
629 current_filename = xstrdup(fn);
631 if (size < 0) {
632 // file dont exist. Start empty buf with dummy line
633 char_insert(text, '\n');
634 rc = 0;
635 } else {
636 rc = file_insert(fn, text, 1);
638 file_modified = 0;
639 last_file_modified = -1;
640 #if ENABLE_FEATURE_VI_YANKMARK
641 /* init the marks. */
642 memset(mark, 0, sizeof(mark));
643 #endif
644 return rc;
647 #if ENABLE_FEATURE_VI_WIN_RESIZE
648 static int query_screen_dimensions(void)
650 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
651 if (rows > MAX_SCR_ROWS)
652 rows = MAX_SCR_ROWS;
653 if (columns > MAX_SCR_COLS)
654 columns = MAX_SCR_COLS;
655 return err;
657 #else
658 # define query_screen_dimensions() (0)
659 #endif
661 static void edit_file(char *fn)
663 #if ENABLE_FEATURE_VI_YANKMARK
664 #define cur_line edit_file__cur_line
665 #endif
666 int c;
667 #if ENABLE_FEATURE_VI_USE_SIGNALS
668 int sig;
669 #endif
671 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
672 rawmode();
673 rows = 24;
674 columns = 80;
675 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
676 #if ENABLE_FEATURE_VI_ASK_TERMINAL
677 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
678 uint64_t k;
679 write1("\033[999;999H" "\033[6n");
680 fflush_all();
681 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
682 if ((int32_t)k == KEYCODE_CURSOR_POS) {
683 uint32_t rc = (k >> 32);
684 columns = (rc & 0x7fff);
685 if (columns > MAX_SCR_COLS)
686 columns = MAX_SCR_COLS;
687 rows = ((rc >> 16) & 0x7fff);
688 if (rows > MAX_SCR_ROWS)
689 rows = MAX_SCR_ROWS;
692 #endif
693 new_screen(rows, columns); // get memory for virtual screen
694 init_text_buffer(fn);
696 #if ENABLE_FEATURE_VI_YANKMARK
697 YDreg = 26; // default Yank/Delete reg
698 Ureg = 27; // hold orig line for "U" cmd
699 mark[26] = mark[27] = text; // init "previous context"
700 #endif
702 last_forward_char = last_input_char = '\0';
703 crow = 0;
704 ccol = 0;
706 #if ENABLE_FEATURE_VI_USE_SIGNALS
707 signal(SIGINT, catch_sig);
708 signal(SIGWINCH, winch_sig);
709 signal(SIGTSTP, suspend_sig);
710 sig = sigsetjmp(restart, 1);
711 if (sig != 0) {
712 screenbegin = dot = text;
714 #endif
716 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
717 cmdcnt = 0;
718 tabstop = 8;
719 offset = 0; // no horizontal offset
720 c = '\0';
721 #if ENABLE_FEATURE_VI_DOT_CMD
722 free(ioq_start);
723 ioq = ioq_start = NULL;
724 lmc_len = 0;
725 adding2q = 0;
726 #endif
728 #if ENABLE_FEATURE_VI_COLON
730 char *p, *q;
731 int n = 0;
733 while ((p = initial_cmds[n]) != NULL) {
734 do {
735 q = p;
736 p = strchr(q, '\n');
737 if (p)
738 while (*p == '\n')
739 *p++ = '\0';
740 if (*q)
741 colon(q);
742 } while (p);
743 free(initial_cmds[n]);
744 initial_cmds[n] = NULL;
745 n++;
748 #endif
749 redraw(FALSE); // dont force every col re-draw
750 //------This is the main Vi cmd handling loop -----------------------
751 while (editing > 0) {
752 #if ENABLE_FEATURE_VI_CRASHME
753 if (crashme > 0) {
754 if ((end - text) > 1) {
755 crash_dummy(); // generate a random command
756 } else {
757 crashme = 0;
758 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
759 dot = text;
760 refresh(FALSE);
763 #endif
764 last_input_char = c = get_one_char(); // get a cmd from user
765 #if ENABLE_FEATURE_VI_YANKMARK
766 // save a copy of the current line- for the 'U" command
767 if (begin_line(dot) != cur_line) {
768 cur_line = begin_line(dot);
769 text_yank(begin_line(dot), end_line(dot), Ureg);
771 #endif
772 #if ENABLE_FEATURE_VI_DOT_CMD
773 // These are commands that change text[].
774 // Remember the input for the "." command
775 if (!adding2q && ioq_start == NULL
776 && cmd_mode == 0 // command mode
777 && c > '\0' // exclude NUL and non-ASCII chars
778 && c < 0x7f // (Unicode and such)
779 && strchr(modifying_cmds, c)
781 start_new_cmd_q(c);
783 #endif
784 do_cmd(c); // execute the user command
786 // poll to see if there is input already waiting. if we are
787 // not able to display output fast enough to keep up, skip
788 // the display update until we catch up with input.
789 if (!readbuffer[0] && mysleep(0) == 0) {
790 // no input pending - so update output
791 refresh(FALSE);
792 show_status_line();
794 #if ENABLE_FEATURE_VI_CRASHME
795 if (crashme > 0)
796 crash_test(); // test editor variables
797 #endif
799 //-------------------------------------------------------------------
801 go_bottom_and_clear_to_eol();
802 cookmode();
803 #undef cur_line
806 //----- The Colon commands -------------------------------------
807 #if ENABLE_FEATURE_VI_COLON
808 static char *get_one_address(char *p, int *addr) // get colon addr, if present
810 int st;
811 char *q;
812 IF_FEATURE_VI_YANKMARK(char c;)
813 IF_FEATURE_VI_SEARCH(char *pat;)
815 *addr = -1; // assume no addr
816 if (*p == '.') { // the current line
817 p++;
818 q = begin_line(dot);
819 *addr = count_lines(text, q);
821 #if ENABLE_FEATURE_VI_YANKMARK
822 else if (*p == '\'') { // is this a mark addr
823 p++;
824 c = tolower(*p);
825 p++;
826 if (c >= 'a' && c <= 'z') {
827 // we have a mark
828 c = c - 'a';
829 q = mark[(unsigned char) c];
830 if (q != NULL) { // is mark valid
831 *addr = count_lines(text, q);
835 #endif
836 #if ENABLE_FEATURE_VI_SEARCH
837 else if (*p == '/') { // a search pattern
838 q = strchrnul(++p, '/');
839 pat = xstrndup(p, q - p); // save copy of pattern
840 p = q;
841 if (*p == '/')
842 p++;
843 q = char_search(dot, pat, FORWARD, FULL);
844 if (q != NULL) {
845 *addr = count_lines(text, q);
847 free(pat);
849 #endif
850 else if (*p == '$') { // the last line in file
851 p++;
852 q = begin_line(end - 1);
853 *addr = count_lines(text, q);
854 } else if (isdigit(*p)) { // specific line number
855 sscanf(p, "%d%n", addr, &st);
856 p += st;
857 } else {
858 // unrecognized address - assume -1
859 *addr = -1;
861 return p;
864 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
866 //----- get the address' i.e., 1,3 'a,'b -----
867 // get FIRST addr, if present
868 while (isblank(*p))
869 p++; // skip over leading spaces
870 if (*p == '%') { // alias for 1,$
871 p++;
872 *b = 1;
873 *e = count_lines(text, end-1);
874 goto ga0;
876 p = get_one_address(p, b);
877 while (isblank(*p))
878 p++;
879 if (*p == ',') { // is there a address separator
880 p++;
881 while (isblank(*p))
882 p++;
883 // get SECOND addr, if present
884 p = get_one_address(p, e);
886 ga0:
887 while (isblank(*p))
888 p++; // skip over trailing spaces
889 return p;
892 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
893 static void setops(const char *args, const char *opname, int flg_no,
894 const char *short_opname, int opt)
896 const char *a = args + flg_no;
897 int l = strlen(opname) - 1; /* opname have + ' ' */
899 // maybe strncmp? we had tons of erroneous strncasecmp's...
900 if (strncasecmp(a, opname, l) == 0
901 || strncasecmp(a, short_opname, 2) == 0
903 if (flg_no)
904 vi_setops &= ~opt;
905 else
906 vi_setops |= opt;
909 #endif
911 // buf must be no longer than MAX_INPUT_LEN!
912 static void colon(char *buf)
914 char c, *orig_buf, *buf1, *q, *r;
915 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
916 int i, l, li, ch, b, e;
917 int useforce, forced = FALSE;
919 // :3154 // if (-e line 3154) goto it else stay put
920 // :4,33w! foo // write a portion of buffer to file "foo"
921 // :w // write all of buffer to current file
922 // :q // quit
923 // :q! // quit- dont care about modified file
924 // :'a,'z!sort -u // filter block through sort
925 // :'f // goto mark "f"
926 // :'fl // list literal the mark "f" line
927 // :.r bar // read file "bar" into buffer before dot
928 // :/123/,/abc/d // delete lines from "123" line to "abc" line
929 // :/xyz/ // goto the "xyz" line
930 // :s/find/replace/ // substitute pattern "find" with "replace"
931 // :!<cmd> // run <cmd> then return
934 if (!buf[0])
935 goto ret;
936 if (*buf == ':')
937 buf++; // move past the ':'
939 li = ch = i = 0;
940 b = e = -1;
941 q = text; // assume 1,$ for the range
942 r = end - 1;
943 li = count_lines(text, end - 1);
944 fn = current_filename;
946 // look for optional address(es) :. :1 :1,9 :'q,'a :%
947 buf = get_address(buf, &b, &e);
949 // remember orig command line
950 orig_buf = buf;
952 // get the COMMAND into cmd[]
953 buf1 = cmd;
954 while (*buf != '\0') {
955 if (isspace(*buf))
956 break;
957 *buf1++ = *buf++;
959 *buf1 = '\0';
960 // get any ARGuments
961 while (isblank(*buf))
962 buf++;
963 strcpy(args, buf);
964 useforce = FALSE;
965 buf1 = last_char_is(cmd, '!');
966 if (buf1) {
967 useforce = TRUE;
968 *buf1 = '\0'; // get rid of !
970 if (b >= 0) {
971 // if there is only one addr, then the addr
972 // is the line number of the single line the
973 // user wants. So, reset the end
974 // pointer to point at end of the "b" line
975 q = find_line(b); // what line is #b
976 r = end_line(q);
977 li = 1;
979 if (e >= 0) {
980 // we were given two addrs. change the
981 // end pointer to the addr given by user.
982 r = find_line(e); // what line is #e
983 r = end_line(r);
984 li = e - b + 1;
986 // ------------ now look for the command ------------
987 i = strlen(cmd);
988 if (i == 0) { // :123CR goto line #123
989 if (b >= 0) {
990 dot = find_line(b); // what line is #b
991 dot_skip_over_ws();
994 #if ENABLE_FEATURE_ALLOW_EXEC
995 else if (cmd[0] == '!') { // run a cmd
996 int retcode;
997 // :!ls run the <cmd>
998 go_bottom_and_clear_to_eol();
999 cookmode();
1000 retcode = system(orig_buf + 1); // run the cmd
1001 if (retcode)
1002 printf("\nshell returned %i\n\n", retcode);
1003 rawmode();
1004 Hit_Return(); // let user see results
1006 #endif
1007 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1008 if (b < 0) { // no addr given- use defaults
1009 b = e = count_lines(text, dot);
1011 status_line("%d", b);
1012 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1013 if (b < 0) { // no addr given- use defaults
1014 q = begin_line(dot); // assume .,. for the range
1015 r = end_line(dot);
1017 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1018 dot_skip_over_ws();
1019 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1020 // don't edit, if the current file has been modified
1021 if (file_modified && !useforce) {
1022 status_line_bold("No write since last change (:%s! overrides)", cmd);
1023 goto ret;
1025 if (args[0]) {
1026 // the user supplied a file name
1027 fn = args;
1028 } else if (current_filename && current_filename[0]) {
1029 // no user supplied name- use the current filename
1030 // fn = current_filename; was set by default
1031 } else {
1032 // no user file name, no current name- punt
1033 status_line_bold("No current filename");
1034 goto ret;
1037 if (init_text_buffer(fn) < 0)
1038 goto ret;
1040 #if ENABLE_FEATURE_VI_YANKMARK
1041 if (Ureg >= 0 && Ureg < 28) {
1042 free(reg[Ureg]); // free orig line reg- for 'U'
1043 reg[Ureg] = NULL;
1045 if (YDreg >= 0 && YDreg < 28) {
1046 free(reg[YDreg]); // free default yank/delete register
1047 reg[YDreg] = NULL;
1049 #endif
1050 // how many lines in text[]?
1051 li = count_lines(text, end - 1);
1052 status_line("\"%s\"%s"
1053 IF_FEATURE_VI_READONLY("%s")
1054 " %dL, %dC", current_filename,
1055 (file_size(fn) < 0 ? " [New file]" : ""),
1056 IF_FEATURE_VI_READONLY(
1057 ((readonly_mode) ? " [Readonly]" : ""),
1059 li, ch);
1060 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1061 if (b != -1 || e != -1) {
1062 status_line_bold("No address allowed on this command");
1063 goto ret;
1065 if (args[0]) {
1066 // user wants a new filename
1067 free(current_filename);
1068 current_filename = xstrdup(args);
1069 } else {
1070 // user wants file status info
1071 last_status_cksum = 0; // force status update
1073 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1074 // print out values of all features
1075 go_bottom_and_clear_to_eol();
1076 cookmode();
1077 show_help();
1078 rawmode();
1079 Hit_Return();
1080 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1081 if (b < 0) { // no addr given- use defaults
1082 q = begin_line(dot); // assume .,. for the range
1083 r = end_line(dot);
1085 go_bottom_and_clear_to_eol();
1086 puts("\r");
1087 for (; q <= r; q++) {
1088 int c_is_no_print;
1090 c = *q;
1091 c_is_no_print = (c & 0x80) && !Isprint(c);
1092 if (c_is_no_print) {
1093 c = '.';
1094 standout_start();
1096 if (c == '\n') {
1097 write1("$\r");
1098 } else if (c < ' ' || c == 127) {
1099 bb_putchar('^');
1100 if (c == 127)
1101 c = '?';
1102 else
1103 c += '@';
1105 bb_putchar(c);
1106 if (c_is_no_print)
1107 standout_end();
1109 Hit_Return();
1110 } else if (strncmp(cmd, "quit", i) == 0 // quit
1111 || strncmp(cmd, "next", i) == 0 // edit next file
1112 || strncmp(cmd, "prev", i) == 0 // edit previous file
1114 int n;
1115 if (useforce) {
1116 if (*cmd == 'q') {
1117 // force end of argv list
1118 optind = save_argc;
1120 editing = 0;
1121 goto ret;
1123 // don't exit if the file been modified
1124 if (file_modified) {
1125 status_line_bold("No write since last change (:%s! overrides)", cmd);
1126 goto ret;
1128 // are there other file to edit
1129 n = save_argc - optind - 1;
1130 if (*cmd == 'q' && n > 0) {
1131 status_line_bold("%d more file(s) to edit", n);
1132 goto ret;
1134 if (*cmd == 'n' && n <= 0) {
1135 status_line_bold("No more files to edit");
1136 goto ret;
1138 if (*cmd == 'p') {
1139 // are there previous files to edit
1140 if (optind < 1) {
1141 status_line_bold("No previous files to edit");
1142 goto ret;
1144 optind -= 2;
1146 editing = 0;
1147 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1148 fn = args;
1149 if (!fn[0]) {
1150 status_line_bold("No filename given");
1151 goto ret;
1153 if (b < 0) { // no addr given- use defaults
1154 q = begin_line(dot); // assume "dot"
1156 // read after current line- unless user said ":0r foo"
1157 if (b != 0)
1158 q = next_line(q);
1159 { // dance around potentially-reallocated text[]
1160 uintptr_t ofs = q - text;
1161 ch = file_insert(fn, q, 0);
1162 q = text + ofs;
1164 if (ch < 0)
1165 goto ret; // nothing was inserted
1166 // how many lines in text[]?
1167 li = count_lines(q, q + ch - 1);
1168 status_line("\"%s\""
1169 IF_FEATURE_VI_READONLY("%s")
1170 " %dL, %dC", fn,
1171 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1172 li, ch);
1173 if (ch > 0) {
1174 // if the insert is before "dot" then we need to update
1175 if (q <= dot)
1176 dot += ch;
1177 /*file_modified++; - done by file_insert */
1179 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1180 if (file_modified && !useforce) {
1181 status_line_bold("No write since last change (:%s! overrides)", cmd);
1182 } else {
1183 // reset the filenames to edit
1184 optind = -1; /* start from 0th file */
1185 editing = 0;
1187 #if ENABLE_FEATURE_VI_SET
1188 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1189 #if ENABLE_FEATURE_VI_SETOPTS
1190 char *argp;
1191 #endif
1192 i = 0; // offset into args
1193 // only blank is regarded as args delimiter. What about tab '\t'?
1194 if (!args[0] || strcasecmp(args, "all") == 0) {
1195 // print out values of all options
1196 #if ENABLE_FEATURE_VI_SETOPTS
1197 status_line_bold(
1198 "%sautoindent "
1199 "%sflash "
1200 "%signorecase "
1201 "%sshowmatch "
1202 "tabstop=%u",
1203 autoindent ? "" : "no",
1204 err_method ? "" : "no",
1205 ignorecase ? "" : "no",
1206 showmatch ? "" : "no",
1207 tabstop
1209 #endif
1210 goto ret;
1212 #if ENABLE_FEATURE_VI_SETOPTS
1213 argp = args;
1214 while (*argp) {
1215 if (strncmp(argp, "no", 2) == 0)
1216 i = 2; // ":set noautoindent"
1217 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1218 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1219 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1220 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1221 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1222 int t = 0;
1223 sscanf(argp + i+8, "%u", &t);
1224 if (t > 0 && t <= MAX_TABSTOP)
1225 tabstop = t;
1227 argp = skip_non_whitespace(argp);
1228 argp = skip_whitespace(argp);
1230 #endif /* FEATURE_VI_SETOPTS */
1231 #endif /* FEATURE_VI_SET */
1232 #if ENABLE_FEATURE_VI_SEARCH
1233 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1234 char *F, *R, *flags;
1235 size_t len_F, len_R;
1236 int gflag; // global replace flag
1238 // F points to the "find" pattern
1239 // R points to the "replace" pattern
1240 // replace the cmd line delimiters "/" with NULs
1241 c = orig_buf[1]; // what is the delimiter
1242 F = orig_buf + 2; // start of "find"
1243 R = strchr(F, c); // middle delimiter
1244 if (!R)
1245 goto colon_s_fail;
1246 len_F = R - F;
1247 *R++ = '\0'; // terminate "find"
1248 flags = strchr(R, c);
1249 if (!flags)
1250 goto colon_s_fail;
1251 len_R = flags - R;
1252 *flags++ = '\0'; // terminate "replace"
1253 gflag = *flags;
1255 q = begin_line(q);
1256 if (b < 0) { // maybe :s/foo/bar/
1257 q = begin_line(dot); // start with cur line
1258 b = count_lines(text, q); // cur line number
1260 if (e < 0)
1261 e = b; // maybe :.s/foo/bar/
1263 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1264 char *ls = q; // orig line start
1265 char *found;
1266 vc4:
1267 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1268 if (found) {
1269 uintptr_t bias;
1270 // we found the "find" pattern - delete it
1271 text_hole_delete(found, found + len_F - 1);
1272 // inset the "replace" patern
1273 bias = string_insert(found, R); // insert the string
1274 found += bias;
1275 ls += bias;
1276 /*q += bias; - recalculated anyway */
1277 // check for "global" :s/foo/bar/g
1278 if (gflag == 'g') {
1279 if ((found + len_R) < end_line(ls)) {
1280 q = found + len_R;
1281 goto vc4; // don't let q move past cur line
1285 q = next_line(ls);
1287 #endif /* FEATURE_VI_SEARCH */
1288 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1289 status_line(BB_VER " " BB_BT);
1290 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1291 || strncmp(cmd, "wq", i) == 0
1292 || strncmp(cmd, "wn", i) == 0
1293 || (cmd[0] == 'x' && !cmd[1])
1295 // is there a file name to write to?
1296 if (args[0]) {
1297 fn = args;
1299 #if ENABLE_FEATURE_VI_READONLY
1300 if (readonly_mode && !useforce) {
1301 status_line_bold("\"%s\" File is read only", fn);
1302 goto ret;
1304 #endif
1305 // how many lines in text[]?
1306 li = count_lines(q, r);
1307 ch = r - q + 1;
1308 // see if file exists- if not, its just a new file request
1309 if (useforce) {
1310 // if "fn" is not write-able, chmod u+w
1311 // sprintf(syscmd, "chmod u+w %s", fn);
1312 // system(syscmd);
1313 forced = TRUE;
1315 l = file_write(fn, q, r);
1316 if (useforce && forced) {
1317 // chmod u-w
1318 // sprintf(syscmd, "chmod u-w %s", fn);
1319 // system(syscmd);
1320 forced = FALSE;
1322 if (l < 0) {
1323 if (l == -1)
1324 status_line_bold("\"%s\" %s", fn, strerror(errno));
1325 } else {
1326 status_line("\"%s\" %dL, %dC", fn, li, l);
1327 if (q == text && r == end - 1 && l == ch) {
1328 file_modified = 0;
1329 last_file_modified = -1;
1331 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1332 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1334 && l == ch
1336 editing = 0;
1339 #if ENABLE_FEATURE_VI_YANKMARK
1340 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1341 if (b < 0) { // no addr given- use defaults
1342 q = begin_line(dot); // assume .,. for the range
1343 r = end_line(dot);
1345 text_yank(q, r, YDreg);
1346 li = count_lines(q, r);
1347 status_line("Yank %d lines (%d chars) into [%c]",
1348 li, strlen(reg[YDreg]), what_reg());
1349 #endif
1350 } else {
1351 // cmd unknown
1352 not_implemented(cmd);
1354 ret:
1355 dot = bound_dot(dot); // make sure "dot" is valid
1356 return;
1357 #if ENABLE_FEATURE_VI_SEARCH
1358 colon_s_fail:
1359 status_line(":s expression missing delimiters");
1360 #endif
1363 #endif /* FEATURE_VI_COLON */
1365 static void Hit_Return(void)
1367 int c;
1369 standout_start();
1370 write1("[Hit return to continue]");
1371 standout_end();
1372 while ((c = get_one_char()) != '\n' && c != '\r')
1373 continue;
1374 redraw(TRUE); // force redraw all
1377 static int next_tabstop(int col)
1379 return col + ((tabstop - 1) - (col % tabstop));
1382 //----- Synchronize the cursor to Dot --------------------------
1383 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1385 char *beg_cur; // begin and end of "d" line
1386 char *tp;
1387 int cnt, ro, co;
1389 beg_cur = begin_line(d); // first char of cur line
1391 if (beg_cur < screenbegin) {
1392 // "d" is before top line on screen
1393 // how many lines do we have to move
1394 cnt = count_lines(beg_cur, screenbegin);
1395 sc1:
1396 screenbegin = beg_cur;
1397 if (cnt > (rows - 1) / 2) {
1398 // we moved too many lines. put "dot" in middle of screen
1399 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1400 screenbegin = prev_line(screenbegin);
1403 } else {
1404 char *end_scr; // begin and end of screen
1405 end_scr = end_screen(); // last char of screen
1406 if (beg_cur > end_scr) {
1407 // "d" is after bottom line on screen
1408 // how many lines do we have to move
1409 cnt = count_lines(end_scr, beg_cur);
1410 if (cnt > (rows - 1) / 2)
1411 goto sc1; // too many lines
1412 for (ro = 0; ro < cnt - 1; ro++) {
1413 // move screen begin the same amount
1414 screenbegin = next_line(screenbegin);
1415 // now, move the end of screen
1416 end_scr = next_line(end_scr);
1417 end_scr = end_line(end_scr);
1421 // "d" is on screen- find out which row
1422 tp = screenbegin;
1423 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1424 if (tp == beg_cur)
1425 break;
1426 tp = next_line(tp);
1429 // find out what col "d" is on
1430 co = 0;
1431 while (tp < d) { // drive "co" to correct column
1432 if (*tp == '\n') //vda || *tp == '\0')
1433 break;
1434 if (*tp == '\t') {
1435 // handle tabs like real vi
1436 if (d == tp && cmd_mode) {
1437 break;
1439 co = next_tabstop(co);
1440 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1441 co++; // display as ^X, use 2 columns
1443 co++;
1444 tp++;
1447 // "co" is the column where "dot" is.
1448 // The screen has "columns" columns.
1449 // The currently displayed columns are 0+offset -- columns+ofset
1450 // |-------------------------------------------------------------|
1451 // ^ ^ ^
1452 // offset | |------- columns ----------------|
1454 // If "co" is already in this range then we do not have to adjust offset
1455 // but, we do have to subtract the "offset" bias from "co".
1456 // If "co" is outside this range then we have to change "offset".
1457 // If the first char of a line is a tab the cursor will try to stay
1458 // in column 7, but we have to set offset to 0.
1460 if (co < 0 + offset) {
1461 offset = co;
1463 if (co >= columns + offset) {
1464 offset = co - columns + 1;
1466 // if the first char of the line is a tab, and "dot" is sitting on it
1467 // force offset to 0.
1468 if (d == beg_cur && *d == '\t') {
1469 offset = 0;
1471 co -= offset;
1473 *row = ro;
1474 *col = co;
1477 //----- Text Movement Routines ---------------------------------
1478 static char *begin_line(char *p) // return pointer to first char cur line
1480 if (p > text) {
1481 p = memrchr(text, '\n', p - text);
1482 if (!p)
1483 return text;
1484 return p + 1;
1486 return p;
1489 static char *end_line(char *p) // return pointer to NL of cur line
1491 if (p < end - 1) {
1492 p = memchr(p, '\n', end - p - 1);
1493 if (!p)
1494 return end - 1;
1496 return p;
1499 static char *dollar_line(char *p) // return pointer to just before NL line
1501 p = end_line(p);
1502 // Try to stay off of the Newline
1503 if (*p == '\n' && (p - begin_line(p)) > 0)
1504 p--;
1505 return p;
1508 static char *prev_line(char *p) // return pointer first char prev line
1510 p = begin_line(p); // goto begining of cur line
1511 if (p > text && p[-1] == '\n')
1512 p--; // step to prev line
1513 p = begin_line(p); // goto begining of prev line
1514 return p;
1517 static char *next_line(char *p) // return pointer first char next line
1519 p = end_line(p);
1520 if (p < end - 1 && *p == '\n')
1521 p++; // step to next line
1522 return p;
1525 //----- Text Information Routines ------------------------------
1526 static char *end_screen(void)
1528 char *q;
1529 int cnt;
1531 // find new bottom line
1532 q = screenbegin;
1533 for (cnt = 0; cnt < rows - 2; cnt++)
1534 q = next_line(q);
1535 q = end_line(q);
1536 return q;
1539 // count line from start to stop
1540 static int count_lines(char *start, char *stop)
1542 char *q;
1543 int cnt;
1545 if (stop < start) { // start and stop are backwards- reverse them
1546 q = start;
1547 start = stop;
1548 stop = q;
1550 cnt = 0;
1551 stop = end_line(stop);
1552 while (start <= stop && start <= end - 1) {
1553 start = end_line(start);
1554 if (*start == '\n')
1555 cnt++;
1556 start++;
1558 return cnt;
1561 static char *find_line(int li) // find begining of line #li
1563 char *q;
1565 for (q = text; li > 1; li--) {
1566 q = next_line(q);
1568 return q;
1571 //----- Dot Movement Routines ----------------------------------
1572 static void dot_left(void)
1574 if (dot > text && dot[-1] != '\n')
1575 dot--;
1578 static void dot_right(void)
1580 if (dot < end - 1 && *dot != '\n')
1581 dot++;
1584 static void dot_begin(void)
1586 dot = begin_line(dot); // return pointer to first char cur line
1589 static void dot_end(void)
1591 dot = end_line(dot); // return pointer to last char cur line
1594 static char *move_to_col(char *p, int l)
1596 int co;
1598 p = begin_line(p);
1599 co = 0;
1600 while (co < l && p < end) {
1601 if (*p == '\n') //vda || *p == '\0')
1602 break;
1603 if (*p == '\t') {
1604 co = next_tabstop(co);
1605 } else if (*p < ' ' || *p == 127) {
1606 co++; // display as ^X, use 2 columns
1608 co++;
1609 p++;
1611 return p;
1614 static void dot_next(void)
1616 dot = next_line(dot);
1619 static void dot_prev(void)
1621 dot = prev_line(dot);
1624 static void dot_scroll(int cnt, int dir)
1626 char *q;
1628 for (; cnt > 0; cnt--) {
1629 if (dir < 0) {
1630 // scroll Backwards
1631 // ctrl-Y scroll up one line
1632 screenbegin = prev_line(screenbegin);
1633 } else {
1634 // scroll Forwards
1635 // ctrl-E scroll down one line
1636 screenbegin = next_line(screenbegin);
1639 // make sure "dot" stays on the screen so we dont scroll off
1640 if (dot < screenbegin)
1641 dot = screenbegin;
1642 q = end_screen(); // find new bottom line
1643 if (dot > q)
1644 dot = begin_line(q); // is dot is below bottom line?
1645 dot_skip_over_ws();
1648 static void dot_skip_over_ws(void)
1650 // skip WS
1651 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1652 dot++;
1655 static void dot_delete(void) // delete the char at 'dot'
1657 text_hole_delete(dot, dot);
1660 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1662 if (p >= end && end > text) {
1663 p = end - 1;
1664 indicate_error('1');
1666 if (p < text) {
1667 p = text;
1668 indicate_error('2');
1670 return p;
1673 //----- Helper Utility Routines --------------------------------
1675 //----------------------------------------------------------------
1676 //----- Char Routines --------------------------------------------
1677 /* Chars that are part of a word-
1678 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1679 * Chars that are Not part of a word (stoppers)
1680 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1681 * Chars that are WhiteSpace
1682 * TAB NEWLINE VT FF RETURN SPACE
1683 * DO NOT COUNT NEWLINE AS WHITESPACE
1686 static char *new_screen(int ro, int co)
1688 int li;
1690 free(screen);
1691 screensize = ro * co + 8;
1692 screen = xmalloc(screensize);
1693 // initialize the new screen. assume this will be a empty file.
1694 screen_erase();
1695 // non-existent text[] lines start with a tilde (~).
1696 for (li = 1; li < ro - 1; li++) {
1697 screen[(li * co) + 0] = '~';
1699 return screen;
1702 #if ENABLE_FEATURE_VI_SEARCH
1704 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1706 // search for pattern starting at p
1707 static char *char_search(char *p, const char *pat, int dir, int range)
1709 char *q;
1710 struct re_pattern_buffer preg;
1711 int i;
1712 int size;
1714 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1715 preg.translate = 0;
1716 preg.fastmap = 0;
1717 preg.buffer = 0;
1718 preg.allocated = 0;
1720 // assume a LIMITED forward search
1721 q = next_line(p);
1722 q = end_line(q);
1723 q = end - 1;
1724 if (dir == BACK) {
1725 q = prev_line(p);
1726 q = text;
1728 // count the number of chars to search over, forward or backward
1729 size = q - p;
1730 if (size < 0)
1731 size = p - q;
1732 // RANGE could be negative if we are searching backwards
1733 range = q - p;
1735 q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1736 if (q != 0) {
1737 // The pattern was not compiled
1738 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1739 i = 0; // return p if pattern not compiled
1740 goto cs1;
1743 q = p;
1744 if (range < 0) {
1745 q = p - size;
1746 if (q < text)
1747 q = text;
1749 // search for the compiled pattern, preg, in p[]
1750 // range < 0- search backward
1751 // range > 0- search forward
1752 // 0 < start < size
1753 // re_search() < 0 not found or error
1754 // re_search() > 0 index of found pattern
1755 // struct pattern char int int int struct reg
1756 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1757 i = re_search(&preg, q, size, 0, range, 0);
1758 if (i == -1) {
1759 p = 0;
1760 i = 0; // return NULL if pattern not found
1762 cs1:
1763 if (dir == FORWARD) {
1764 p = p + i;
1765 } else {
1766 p = p - i;
1768 return p;
1771 # else
1773 # if ENABLE_FEATURE_VI_SETOPTS
1774 static int mycmp(const char *s1, const char *s2, int len)
1776 if (ignorecase) {
1777 return strncasecmp(s1, s2, len);
1779 return strncmp(s1, s2, len);
1781 # else
1782 # define mycmp strncmp
1783 # endif
1785 static char *char_search(char *p, const char *pat, int dir, int range)
1787 char *start, *stop;
1788 int len;
1790 len = strlen(pat);
1791 if (dir == FORWARD) {
1792 stop = end - 1; // assume range is p - end-1
1793 if (range == LIMITED)
1794 stop = next_line(p); // range is to next line
1795 for (start = p; start < stop; start++) {
1796 if (mycmp(start, pat, len) == 0) {
1797 return start;
1800 } else if (dir == BACK) {
1801 stop = text; // assume range is text - p
1802 if (range == LIMITED)
1803 stop = prev_line(p); // range is to prev line
1804 for (start = p - len; start >= stop; start--) {
1805 if (mycmp(start, pat, len) == 0) {
1806 return start;
1810 // pattern not found
1811 return NULL;
1814 # endif
1816 #endif /* FEATURE_VI_SEARCH */
1818 static char *char_insert(char *p, char c) // insert the char c at 'p'
1820 if (c == 22) { // Is this an ctrl-V?
1821 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1822 refresh(FALSE); // show the ^
1823 c = get_one_char();
1824 *p = c;
1825 p++;
1826 file_modified++;
1827 } else if (c == 27) { // Is this an ESC?
1828 cmd_mode = 0;
1829 cmdcnt = 0;
1830 end_cmd_q(); // stop adding to q
1831 last_status_cksum = 0; // force status update
1832 if ((p[-1] != '\n') && (dot > text)) {
1833 p--;
1835 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1836 // 123456789
1837 if ((p[-1] != '\n') && (dot>text)) {
1838 p--;
1839 p = text_hole_delete(p, p); // shrink buffer 1 char
1841 } else {
1842 #if ENABLE_FEATURE_VI_SETOPTS
1843 // insert a char into text[]
1844 char *sp; // "save p"
1845 #endif
1847 if (c == 13)
1848 c = '\n'; // translate \r to \n
1849 #if ENABLE_FEATURE_VI_SETOPTS
1850 sp = p; // remember addr of insert
1851 #endif
1852 p += 1 + stupid_insert(p, c); // insert the char
1853 #if ENABLE_FEATURE_VI_SETOPTS
1854 if (showmatch && strchr(")]}", *sp) != NULL) {
1855 showmatching(sp);
1857 if (autoindent && c == '\n') { // auto indent the new line
1858 char *q;
1859 size_t len;
1860 q = prev_line(p); // use prev line as template
1861 len = strspn(q, " \t"); // space or tab
1862 if (len) {
1863 uintptr_t bias;
1864 bias = text_hole_make(p, len);
1865 p += bias;
1866 q += bias;
1867 memcpy(p, q, len);
1868 p += len;
1871 #endif
1873 return p;
1876 // might reallocate text[]! use p += stupid_insert(p, ...),
1877 // and be careful to not use pointers into potentially freed text[]!
1878 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1880 uintptr_t bias;
1881 bias = text_hole_make(p, 1);
1882 p += bias;
1883 *p = c;
1884 //file_modified++; - done by text_hole_make()
1885 return bias;
1888 static int find_range(char **start, char **stop, char c)
1890 char *save_dot, *p, *q, *t;
1891 int cnt, multiline = 0;
1893 save_dot = dot;
1894 p = q = dot;
1896 if (strchr("cdy><", c)) {
1897 // these cmds operate on whole lines
1898 p = q = begin_line(p);
1899 for (cnt = 1; cnt < cmdcnt; cnt++) {
1900 q = next_line(q);
1902 q = end_line(q);
1903 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1904 // These cmds operate on char positions
1905 do_cmd(c); // execute movement cmd
1906 q = dot;
1907 } else if (strchr("wW", c)) {
1908 do_cmd(c); // execute movement cmd
1909 // if we are at the next word's first char
1910 // step back one char
1911 // but check the possibilities when it is true
1912 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1913 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1914 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1915 dot--; // move back off of next word
1916 if (dot > text && *dot == '\n')
1917 dot--; // stay off NL
1918 q = dot;
1919 } else if (strchr("H-k{", c)) {
1920 // these operate on multi-lines backwards
1921 q = end_line(dot); // find NL
1922 do_cmd(c); // execute movement cmd
1923 dot_begin();
1924 p = dot;
1925 } else if (strchr("L+j}\r\n", c)) {
1926 // these operate on multi-lines forwards
1927 p = begin_line(dot);
1928 do_cmd(c); // execute movement cmd
1929 dot_end(); // find NL
1930 q = dot;
1931 } else {
1932 // nothing -- this causes any other values of c to
1933 // represent the one-character range under the
1934 // cursor. this is correct for ' ' and 'l', but
1935 // perhaps no others.
1938 if (q < p) {
1939 t = q;
1940 q = p;
1941 p = t;
1944 // backward char movements don't include start position
1945 if (q > p && strchr("^0bBh\b\177", c)) q--;
1947 multiline = 0;
1948 for (t = p; t <= q; t++) {
1949 if (*t == '\n') {
1950 multiline = 1;
1951 break;
1955 *start = p;
1956 *stop = q;
1957 dot = save_dot;
1958 return multiline;
1961 static int st_test(char *p, int type, int dir, char *tested)
1963 char c, c0, ci;
1964 int test, inc;
1966 inc = dir;
1967 c = c0 = p[0];
1968 ci = p[inc];
1969 test = 0;
1971 if (type == S_BEFORE_WS) {
1972 c = ci;
1973 test = (!isspace(c) || c == '\n');
1975 if (type == S_TO_WS) {
1976 c = c0;
1977 test = (!isspace(c) || c == '\n');
1979 if (type == S_OVER_WS) {
1980 c = c0;
1981 test = isspace(c);
1983 if (type == S_END_PUNCT) {
1984 c = ci;
1985 test = ispunct(c);
1987 if (type == S_END_ALNUM) {
1988 c = ci;
1989 test = (isalnum(c) || c == '_');
1991 *tested = c;
1992 return test;
1995 static char *skip_thing(char *p, int linecnt, int dir, int type)
1997 char c;
1999 while (st_test(p, type, dir, &c)) {
2000 // make sure we limit search to correct number of lines
2001 if (c == '\n' && --linecnt < 1)
2002 break;
2003 if (dir >= 0 && p >= end - 1)
2004 break;
2005 if (dir < 0 && p <= text)
2006 break;
2007 p += dir; // move to next char
2009 return p;
2012 // find matching char of pair () [] {}
2013 static char *find_pair(char *p, const char c)
2015 char match, *q;
2016 int dir, level;
2018 match = ')';
2019 level = 1;
2020 dir = 1; // assume forward
2021 switch (c) {
2022 case '(': match = ')'; break;
2023 case '[': match = ']'; break;
2024 case '{': match = '}'; break;
2025 case ')': match = '('; dir = -1; break;
2026 case ']': match = '['; dir = -1; break;
2027 case '}': match = '{'; dir = -1; break;
2029 for (q = p + dir; text <= q && q < end; q += dir) {
2030 // look for match, count levels of pairs (( ))
2031 if (*q == c)
2032 level++; // increase pair levels
2033 if (*q == match)
2034 level--; // reduce pair level
2035 if (level == 0)
2036 break; // found matching pair
2038 if (level != 0)
2039 q = NULL; // indicate no match
2040 return q;
2043 #if ENABLE_FEATURE_VI_SETOPTS
2044 // show the matching char of a pair, () [] {}
2045 static void showmatching(char *p)
2047 char *q, *save_dot;
2049 // we found half of a pair
2050 q = find_pair(p, *p); // get loc of matching char
2051 if (q == NULL) {
2052 indicate_error('3'); // no matching char
2053 } else {
2054 // "q" now points to matching pair
2055 save_dot = dot; // remember where we are
2056 dot = q; // go to new loc
2057 refresh(FALSE); // let the user see it
2058 mysleep(40); // give user some time
2059 dot = save_dot; // go back to old loc
2060 refresh(FALSE);
2063 #endif /* FEATURE_VI_SETOPTS */
2065 // open a hole in text[]
2066 // might reallocate text[]! use p += text_hole_make(p, ...),
2067 // and be careful to not use pointers into potentially freed text[]!
2068 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2070 uintptr_t bias = 0;
2072 if (size <= 0)
2073 return bias;
2074 end += size; // adjust the new END
2075 if (end >= (text + text_size)) {
2076 char *new_text;
2077 text_size += end - (text + text_size) + 10240;
2078 new_text = xrealloc(text, text_size);
2079 bias = (new_text - text);
2080 screenbegin += bias;
2081 dot += bias;
2082 end += bias;
2083 p += bias;
2084 #if ENABLE_FEATURE_VI_YANKMARK
2086 int i;
2087 for (i = 0; i < ARRAY_SIZE(mark); i++)
2088 if (mark[i])
2089 mark[i] += bias;
2091 #endif
2092 text = new_text;
2094 memmove(p + size, p, end - size - p);
2095 memset(p, ' ', size); // clear new hole
2096 file_modified++;
2097 return bias;
2100 // close a hole in text[]
2101 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2103 char *src, *dest;
2104 int cnt, hole_size;
2106 // move forwards, from beginning
2107 // assume p <= q
2108 src = q + 1;
2109 dest = p;
2110 if (q < p) { // they are backward- swap them
2111 src = p + 1;
2112 dest = q;
2114 hole_size = q - p + 1;
2115 cnt = end - src;
2116 if (src < text || src > end)
2117 goto thd0;
2118 if (dest < text || dest >= end)
2119 goto thd0;
2120 if (src >= end)
2121 goto thd_atend; // just delete the end of the buffer
2122 memmove(dest, src, cnt);
2123 thd_atend:
2124 end = end - hole_size; // adjust the new END
2125 if (dest >= end)
2126 dest = end - 1; // make sure dest in below end-1
2127 if (end <= text)
2128 dest = end = text; // keep pointers valid
2129 file_modified++;
2130 thd0:
2131 return dest;
2134 // copy text into register, then delete text.
2135 // if dist <= 0, do not include, or go past, a NewLine
2137 static char *yank_delete(char *start, char *stop, int dist, int yf)
2139 char *p;
2141 // make sure start <= stop
2142 if (start > stop) {
2143 // they are backwards, reverse them
2144 p = start;
2145 start = stop;
2146 stop = p;
2148 if (dist <= 0) {
2149 // we cannot cross NL boundaries
2150 p = start;
2151 if (*p == '\n')
2152 return p;
2153 // dont go past a NewLine
2154 for (; p + 1 <= stop; p++) {
2155 if (p[1] == '\n') {
2156 stop = p; // "stop" just before NewLine
2157 break;
2161 p = start;
2162 #if ENABLE_FEATURE_VI_YANKMARK
2163 text_yank(start, stop, YDreg);
2164 #endif
2165 if (yf == YANKDEL) {
2166 p = text_hole_delete(start, stop);
2167 } // delete lines
2168 return p;
2171 static void show_help(void)
2173 puts("These features are available:"
2174 #if ENABLE_FEATURE_VI_SEARCH
2175 "\n\tPattern searches with / and ?"
2176 #endif
2177 #if ENABLE_FEATURE_VI_DOT_CMD
2178 "\n\tLast command repeat with ."
2179 #endif
2180 #if ENABLE_FEATURE_VI_YANKMARK
2181 "\n\tLine marking with 'x"
2182 "\n\tNamed buffers with \"x"
2183 #endif
2184 #if ENABLE_FEATURE_VI_READONLY
2185 //not implemented: "\n\tReadonly if vi is called as \"view\""
2186 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2187 #endif
2188 #if ENABLE_FEATURE_VI_SET
2189 "\n\tSome colon mode commands with :"
2190 #endif
2191 #if ENABLE_FEATURE_VI_SETOPTS
2192 "\n\tSettable options with \":set\""
2193 #endif
2194 #if ENABLE_FEATURE_VI_USE_SIGNALS
2195 "\n\tSignal catching- ^C"
2196 "\n\tJob suspend and resume with ^Z"
2197 #endif
2198 #if ENABLE_FEATURE_VI_WIN_RESIZE
2199 "\n\tAdapt to window re-sizes"
2200 #endif
2204 #if ENABLE_FEATURE_VI_DOT_CMD
2205 static void start_new_cmd_q(char c)
2207 // get buffer for new cmd
2208 // if there is a current cmd count put it in the buffer first
2209 if (cmdcnt > 0) {
2210 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2211 } else { // just save char c onto queue
2212 last_modifying_cmd[0] = c;
2213 lmc_len = 1;
2215 adding2q = 1;
2218 static void end_cmd_q(void)
2220 #if ENABLE_FEATURE_VI_YANKMARK
2221 YDreg = 26; // go back to default Yank/Delete reg
2222 #endif
2223 adding2q = 0;
2225 #endif /* FEATURE_VI_DOT_CMD */
2227 #if ENABLE_FEATURE_VI_YANKMARK \
2228 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2229 || ENABLE_FEATURE_VI_CRASHME
2230 // might reallocate text[]! use p += string_insert(p, ...),
2231 // and be careful to not use pointers into potentially freed text[]!
2232 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2234 uintptr_t bias;
2235 int i;
2237 i = strlen(s);
2238 bias = text_hole_make(p, i);
2239 p += bias;
2240 memcpy(p, s, i);
2241 #if ENABLE_FEATURE_VI_YANKMARK
2243 int cnt;
2244 for (cnt = 0; *s != '\0'; s++) {
2245 if (*s == '\n')
2246 cnt++;
2248 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2250 #endif
2251 return bias;
2253 #endif
2255 #if ENABLE_FEATURE_VI_YANKMARK
2256 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2258 int cnt = q - p;
2259 if (cnt < 0) { // they are backwards- reverse them
2260 p = q;
2261 cnt = -cnt;
2263 free(reg[dest]); // if already a yank register, free it
2264 reg[dest] = xstrndup(p, cnt + 1);
2265 return p;
2268 static char what_reg(void)
2270 char c;
2272 c = 'D'; // default to D-reg
2273 if (0 <= YDreg && YDreg <= 25)
2274 c = 'a' + (char) YDreg;
2275 if (YDreg == 26)
2276 c = 'D';
2277 if (YDreg == 27)
2278 c = 'U';
2279 return c;
2282 static void check_context(char cmd)
2284 // A context is defined to be "modifying text"
2285 // Any modifying command establishes a new context.
2287 if (dot < context_start || dot > context_end) {
2288 if (strchr(modifying_cmds, cmd) != NULL) {
2289 // we are trying to modify text[]- make this the current context
2290 mark[27] = mark[26]; // move cur to prev
2291 mark[26] = dot; // move local to cur
2292 context_start = prev_line(prev_line(dot));
2293 context_end = next_line(next_line(dot));
2294 //loiter= start_loiter= now;
2299 static char *swap_context(char *p) // goto new context for '' command make this the current context
2301 char *tmp;
2303 // the current context is in mark[26]
2304 // the previous context is in mark[27]
2305 // only swap context if other context is valid
2306 if (text <= mark[27] && mark[27] <= end - 1) {
2307 tmp = mark[27];
2308 mark[27] = mark[26];
2309 mark[26] = tmp;
2310 p = mark[26]; // where we are going- previous context
2311 context_start = prev_line(prev_line(prev_line(p)));
2312 context_end = next_line(next_line(next_line(p)));
2314 return p;
2316 #endif /* FEATURE_VI_YANKMARK */
2318 //----- Set terminal attributes --------------------------------
2319 static void rawmode(void)
2321 tcgetattr(0, &term_orig);
2322 term_vi = term_orig;
2323 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2324 term_vi.c_iflag &= (~IXON & ~ICRNL);
2325 term_vi.c_oflag &= (~ONLCR);
2326 term_vi.c_cc[VMIN] = 1;
2327 term_vi.c_cc[VTIME] = 0;
2328 erase_char = term_vi.c_cc[VERASE];
2329 tcsetattr_stdin_TCSANOW(&term_vi);
2332 static void cookmode(void)
2334 fflush_all();
2335 tcsetattr_stdin_TCSANOW(&term_orig);
2338 #if ENABLE_FEATURE_VI_USE_SIGNALS
2339 //----- Come here when we get a window resize signal ---------
2340 static void winch_sig(int sig UNUSED_PARAM)
2342 int save_errno = errno;
2343 // FIXME: do it in main loop!!!
2344 signal(SIGWINCH, winch_sig);
2345 query_screen_dimensions();
2346 new_screen(rows, columns); // get memory for virtual screen
2347 redraw(TRUE); // re-draw the screen
2348 errno = save_errno;
2351 //----- Come here when we get a continue signal -------------------
2352 static void cont_sig(int sig UNUSED_PARAM)
2354 int save_errno = errno;
2355 rawmode(); // terminal to "raw"
2356 last_status_cksum = 0; // force status update
2357 redraw(TRUE); // re-draw the screen
2359 signal(SIGTSTP, suspend_sig);
2360 signal(SIGCONT, SIG_DFL);
2361 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2362 errno = save_errno;
2365 //----- Come here when we get a Suspend signal -------------------
2366 static void suspend_sig(int sig UNUSED_PARAM)
2368 int save_errno = errno;
2369 go_bottom_and_clear_to_eol();
2370 cookmode(); // terminal to "cooked"
2372 signal(SIGCONT, cont_sig);
2373 signal(SIGTSTP, SIG_DFL);
2374 kill(my_pid, SIGTSTP);
2375 errno = save_errno;
2378 //----- Come here when we get a signal ---------------------------
2379 static void catch_sig(int sig)
2381 signal(SIGINT, catch_sig);
2382 siglongjmp(restart, sig);
2384 #endif /* FEATURE_VI_USE_SIGNALS */
2386 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2388 struct pollfd pfd[1];
2390 pfd[0].fd = STDIN_FILENO;
2391 pfd[0].events = POLLIN;
2392 return safe_poll(pfd, 1, hund*10) > 0;
2395 //----- IO Routines --------------------------------------------
2396 static int readit(void) // read (maybe cursor) key from stdin
2398 int c;
2400 fflush_all();
2401 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2402 if (c == -1) { // EOF/error
2403 go_bottom_and_clear_to_eol();
2404 cookmode(); // terminal to "cooked"
2405 bb_error_msg_and_die("can't read user input");
2407 return c;
2410 //----- IO Routines --------------------------------------------
2411 static int get_one_char(void)
2413 int c;
2415 #if ENABLE_FEATURE_VI_DOT_CMD
2416 if (!adding2q) {
2417 // we are not adding to the q.
2418 // but, we may be reading from a q
2419 if (ioq == 0) {
2420 // there is no current q, read from STDIN
2421 c = readit(); // get the users input
2422 } else {
2423 // there is a queue to get chars from first
2424 // careful with correct sign expansion!
2425 c = (unsigned char)*ioq++;
2426 if (c == '\0') {
2427 // the end of the q, read from STDIN
2428 free(ioq_start);
2429 ioq_start = ioq = 0;
2430 c = readit(); // get the users input
2433 } else {
2434 // adding STDIN chars to q
2435 c = readit(); // get the users input
2436 if (lmc_len >= MAX_INPUT_LEN - 1) {
2437 status_line_bold("last_modifying_cmd overrun");
2438 } else {
2439 // add new char to q
2440 last_modifying_cmd[lmc_len++] = c;
2443 #else
2444 c = readit(); // get the users input
2445 #endif /* FEATURE_VI_DOT_CMD */
2446 return c;
2449 // Get input line (uses "status line" area)
2450 static char *get_input_line(const char *prompt)
2452 // char [MAX_INPUT_LEN]
2453 #define buf get_input_line__buf
2455 int c;
2456 int i;
2458 strcpy(buf, prompt);
2459 last_status_cksum = 0; // force status update
2460 go_bottom_and_clear_to_eol();
2461 write1(prompt); // write out the :, /, or ? prompt
2463 i = strlen(buf);
2464 while (i < MAX_INPUT_LEN) {
2465 c = get_one_char();
2466 if (c == '\n' || c == '\r' || c == 27)
2467 break; // this is end of input
2468 if (c == erase_char || c == 8 || c == 127) {
2469 // user wants to erase prev char
2470 buf[--i] = '\0';
2471 write1("\b \b"); // erase char on screen
2472 if (i <= 0) // user backs up before b-o-l, exit
2473 break;
2474 } else if (c > 0 && c < 256) { // exclude Unicode
2475 // (TODO: need to handle Unicode)
2476 buf[i] = c;
2477 buf[++i] = '\0';
2478 bb_putchar(c);
2481 refresh(FALSE);
2482 return buf;
2483 #undef buf
2486 static int file_size(const char *fn) // what is the byte size of "fn"
2488 struct stat st_buf;
2489 int cnt;
2491 cnt = -1;
2492 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2493 cnt = (int) st_buf.st_size;
2494 return cnt;
2497 // might reallocate text[]!
2498 static int file_insert(const char *fn, char *p, int update_ro_status)
2500 int cnt = -1;
2501 int fd, size;
2502 struct stat statbuf;
2504 /* Validate file */
2505 if (stat(fn, &statbuf) < 0) {
2506 status_line_bold("\"%s\" %s", fn, strerror(errno));
2507 goto fi0;
2509 if (!S_ISREG(statbuf.st_mode)) {
2510 // This is not a regular file
2511 status_line_bold("\"%s\" Not a regular file", fn);
2512 goto fi0;
2514 if (p < text || p > end) {
2515 status_line_bold("Trying to insert file outside of memory");
2516 goto fi0;
2519 // read file to buffer
2520 fd = open(fn, O_RDONLY);
2521 if (fd < 0) {
2522 status_line_bold("\"%s\" %s", fn, strerror(errno));
2523 goto fi0;
2525 size = statbuf.st_size;
2526 p += text_hole_make(p, size);
2527 cnt = safe_read(fd, p, size);
2528 if (cnt < 0) {
2529 status_line_bold("\"%s\" %s", fn, strerror(errno));
2530 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2531 } else if (cnt < size) {
2532 // There was a partial read, shrink unused space text[]
2533 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2534 status_line_bold("can't read all of file \"%s\"", fn);
2536 if (cnt >= size)
2537 file_modified++;
2538 close(fd);
2539 fi0:
2540 #if ENABLE_FEATURE_VI_READONLY
2541 if (update_ro_status
2542 && ((access(fn, W_OK) < 0) ||
2543 /* root will always have access()
2544 * so we check fileperms too */
2545 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2548 SET_READONLY_FILE(readonly_mode);
2550 #endif
2551 return cnt;
2554 static int file_write(char *fn, char *first, char *last)
2556 int fd, cnt, charcnt;
2558 if (fn == 0) {
2559 status_line_bold("No current filename");
2560 return -2;
2562 /* By popular request we do not open file with O_TRUNC,
2563 * but instead ftruncate() it _after_ successful write.
2564 * Might reduce amount of data lost on power fail etc.
2566 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2567 if (fd < 0)
2568 return -1;
2569 cnt = last - first + 1;
2570 charcnt = full_write(fd, first, cnt);
2571 ftruncate(fd, charcnt);
2572 if (charcnt == cnt) {
2573 // good write
2574 //file_modified = FALSE;
2575 } else {
2576 charcnt = 0;
2578 close(fd);
2579 return charcnt;
2582 //----- Terminal Drawing ---------------------------------------
2583 // The terminal is made up of 'rows' line of 'columns' columns.
2584 // classically this would be 24 x 80.
2585 // screen coordinates
2586 // 0,0 ... 0,79
2587 // 1,0 ... 1,79
2588 // . ... .
2589 // . ... .
2590 // 22,0 ... 22,79
2591 // 23,0 ... 23,79 <- status line
2593 //----- Move the cursor to row x col (count from 0, not 1) -------
2594 static void place_cursor(int row, int col)
2596 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2598 if (row < 0) row = 0;
2599 if (row >= rows) row = rows - 1;
2600 if (col < 0) col = 0;
2601 if (col >= columns) col = columns - 1;
2603 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
2604 write1(cm1);
2607 //----- Erase from cursor to end of line -----------------------
2608 static void clear_to_eol(void)
2610 write1(ESC_CLEAR2EOL);
2613 static void go_bottom_and_clear_to_eol(void)
2615 place_cursor(rows - 1, 0);
2616 clear_to_eol();
2619 //----- Erase from cursor to end of screen -----------------------
2620 static void clear_to_eos(void)
2622 write1(ESC_CLEAR2EOS);
2625 //----- Start standout mode ------------------------------------
2626 static void standout_start(void)
2628 write1(ESC_BOLD_TEXT);
2631 //----- End standout mode --------------------------------------
2632 static void standout_end(void)
2634 write1(ESC_NORM_TEXT);
2637 //----- Flash the screen --------------------------------------
2638 static void flash(int h)
2640 standout_start();
2641 redraw(TRUE);
2642 mysleep(h);
2643 standout_end();
2644 redraw(TRUE);
2647 static void Indicate_Error(void)
2649 #if ENABLE_FEATURE_VI_CRASHME
2650 if (crashme > 0)
2651 return; // generate a random command
2652 #endif
2653 if (!err_method) {
2654 write1(ESC_BELL);
2655 } else {
2656 flash(10);
2660 //----- Screen[] Routines --------------------------------------
2661 //----- Erase the Screen[] memory ------------------------------
2662 static void screen_erase(void)
2664 memset(screen, ' ', screensize); // clear new screen
2667 static int bufsum(char *buf, int count)
2669 int sum = 0;
2670 char *e = buf + count;
2672 while (buf < e)
2673 sum += (unsigned char) *buf++;
2674 return sum;
2677 //----- Draw the status line at bottom of the screen -------------
2678 static void show_status_line(void)
2680 int cnt = 0, cksum = 0;
2682 // either we already have an error or status message, or we
2683 // create one.
2684 if (!have_status_msg) {
2685 cnt = format_edit_status();
2686 cksum = bufsum(status_buffer, cnt);
2688 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2689 last_status_cksum = cksum; // remember if we have seen this line
2690 go_bottom_and_clear_to_eol();
2691 write1(status_buffer);
2692 if (have_status_msg) {
2693 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2694 (columns - 1) ) {
2695 have_status_msg = 0;
2696 Hit_Return();
2698 have_status_msg = 0;
2700 place_cursor(crow, ccol); // put cursor back in correct place
2702 fflush_all();
2705 //----- format the status buffer, the bottom line of screen ------
2706 // format status buffer, with STANDOUT mode
2707 static void status_line_bold(const char *format, ...)
2709 va_list args;
2711 va_start(args, format);
2712 strcpy(status_buffer, ESC_BOLD_TEXT);
2713 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
2714 strcat(status_buffer, ESC_NORM_TEXT);
2715 va_end(args);
2717 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
2720 // format status buffer
2721 static void status_line(const char *format, ...)
2723 va_list args;
2725 va_start(args, format);
2726 vsprintf(status_buffer, format, args);
2727 va_end(args);
2729 have_status_msg = 1;
2732 // copy s to buf, convert unprintable
2733 static void print_literal(char *buf, const char *s)
2735 char *d;
2736 unsigned char c;
2738 buf[0] = '\0';
2739 if (!s[0])
2740 s = "(NULL)";
2742 d = buf;
2743 for (; *s; s++) {
2744 int c_is_no_print;
2746 c = *s;
2747 c_is_no_print = (c & 0x80) && !Isprint(c);
2748 if (c_is_no_print) {
2749 strcpy(d, ESC_NORM_TEXT);
2750 d += sizeof(ESC_NORM_TEXT)-1;
2751 c = '.';
2753 if (c < ' ' || c == 0x7f) {
2754 *d++ = '^';
2755 c |= '@'; /* 0x40 */
2756 if (c == 0x7f)
2757 c = '?';
2759 *d++ = c;
2760 *d = '\0';
2761 if (c_is_no_print) {
2762 strcpy(d, ESC_BOLD_TEXT);
2763 d += sizeof(ESC_BOLD_TEXT)-1;
2765 if (*s == '\n') {
2766 *d++ = '$';
2767 *d = '\0';
2769 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2770 break;
2774 static void not_implemented(const char *s)
2776 char buf[MAX_INPUT_LEN];
2778 print_literal(buf, s);
2779 status_line_bold("\'%s\' is not implemented", buf);
2782 // show file status on status line
2783 static int format_edit_status(void)
2785 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2787 #define tot format_edit_status__tot
2789 int cur, percent, ret, trunc_at;
2791 // file_modified is now a counter rather than a flag. this
2792 // helps reduce the amount of line counting we need to do.
2793 // (this will cause a mis-reporting of modified status
2794 // once every MAXINT editing operations.)
2796 // it would be nice to do a similar optimization here -- if
2797 // we haven't done a motion that could have changed which line
2798 // we're on, then we shouldn't have to do this count_lines()
2799 cur = count_lines(text, dot);
2801 // reduce counting -- the total lines can't have
2802 // changed if we haven't done any edits.
2803 if (file_modified != last_file_modified) {
2804 tot = cur + count_lines(dot, end - 1) - 1;
2805 last_file_modified = file_modified;
2808 // current line percent
2809 // ------------- ~~ ----------
2810 // total lines 100
2811 if (tot > 0) {
2812 percent = (100 * cur) / tot;
2813 } else {
2814 cur = tot = 0;
2815 percent = 100;
2818 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2819 columns : STATUS_BUFFER_LEN-1;
2821 ret = snprintf(status_buffer, trunc_at+1,
2822 #if ENABLE_FEATURE_VI_READONLY
2823 "%c %s%s%s %d/%d %d%%",
2824 #else
2825 "%c %s%s %d/%d %d%%",
2826 #endif
2827 cmd_mode_indicator[cmd_mode & 3],
2828 (current_filename != NULL ? current_filename : "No file"),
2829 #if ENABLE_FEATURE_VI_READONLY
2830 (readonly_mode ? " [Readonly]" : ""),
2831 #endif
2832 (file_modified ? " [Modified]" : ""),
2833 cur, tot, percent);
2835 if (ret >= 0 && ret < trunc_at)
2836 return ret; /* it all fit */
2838 return trunc_at; /* had to truncate */
2839 #undef tot
2842 //----- Force refresh of all Lines -----------------------------
2843 static void redraw(int full_screen)
2845 place_cursor(0, 0);
2846 clear_to_eos();
2847 screen_erase(); // erase the internal screen buffer
2848 last_status_cksum = 0; // force status update
2849 refresh(full_screen); // this will redraw the entire display
2850 show_status_line();
2853 //----- Format a text[] line into a buffer ---------------------
2854 static char* format_line(char *src /*, int li*/)
2856 unsigned char c;
2857 int co;
2858 int ofs = offset;
2859 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2861 c = '~'; // char in col 0 in non-existent lines is '~'
2862 co = 0;
2863 while (co < columns + tabstop) {
2864 // have we gone past the end?
2865 if (src < end) {
2866 c = *src++;
2867 if (c == '\n')
2868 break;
2869 if ((c & 0x80) && !Isprint(c)) {
2870 c = '.';
2872 if (c < ' ' || c == 0x7f) {
2873 if (c == '\t') {
2874 c = ' ';
2875 // co % 8 != 7
2876 while ((co % tabstop) != (tabstop - 1)) {
2877 dest[co++] = c;
2879 } else {
2880 dest[co++] = '^';
2881 if (c == 0x7f)
2882 c = '?';
2883 else
2884 c += '@'; // Ctrl-X -> 'X'
2888 dest[co++] = c;
2889 // discard scrolled-off-to-the-left portion,
2890 // in tabstop-sized pieces
2891 if (ofs >= tabstop && co >= tabstop) {
2892 memmove(dest, dest + tabstop, co);
2893 co -= tabstop;
2894 ofs -= tabstop;
2896 if (src >= end)
2897 break;
2899 // check "short line, gigantic offset" case
2900 if (co < ofs)
2901 ofs = co;
2902 // discard last scrolled off part
2903 co -= ofs;
2904 dest += ofs;
2905 // fill the rest with spaces
2906 if (co < columns)
2907 memset(&dest[co], ' ', columns - co);
2908 return dest;
2911 //----- Refresh the changed screen lines -----------------------
2912 // Copy the source line from text[] into the buffer and note
2913 // if the current screenline is different from the new buffer.
2914 // If they differ then that line needs redrawing on the terminal.
2916 static void refresh(int full_screen)
2918 #define old_offset refresh__old_offset
2920 int li, changed;
2921 char *tp, *sp; // pointer into text[] and screen[]
2923 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2924 unsigned c = columns, r = rows;
2925 query_screen_dimensions();
2926 full_screen |= (c - columns) | (r - rows);
2928 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2929 tp = screenbegin; // index into text[] of top line
2931 // compare text[] to screen[] and mark screen[] lines that need updating
2932 for (li = 0; li < rows - 1; li++) {
2933 int cs, ce; // column start & end
2934 char *out_buf;
2935 // format current text line
2936 out_buf = format_line(tp /*, li*/);
2938 // skip to the end of the current text[] line
2939 if (tp < end) {
2940 char *t = memchr(tp, '\n', end - tp);
2941 if (!t) t = end - 1;
2942 tp = t + 1;
2945 // see if there are any changes between vitual screen and out_buf
2946 changed = FALSE; // assume no change
2947 cs = 0;
2948 ce = columns - 1;
2949 sp = &screen[li * columns]; // start of screen line
2950 if (full_screen) {
2951 // force re-draw of every single column from 0 - columns-1
2952 goto re0;
2954 // compare newly formatted buffer with virtual screen
2955 // look forward for first difference between buf and screen
2956 for (; cs <= ce; cs++) {
2957 if (out_buf[cs] != sp[cs]) {
2958 changed = TRUE; // mark for redraw
2959 break;
2963 // look backward for last difference between out_buf and screen
2964 for (; ce >= cs; ce--) {
2965 if (out_buf[ce] != sp[ce]) {
2966 changed = TRUE; // mark for redraw
2967 break;
2970 // now, cs is index of first diff, and ce is index of last diff
2972 // if horz offset has changed, force a redraw
2973 if (offset != old_offset) {
2974 re0:
2975 changed = TRUE;
2978 // make a sanity check of columns indexes
2979 if (cs < 0) cs = 0;
2980 if (ce > columns - 1) ce = columns - 1;
2981 if (cs > ce) { cs = 0; ce = columns - 1; }
2982 // is there a change between vitual screen and out_buf
2983 if (changed) {
2984 // copy changed part of buffer to virtual screen
2985 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2986 place_cursor(li, cs);
2987 // write line out to terminal
2988 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2992 place_cursor(crow, ccol);
2994 old_offset = offset;
2995 #undef old_offset
2998 //---------------------------------------------------------------------
2999 //----- the Ascii Chart -----------------------------------------------
3001 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3002 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3003 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3004 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3005 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3006 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3007 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3008 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3009 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3010 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3011 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3012 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3013 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3014 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3015 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3016 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3017 //---------------------------------------------------------------------
3019 //----- Execute a Vi Command -----------------------------------
3020 static void do_cmd(int c)
3022 char *p, *q, *save_dot;
3023 char buf[12];
3024 int dir;
3025 int cnt, i, j;
3026 int c1;
3028 // c1 = c; // quiet the compiler
3029 // cnt = yf = 0; // quiet the compiler
3030 // p = q = save_dot = buf; // quiet the compiler
3031 memset(buf, '\0', sizeof(buf));
3033 show_status_line();
3035 /* if this is a cursor key, skip these checks */
3036 switch (c) {
3037 case KEYCODE_UP:
3038 case KEYCODE_DOWN:
3039 case KEYCODE_LEFT:
3040 case KEYCODE_RIGHT:
3041 case KEYCODE_HOME:
3042 case KEYCODE_END:
3043 case KEYCODE_PAGEUP:
3044 case KEYCODE_PAGEDOWN:
3045 case KEYCODE_DELETE:
3046 goto key_cmd_mode;
3049 if (cmd_mode == 2) {
3050 // flip-flop Insert/Replace mode
3051 if (c == KEYCODE_INSERT)
3052 goto dc_i;
3053 // we are 'R'eplacing the current *dot with new char
3054 if (*dot == '\n') {
3055 // don't Replace past E-o-l
3056 cmd_mode = 1; // convert to insert
3057 } else {
3058 if (1 <= c || Isprint(c)) {
3059 if (c != 27)
3060 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3061 dot = char_insert(dot, c); // insert new char
3063 goto dc1;
3066 if (cmd_mode == 1) {
3067 // hitting "Insert" twice means "R" replace mode
3068 if (c == KEYCODE_INSERT) goto dc5;
3069 // insert the char c at "dot"
3070 if (1 <= c || Isprint(c)) {
3071 dot = char_insert(dot, c);
3073 goto dc1;
3076 key_cmd_mode:
3077 switch (c) {
3078 //case 0x01: // soh
3079 //case 0x09: // ht
3080 //case 0x0b: // vt
3081 //case 0x0e: // so
3082 //case 0x0f: // si
3083 //case 0x10: // dle
3084 //case 0x11: // dc1
3085 //case 0x13: // dc3
3086 #if ENABLE_FEATURE_VI_CRASHME
3087 case 0x14: // dc4 ctrl-T
3088 crashme = (crashme == 0) ? 1 : 0;
3089 break;
3090 #endif
3091 //case 0x16: // syn
3092 //case 0x17: // etb
3093 //case 0x18: // can
3094 //case 0x1c: // fs
3095 //case 0x1d: // gs
3096 //case 0x1e: // rs
3097 //case 0x1f: // us
3098 //case '!': // !-
3099 //case '#': // #-
3100 //case '&': // &-
3101 //case '(': // (-
3102 //case ')': // )-
3103 //case '*': // *-
3104 //case '=': // =-
3105 //case '@': // @-
3106 //case 'F': // F-
3107 //case 'K': // K-
3108 //case 'Q': // Q-
3109 //case 'S': // S-
3110 //case 'T': // T-
3111 //case 'V': // V-
3112 //case '[': // [-
3113 //case '\\': // \-
3114 //case ']': // ]-
3115 //case '_': // _-
3116 //case '`': // `-
3117 //case 'u': // u- FIXME- there is no undo
3118 //case 'v': // v-
3119 default: // unrecognized command
3120 buf[0] = c;
3121 buf[1] = '\0';
3122 not_implemented(buf);
3123 end_cmd_q(); // stop adding to q
3124 case 0x00: // nul- ignore
3125 break;
3126 case 2: // ctrl-B scroll up full screen
3127 case KEYCODE_PAGEUP: // Cursor Key Page Up
3128 dot_scroll(rows - 2, -1);
3129 break;
3130 case 4: // ctrl-D scroll down half screen
3131 dot_scroll((rows - 2) / 2, 1);
3132 break;
3133 case 5: // ctrl-E scroll down one line
3134 dot_scroll(1, 1);
3135 break;
3136 case 6: // ctrl-F scroll down full screen
3137 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3138 dot_scroll(rows - 2, 1);
3139 break;
3140 case 7: // ctrl-G show current status
3141 last_status_cksum = 0; // force status update
3142 break;
3143 case 'h': // h- move left
3144 case KEYCODE_LEFT: // cursor key Left
3145 case 8: // ctrl-H- move left (This may be ERASE char)
3146 case 0x7f: // DEL- move left (This may be ERASE char)
3147 do {
3148 dot_left();
3149 } while (--cmdcnt > 0);
3150 break;
3151 case 10: // Newline ^J
3152 case 'j': // j- goto next line, same col
3153 case KEYCODE_DOWN: // cursor key Down
3154 do {
3155 dot_next(); // go to next B-o-l
3156 // try stay in same col
3157 dot = move_to_col(dot, ccol + offset);
3158 } while (--cmdcnt > 0);
3159 break;
3160 case 12: // ctrl-L force redraw whole screen
3161 case 18: // ctrl-R force redraw
3162 place_cursor(0, 0);
3163 clear_to_eos();
3164 //mysleep(10); // why???
3165 screen_erase(); // erase the internal screen buffer
3166 last_status_cksum = 0; // force status update
3167 refresh(TRUE); // this will redraw the entire display
3168 break;
3169 case 13: // Carriage Return ^M
3170 case '+': // +- goto next line
3171 do {
3172 dot_next();
3173 dot_skip_over_ws();
3174 } while (--cmdcnt > 0);
3175 break;
3176 case 21: // ctrl-U scroll up half screen
3177 dot_scroll((rows - 2) / 2, -1);
3178 break;
3179 case 25: // ctrl-Y scroll up one line
3180 dot_scroll(1, -1);
3181 break;
3182 case 27: // esc
3183 if (cmd_mode == 0)
3184 indicate_error(c);
3185 cmd_mode = 0; // stop insrting
3186 end_cmd_q();
3187 last_status_cksum = 0; // force status update
3188 break;
3189 case ' ': // move right
3190 case 'l': // move right
3191 case KEYCODE_RIGHT: // Cursor Key Right
3192 do {
3193 dot_right();
3194 } while (--cmdcnt > 0);
3195 break;
3196 #if ENABLE_FEATURE_VI_YANKMARK
3197 case '"': // "- name a register to use for Delete/Yank
3198 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3199 if ((unsigned)c1 <= 25) { // a-z?
3200 YDreg = c1;
3201 } else {
3202 indicate_error(c);
3204 break;
3205 case '\'': // '- goto a specific mark
3206 c1 = (get_one_char() | 0x20) - 'a';
3207 if ((unsigned)c1 <= 25) { // a-z?
3208 // get the b-o-l
3209 q = mark[c1];
3210 if (text <= q && q < end) {
3211 dot = q;
3212 dot_begin(); // go to B-o-l
3213 dot_skip_over_ws();
3215 } else if (c1 == '\'') { // goto previous context
3216 dot = swap_context(dot); // swap current and previous context
3217 dot_begin(); // go to B-o-l
3218 dot_skip_over_ws();
3219 } else {
3220 indicate_error(c);
3222 break;
3223 case 'm': // m- Mark a line
3224 // this is really stupid. If there are any inserts or deletes
3225 // between text[0] and dot then this mark will not point to the
3226 // correct location! It could be off by many lines!
3227 // Well..., at least its quick and dirty.
3228 c1 = (get_one_char() | 0x20) - 'a';
3229 if ((unsigned)c1 <= 25) { // a-z?
3230 // remember the line
3231 mark[c1] = dot;
3232 } else {
3233 indicate_error(c);
3235 break;
3236 case 'P': // P- Put register before
3237 case 'p': // p- put register after
3238 p = reg[YDreg];
3239 if (p == NULL) {
3240 status_line_bold("Nothing in register %c", what_reg());
3241 break;
3243 // are we putting whole lines or strings
3244 if (strchr(p, '\n') != NULL) {
3245 if (c == 'P') {
3246 dot_begin(); // putting lines- Put above
3248 if (c == 'p') {
3249 // are we putting after very last line?
3250 if (end_line(dot) == (end - 1)) {
3251 dot = end; // force dot to end of text[]
3252 } else {
3253 dot_next(); // next line, then put before
3256 } else {
3257 if (c == 'p')
3258 dot_right(); // move to right, can move to NL
3260 string_insert(dot, p); // insert the string
3261 end_cmd_q(); // stop adding to q
3262 break;
3263 case 'U': // U- Undo; replace current line with original version
3264 if (reg[Ureg] != NULL) {
3265 p = begin_line(dot);
3266 q = end_line(dot);
3267 p = text_hole_delete(p, q); // delete cur line
3268 p += string_insert(p, reg[Ureg]); // insert orig line
3269 dot = p;
3270 dot_skip_over_ws();
3272 break;
3273 #endif /* FEATURE_VI_YANKMARK */
3274 case '$': // $- goto end of line
3275 case KEYCODE_END: // Cursor Key End
3276 for (;;) {
3277 dot = end_line(dot);
3278 if (--cmdcnt <= 0)
3279 break;
3280 dot_next();
3282 break;
3283 case '%': // %- find matching char of pair () [] {}
3284 for (q = dot; q < end && *q != '\n'; q++) {
3285 if (strchr("()[]{}", *q) != NULL) {
3286 // we found half of a pair
3287 p = find_pair(q, *q);
3288 if (p == NULL) {
3289 indicate_error(c);
3290 } else {
3291 dot = p;
3293 break;
3296 if (*q == '\n')
3297 indicate_error(c);
3298 break;
3299 case 'f': // f- forward to a user specified char
3300 last_forward_char = get_one_char(); // get the search char
3302 // dont separate these two commands. 'f' depends on ';'
3304 //**** fall through to ... ';'
3305 case ';': // ;- look at rest of line for last forward char
3306 do {
3307 if (last_forward_char == 0)
3308 break;
3309 q = dot + 1;
3310 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3311 q++;
3313 if (*q == last_forward_char)
3314 dot = q;
3315 } while (--cmdcnt > 0);
3316 break;
3317 case ',': // repeat latest 'f' in opposite direction
3318 if (last_forward_char == 0)
3319 break;
3320 do {
3321 q = dot - 1;
3322 while (q >= text && *q != '\n' && *q != last_forward_char) {
3323 q--;
3325 if (q >= text && *q == last_forward_char)
3326 dot = q;
3327 } while (--cmdcnt > 0);
3328 break;
3330 case '-': // -- goto prev line
3331 do {
3332 dot_prev();
3333 dot_skip_over_ws();
3334 } while (--cmdcnt > 0);
3335 break;
3336 #if ENABLE_FEATURE_VI_DOT_CMD
3337 case '.': // .- repeat the last modifying command
3338 // Stuff the last_modifying_cmd back into stdin
3339 // and let it be re-executed.
3340 if (lmc_len > 0) {
3341 last_modifying_cmd[lmc_len] = 0;
3342 ioq = ioq_start = xstrdup(last_modifying_cmd);
3344 break;
3345 #endif
3346 #if ENABLE_FEATURE_VI_SEARCH
3347 case '?': // /- search for a pattern
3348 case '/': // /- search for a pattern
3349 buf[0] = c;
3350 buf[1] = '\0';
3351 q = get_input_line(buf); // get input line- use "status line"
3352 if (q[0] && !q[1]) {
3353 if (last_search_pattern[0])
3354 last_search_pattern[0] = c;
3355 goto dc3; // if no pat re-use old pat
3357 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3358 // there is a new pat
3359 free(last_search_pattern);
3360 last_search_pattern = xstrdup(q);
3361 goto dc3; // now find the pattern
3363 // user changed mind and erased the "/"- do nothing
3364 break;
3365 case 'N': // N- backward search for last pattern
3366 dir = BACK; // assume BACKWARD search
3367 p = dot - 1;
3368 if (last_search_pattern[0] == '?') {
3369 dir = FORWARD;
3370 p = dot + 1;
3372 goto dc4; // now search for pattern
3373 break;
3374 case 'n': // n- repeat search for last pattern
3375 // search rest of text[] starting at next char
3376 // if search fails return orignal "p" not the "p+1" address
3377 do {
3378 const char *msg;
3379 dc3:
3380 dir = FORWARD; // assume FORWARD search
3381 p = dot + 1;
3382 if (last_search_pattern[0] == '?') {
3383 dir = BACK;
3384 p = dot - 1;
3386 dc4:
3387 q = char_search(p, last_search_pattern + 1, dir, FULL);
3388 if (q != NULL) {
3389 dot = q; // good search, update "dot"
3390 msg = NULL;
3391 goto dc2;
3393 // no pattern found between "dot" and "end"- continue at top
3394 p = text;
3395 if (dir == BACK) {
3396 p = end - 1;
3398 q = char_search(p, last_search_pattern + 1, dir, FULL);
3399 if (q != NULL) { // found something
3400 dot = q; // found new pattern- goto it
3401 msg = "search hit BOTTOM, continuing at TOP";
3402 if (dir == BACK) {
3403 msg = "search hit TOP, continuing at BOTTOM";
3405 } else {
3406 msg = "Pattern not found";
3408 dc2:
3409 if (msg)
3410 status_line_bold("%s", msg);
3411 } while (--cmdcnt > 0);
3412 break;
3413 case '{': // {- move backward paragraph
3414 q = char_search(dot, "\n\n", BACK, FULL);
3415 if (q != NULL) { // found blank line
3416 dot = next_line(q); // move to next blank line
3418 break;
3419 case '}': // }- move forward paragraph
3420 q = char_search(dot, "\n\n", FORWARD, FULL);
3421 if (q != NULL) { // found blank line
3422 dot = next_line(q); // move to next blank line
3424 break;
3425 #endif /* FEATURE_VI_SEARCH */
3426 case '0': // 0- goto begining of line
3427 case '1': // 1-
3428 case '2': // 2-
3429 case '3': // 3-
3430 case '4': // 4-
3431 case '5': // 5-
3432 case '6': // 6-
3433 case '7': // 7-
3434 case '8': // 8-
3435 case '9': // 9-
3436 if (c == '0' && cmdcnt < 1) {
3437 dot_begin(); // this was a standalone zero
3438 } else {
3439 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3441 break;
3442 case ':': // :- the colon mode commands
3443 p = get_input_line(":"); // get input line- use "status line"
3444 #if ENABLE_FEATURE_VI_COLON
3445 colon(p); // execute the command
3446 #else
3447 if (*p == ':')
3448 p++; // move past the ':'
3449 cnt = strlen(p);
3450 if (cnt <= 0)
3451 break;
3452 if (strncmp(p, "quit", cnt) == 0
3453 || strncmp(p, "q!", cnt) == 0 // delete lines
3455 if (file_modified && p[1] != '!') {
3456 status_line_bold("No write since last change (:%s! overrides)", p);
3457 } else {
3458 editing = 0;
3460 } else if (strncmp(p, "write", cnt) == 0
3461 || strncmp(p, "wq", cnt) == 0
3462 || strncmp(p, "wn", cnt) == 0
3463 || (p[0] == 'x' && !p[1])
3465 cnt = file_write(current_filename, text, end - 1);
3466 if (cnt < 0) {
3467 if (cnt == -1)
3468 status_line_bold("Write error: %s", strerror(errno));
3469 } else {
3470 file_modified = 0;
3471 last_file_modified = -1;
3472 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3473 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3474 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3476 editing = 0;
3479 } else if (strncmp(p, "file", cnt) == 0) {
3480 last_status_cksum = 0; // force status update
3481 } else if (sscanf(p, "%d", &j) > 0) {
3482 dot = find_line(j); // go to line # j
3483 dot_skip_over_ws();
3484 } else { // unrecognized cmd
3485 not_implemented(p);
3487 #endif /* !FEATURE_VI_COLON */
3488 break;
3489 case '<': // <- Left shift something
3490 case '>': // >- Right shift something
3491 cnt = count_lines(text, dot); // remember what line we are on
3492 c1 = get_one_char(); // get the type of thing to delete
3493 find_range(&p, &q, c1);
3494 yank_delete(p, q, 1, YANKONLY); // save copy before change
3495 p = begin_line(p);
3496 q = end_line(q);
3497 i = count_lines(p, q); // # of lines we are shifting
3498 for ( ; i > 0; i--, p = next_line(p)) {
3499 if (c == '<') {
3500 // shift left- remove tab or 8 spaces
3501 if (*p == '\t') {
3502 // shrink buffer 1 char
3503 text_hole_delete(p, p);
3504 } else if (*p == ' ') {
3505 // we should be calculating columns, not just SPACE
3506 for (j = 0; *p == ' ' && j < tabstop; j++) {
3507 text_hole_delete(p, p);
3510 } else if (c == '>') {
3511 // shift right -- add tab or 8 spaces
3512 char_insert(p, '\t');
3515 dot = find_line(cnt); // what line were we on
3516 dot_skip_over_ws();
3517 end_cmd_q(); // stop adding to q
3518 break;
3519 case 'A': // A- append at e-o-l
3520 dot_end(); // go to e-o-l
3521 //**** fall through to ... 'a'
3522 case 'a': // a- append after current char
3523 if (*dot != '\n')
3524 dot++;
3525 goto dc_i;
3526 break;
3527 case 'B': // B- back a blank-delimited Word
3528 case 'E': // E- end of a blank-delimited word
3529 case 'W': // W- forward a blank-delimited word
3530 dir = FORWARD;
3531 if (c == 'B')
3532 dir = BACK;
3533 do {
3534 if (c == 'W' || isspace(dot[dir])) {
3535 dot = skip_thing(dot, 1, dir, S_TO_WS);
3536 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3538 if (c != 'W')
3539 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3540 } while (--cmdcnt > 0);
3541 break;
3542 case 'C': // C- Change to e-o-l
3543 case 'D': // D- delete to e-o-l
3544 save_dot = dot;
3545 dot = dollar_line(dot); // move to before NL
3546 // copy text into a register and delete
3547 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3548 if (c == 'C')
3549 goto dc_i; // start inserting
3550 #if ENABLE_FEATURE_VI_DOT_CMD
3551 if (c == 'D')
3552 end_cmd_q(); // stop adding to q
3553 #endif
3554 break;
3555 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3556 c1 = get_one_char();
3557 if (c1 != 'g') {
3558 buf[0] = 'g';
3559 buf[1] = c1;