Changes for kernel and Busybox
[tomato.git] / release / src / router / busybox / editors / vi.c
blobb4ad12e5c966cf3122b7226fdba758babc42be1d
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.
139 //config:
140 //config:config FEATURE_VI_OPTIMIZE_CURSOR
141 //config: bool "Optimize cursor movement"
142 //config: default y
143 //config: depends on VI
144 //config: help
145 //config: This will make the cursor movement faster, but requires more memory
146 //config: and it makes the applet a tiny bit larger.
148 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
150 //kbuild:lib-$(CONFIG_VI) += vi.o
152 //usage:#define vi_trivial_usage
153 //usage: "[OPTIONS] [FILE]..."
154 //usage:#define vi_full_usage "\n\n"
155 //usage: "Edit FILE\n"
156 //usage: IF_FEATURE_VI_COLON(
157 //usage: "\n -c Initial command to run ($EXINIT also available)"
158 //usage: )
159 //usage: IF_FEATURE_VI_READONLY(
160 //usage: "\n -R Read-only"
161 //usage: )
162 //usage: "\n -H Short help regarding available features"
164 #include "libbb.h"
165 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
166 #if ENABLE_FEATURE_VI_REGEX_SEARCH
167 # include <regex.h>
168 #endif
170 /* the CRASHME code is unmaintained, and doesn't currently build */
171 #define ENABLE_FEATURE_VI_CRASHME 0
174 #if ENABLE_LOCALE_SUPPORT
176 #if ENABLE_FEATURE_VI_8BIT
177 //FIXME: this does not work properly for Unicode anyway
178 # define Isprint(c) (isprint)(c)
179 #else
180 # define Isprint(c) isprint_asciionly(c)
181 #endif
183 #else
185 /* 0x9b is Meta-ESC */
186 #if ENABLE_FEATURE_VI_8BIT
187 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
188 #else
189 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
190 #endif
192 #endif
195 enum {
196 MAX_TABSTOP = 32, // sanity limit
197 // User input len. Need not be extra big.
198 // Lines in file being edited *can* be bigger than this.
199 MAX_INPUT_LEN = 128,
200 // Sanity limits. We have only one buffer of this size.
201 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
202 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
205 /* vt102 typical ESC sequence */
206 /* terminal standout start/normal ESC sequence */
207 #define SOs "\033[7m"
208 #define SOn "\033[0m"
209 /* terminal bell sequence */
210 #define bell "\007"
211 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
212 #define Ceol "\033[K"
213 #define Ceos "\033[J"
214 /* Cursor motion arbitrary destination ESC sequence */
215 #define CMrc "\033[%u;%uH"
216 /* Cursor motion up and down ESC sequence */
217 #define CMup "\033[A"
218 #define CMdown "\n"
220 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
221 // cmds modifying text[]
222 // vda: removed "aAiIs" as they switch us into insert mode
223 // and remembering input for replay after them makes no sense
224 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
225 #endif
227 enum {
228 YANKONLY = FALSE,
229 YANKDEL = TRUE,
230 FORWARD = 1, // code depends on "1" for array index
231 BACK = -1, // code depends on "-1" for array index
232 LIMITED = 0, // how much of text[] in char_search
233 FULL = 1, // how much of text[] in char_search
235 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
236 S_TO_WS = 2, // used in skip_thing() for moving "dot"
237 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
238 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
239 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
243 /* vi.c expects chars to be unsigned. */
244 /* busybox build system provides that, but it's better */
245 /* to audit and fix the source */
247 struct globals {
248 /* many references - keep near the top of globals */
249 char *text, *end; // pointers to the user data in memory
250 char *dot; // where all the action takes place
251 int text_size; // size of the allocated buffer
253 /* the rest */
254 smallint vi_setops;
255 #define VI_AUTOINDENT 1
256 #define VI_SHOWMATCH 2
257 #define VI_IGNORECASE 4
258 #define VI_ERR_METHOD 8
259 #define autoindent (vi_setops & VI_AUTOINDENT)
260 #define showmatch (vi_setops & VI_SHOWMATCH )
261 #define ignorecase (vi_setops & VI_IGNORECASE)
262 /* indicate error with beep or flash */
263 #define err_method (vi_setops & VI_ERR_METHOD)
265 #if ENABLE_FEATURE_VI_READONLY
266 smallint readonly_mode;
267 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
268 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
269 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
270 #else
271 #define SET_READONLY_FILE(flags) ((void)0)
272 #define SET_READONLY_MODE(flags) ((void)0)
273 #define UNSET_READONLY_FILE(flags) ((void)0)
274 #endif
276 smallint editing; // >0 while we are editing a file
277 // [code audit says "can be 0, 1 or 2 only"]
278 smallint cmd_mode; // 0=command 1=insert 2=replace
279 int file_modified; // buffer contents changed (counter, not flag!)
280 int last_file_modified; // = -1;
281 int save_argc; // how many file names on cmd line
282 int cmdcnt; // repetition count
283 unsigned rows, columns; // the terminal screen is this size
284 #if ENABLE_FEATURE_VI_ASK_TERMINAL
285 int get_rowcol_error;
286 #endif
287 int crow, ccol; // cursor is on Crow x Ccol
288 int offset; // chars scrolled off the screen to the left
289 int have_status_msg; // is default edit status needed?
290 // [don't make smallint!]
291 int last_status_cksum; // hash of current status line
292 char *current_filename;
293 char *screenbegin; // index into text[], of top line on the screen
294 char *screen; // pointer to the virtual screen buffer
295 int screensize; // and its size
296 int tabstop;
297 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
298 char erase_char; // the users erase character
299 char last_input_char; // last char read from user
301 #if ENABLE_FEATURE_VI_DOT_CMD
302 smallint adding2q; // are we currently adding user input to q
303 int lmc_len; // length of last_modifying_cmd
304 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
305 #endif
306 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
307 int last_row; // where the cursor was last moved to
308 #endif
309 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
310 int my_pid;
311 #endif
312 #if ENABLE_FEATURE_VI_SEARCH
313 char *last_search_pattern; // last pattern from a '/' or '?' search
314 #endif
316 /* former statics */
317 #if ENABLE_FEATURE_VI_YANKMARK
318 char *edit_file__cur_line;
319 #endif
320 int refresh__old_offset;
321 int format_edit_status__tot;
323 /* a few references only */
324 #if ENABLE_FEATURE_VI_YANKMARK
325 int YDreg, Ureg; // default delete register and orig line for "U"
326 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
327 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
328 char *context_start, *context_end;
329 #endif
330 #if ENABLE_FEATURE_VI_USE_SIGNALS
331 sigjmp_buf restart; // catch_sig()
332 #endif
333 struct termios term_orig, term_vi; // remember what the cooked mode was
334 #if ENABLE_FEATURE_VI_COLON
335 char *initial_cmds[3]; // currently 2 entries, NULL terminated
336 #endif
337 // Should be just enough to hold a key sequence,
338 // but CRASHME mode uses it as generated command buffer too
339 #if ENABLE_FEATURE_VI_CRASHME
340 char readbuffer[128];
341 #else
342 char readbuffer[KEYCODE_BUFFER_SIZE];
343 #endif
344 #define STATUS_BUFFER_LEN 200
345 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
346 #if ENABLE_FEATURE_VI_DOT_CMD
347 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
348 #endif
349 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
351 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
353 #define G (*ptr_to_globals)
354 #define text (G.text )
355 #define text_size (G.text_size )
356 #define end (G.end )
357 #define dot (G.dot )
358 #define reg (G.reg )
360 #define vi_setops (G.vi_setops )
361 #define editing (G.editing )
362 #define cmd_mode (G.cmd_mode )
363 #define file_modified (G.file_modified )
364 #define last_file_modified (G.last_file_modified )
365 #define save_argc (G.save_argc )
366 #define cmdcnt (G.cmdcnt )
367 #define rows (G.rows )
368 #define columns (G.columns )
369 #define crow (G.crow )
370 #define ccol (G.ccol )
371 #define offset (G.offset )
372 #define status_buffer (G.status_buffer )
373 #define have_status_msg (G.have_status_msg )
374 #define last_status_cksum (G.last_status_cksum )
375 #define current_filename (G.current_filename )
376 #define screen (G.screen )
377 #define screensize (G.screensize )
378 #define screenbegin (G.screenbegin )
379 #define tabstop (G.tabstop )
380 #define last_forward_char (G.last_forward_char )
381 #define erase_char (G.erase_char )
382 #define last_input_char (G.last_input_char )
383 #if ENABLE_FEATURE_VI_READONLY
384 #define readonly_mode (G.readonly_mode )
385 #else
386 #define readonly_mode 0
387 #endif
388 #define adding2q (G.adding2q )
389 #define lmc_len (G.lmc_len )
390 #define ioq (G.ioq )
391 #define ioq_start (G.ioq_start )
392 #define last_row (G.last_row )
393 #define my_pid (G.my_pid )
394 #define last_search_pattern (G.last_search_pattern)
396 #define edit_file__cur_line (G.edit_file__cur_line)
397 #define refresh__old_offset (G.refresh__old_offset)
398 #define format_edit_status__tot (G.format_edit_status__tot)
400 #define YDreg (G.YDreg )
401 #define Ureg (G.Ureg )
402 #define mark (G.mark )
403 #define context_start (G.context_start )
404 #define context_end (G.context_end )
405 #define restart (G.restart )
406 #define term_orig (G.term_orig )
407 #define term_vi (G.term_vi )
408 #define initial_cmds (G.initial_cmds )
409 #define readbuffer (G.readbuffer )
410 #define scr_out_buf (G.scr_out_buf )
411 #define last_modifying_cmd (G.last_modifying_cmd )
412 #define get_input_line__buf (G.get_input_line__buf)
414 #define INIT_G() do { \
415 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
416 last_file_modified = -1; \
417 /* "" but has space for 2 chars: */ \
418 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
419 } while (0)
422 static int init_text_buffer(char *); // init from file or create new
423 static void edit_file(char *); // edit one file
424 static void do_cmd(int); // execute a command
425 static int next_tabstop(int);
426 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
427 static char *begin_line(char *); // return pointer to cur line B-o-l
428 static char *end_line(char *); // return pointer to cur line E-o-l
429 static char *prev_line(char *); // return pointer to prev line B-o-l
430 static char *next_line(char *); // return pointer to next line B-o-l
431 static char *end_screen(void); // get pointer to last char on screen
432 static int count_lines(char *, char *); // count line from start to stop
433 static char *find_line(int); // find begining of line #li
434 static char *move_to_col(char *, int); // move "p" to column l
435 static void dot_left(void); // move dot left- dont leave line
436 static void dot_right(void); // move dot right- dont leave line
437 static void dot_begin(void); // move dot to B-o-l
438 static void dot_end(void); // move dot to E-o-l
439 static void dot_next(void); // move dot to next line B-o-l
440 static void dot_prev(void); // move dot to prev line B-o-l
441 static void dot_scroll(int, int); // move the screen up or down
442 static void dot_skip_over_ws(void); // move dot pat WS
443 static void dot_delete(void); // delete the char at 'dot'
444 static char *bound_dot(char *); // make sure text[0] <= P < "end"
445 static char *new_screen(int, int); // malloc virtual screen memory
446 static char *char_insert(char *, char); // insert the char c at 'p'
447 // might reallocate text[]! use p += stupid_insert(p, ...),
448 // and be careful to not use pointers into potentially freed text[]!
449 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
450 static int find_range(char **, char **, char); // return pointers for an object
451 static int st_test(char *, int, int, char *); // helper for skip_thing()
452 static char *skip_thing(char *, int, int, int); // skip some object
453 static char *find_pair(char *, char); // find matching pair () [] {}
454 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
455 // might reallocate text[]! use p += text_hole_make(p, ...),
456 // and be careful to not use pointers into potentially freed text[]!
457 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
458 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
459 static void show_help(void); // display some help info
460 static void rawmode(void); // set "raw" mode on tty
461 static void cookmode(void); // return to "cooked" mode on tty
462 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
463 static int mysleep(int);
464 static int readit(void); // read (maybe cursor) key from stdin
465 static int get_one_char(void); // read 1 char from stdin
466 static int file_size(const char *); // what is the byte size of "fn"
467 #if !ENABLE_FEATURE_VI_READONLY
468 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
469 #endif
470 // file_insert might reallocate text[]!
471 static int file_insert(const char *, char *, int);
472 static int file_write(char *, char *, char *);
473 #if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
474 #define place_cursor(a, b, optimize) place_cursor(a, b)
475 #endif
476 static void place_cursor(int, int, int);
477 static void screen_erase(void);
478 static void clear_to_eol(void);
479 static void clear_to_eos(void);
480 static void go_bottom_and_clear_to_eol(void);
481 static void standout_start(void); // send "start reverse video" sequence
482 static void standout_end(void); // send "end reverse video" sequence
483 static void flash(int); // flash the terminal screen
484 static void show_status_line(void); // put a message on the bottom line
485 static void status_line(const char *, ...); // print to status buf
486 static void status_line_bold(const char *, ...);
487 static void not_implemented(const char *); // display "Not implemented" message
488 static int format_edit_status(void); // format file status on status line
489 static void redraw(int); // force a full screen refresh
490 static char* format_line(char* /*, int*/);
491 static void refresh(int); // update the terminal from screen[]
493 static void Indicate_Error(void); // use flash or beep to indicate error
494 #define indicate_error(c) Indicate_Error()
495 static void Hit_Return(void);
497 #if ENABLE_FEATURE_VI_SEARCH
498 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
499 #endif
500 #if ENABLE_FEATURE_VI_COLON
501 static char *get_one_address(char *, int *); // get colon addr, if present
502 static char *get_address(char *, int *, int *); // get two colon addrs, if present
503 static void colon(char *); // execute the "colon" mode cmds
504 #endif
505 #if ENABLE_FEATURE_VI_USE_SIGNALS
506 static void winch_sig(int); // catch window size changes
507 static void suspend_sig(int); // catch ctrl-Z
508 static void catch_sig(int); // catch ctrl-C and alarm time-outs
509 #endif
510 #if ENABLE_FEATURE_VI_DOT_CMD
511 static void start_new_cmd_q(char); // new queue for command
512 static void end_cmd_q(void); // stop saving input chars
513 #else
514 #define end_cmd_q() ((void)0)
515 #endif
516 #if ENABLE_FEATURE_VI_SETOPTS
517 static void showmatching(char *); // show the matching pair () [] {}
518 #endif
519 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
520 // might reallocate text[]! use p += string_insert(p, ...),
521 // and be careful to not use pointers into potentially freed text[]!
522 static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
523 #endif
524 #if ENABLE_FEATURE_VI_YANKMARK
525 static char *text_yank(char *, char *, int); // save copy of "p" into a register
526 static char what_reg(void); // what is letter of current YDreg
527 static void check_context(char); // remember context for '' command
528 #endif
529 #if ENABLE_FEATURE_VI_CRASHME
530 static void crash_dummy();
531 static void crash_test();
532 static int crashme = 0;
533 #endif
536 static void write1(const char *out)
538 fputs(out, stdout);
541 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
542 int vi_main(int argc, char **argv)
544 int c;
546 INIT_G();
548 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
549 my_pid = getpid();
550 #endif
551 #if ENABLE_FEATURE_VI_CRASHME
552 srand((long) my_pid);
553 #endif
554 #ifdef NO_SUCH_APPLET_YET
555 /* If we aren't "vi", we are "view" */
556 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
557 SET_READONLY_MODE(readonly_mode);
559 #endif
561 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
562 // 1- process $HOME/.exrc file (not inplemented yet)
563 // 2- process EXINIT variable from environment
564 // 3- process command line args
565 #if ENABLE_FEATURE_VI_COLON
567 char *p = getenv("EXINIT");
568 if (p && *p)
569 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
571 #endif
572 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
573 switch (c) {
574 #if ENABLE_FEATURE_VI_CRASHME
575 case 'C':
576 crashme = 1;
577 break;
578 #endif
579 #if ENABLE_FEATURE_VI_READONLY
580 case 'R': // Read-only flag
581 SET_READONLY_MODE(readonly_mode);
582 break;
583 #endif
584 #if ENABLE_FEATURE_VI_COLON
585 case 'c': // cmd line vi command
586 if (*optarg)
587 initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
588 break;
589 #endif
590 case 'H':
591 show_help();
592 /* fall through */
593 default:
594 bb_show_usage();
595 return 1;
599 // The argv array can be used by the ":next" and ":rewind" commands
600 argv += optind;
601 argc -= optind;
602 save_argc = argc;
603 optind = 0;
605 //----- This is the main file handling loop --------------
606 while (1) {
607 edit_file(argv[optind]); /* param might be NULL */
608 if (++optind >= argc)
609 break;
611 //-----------------------------------------------------------
613 return 0;
616 /* read text from file or create an empty buf */
617 /* will also update current_filename */
618 static int init_text_buffer(char *fn)
620 int rc;
621 int size = file_size(fn); // file size. -1 means does not exist.
623 /* allocate/reallocate text buffer */
624 free(text);
625 text_size = size + 10240;
626 screenbegin = dot = end = text = xzalloc(text_size);
628 if (fn != current_filename) {
629 free(current_filename);
630 current_filename = xstrdup(fn);
632 if (size < 0) {
633 // file dont exist. Start empty buf with dummy line
634 char_insert(text, '\n');
635 rc = 0;
636 } else {
637 rc = file_insert(fn, text, 1);
639 file_modified = 0;
640 last_file_modified = -1;
641 #if ENABLE_FEATURE_VI_YANKMARK
642 /* init the marks. */
643 memset(mark, 0, sizeof(mark));
644 #endif
645 return rc;
648 #if ENABLE_FEATURE_VI_WIN_RESIZE
649 static int query_screen_dimensions(void)
651 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
652 if (rows > MAX_SCR_ROWS)
653 rows = MAX_SCR_ROWS;
654 if (columns > MAX_SCR_COLS)
655 columns = MAX_SCR_COLS;
656 return err;
658 #else
659 # define query_screen_dimensions() (0)
660 #endif
662 static void edit_file(char *fn)
664 #if ENABLE_FEATURE_VI_YANKMARK
665 #define cur_line edit_file__cur_line
666 #endif
667 int c;
668 #if ENABLE_FEATURE_VI_USE_SIGNALS
669 int sig;
670 #endif
672 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
673 rawmode();
674 rows = 24;
675 columns = 80;
676 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
677 #if ENABLE_FEATURE_VI_ASK_TERMINAL
678 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
679 uint64_t k;
680 write1("\033[999;999H" "\033[6n");
681 fflush_all();
682 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
683 if ((int32_t)k == KEYCODE_CURSOR_POS) {
684 uint32_t rc = (k >> 32);
685 columns = (rc & 0x7fff);
686 if (columns > MAX_SCR_COLS)
687 columns = MAX_SCR_COLS;
688 rows = ((rc >> 16) & 0x7fff);
689 if (rows > MAX_SCR_ROWS)
690 rows = MAX_SCR_ROWS;
693 #endif
694 new_screen(rows, columns); // get memory for virtual screen
695 init_text_buffer(fn);
697 #if ENABLE_FEATURE_VI_YANKMARK
698 YDreg = 26; // default Yank/Delete reg
699 Ureg = 27; // hold orig line for "U" cmd
700 mark[26] = mark[27] = text; // init "previous context"
701 #endif
703 last_forward_char = last_input_char = '\0';
704 crow = 0;
705 ccol = 0;
707 #if ENABLE_FEATURE_VI_USE_SIGNALS
708 signal(SIGINT, catch_sig);
709 signal(SIGWINCH, winch_sig);
710 signal(SIGTSTP, suspend_sig);
711 sig = sigsetjmp(restart, 1);
712 if (sig != 0) {
713 screenbegin = dot = text;
715 #endif
717 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
718 cmdcnt = 0;
719 tabstop = 8;
720 offset = 0; // no horizontal offset
721 c = '\0';
722 #if ENABLE_FEATURE_VI_DOT_CMD
723 free(ioq_start);
724 ioq = ioq_start = NULL;
725 lmc_len = 0;
726 adding2q = 0;
727 #endif
729 #if ENABLE_FEATURE_VI_COLON
731 char *p, *q;
732 int n = 0;
734 while ((p = initial_cmds[n]) != NULL) {
735 do {
736 q = p;
737 p = strchr(q, '\n');
738 if (p)
739 while (*p == '\n')
740 *p++ = '\0';
741 if (*q)
742 colon(q);
743 } while (p);
744 free(initial_cmds[n]);
745 initial_cmds[n] = NULL;
746 n++;
749 #endif
750 redraw(FALSE); // dont force every col re-draw
751 //------This is the main Vi cmd handling loop -----------------------
752 while (editing > 0) {
753 #if ENABLE_FEATURE_VI_CRASHME
754 if (crashme > 0) {
755 if ((end - text) > 1) {
756 crash_dummy(); // generate a random command
757 } else {
758 crashme = 0;
759 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
760 dot = text;
761 refresh(FALSE);
764 #endif
765 last_input_char = c = get_one_char(); // get a cmd from user
766 #if ENABLE_FEATURE_VI_YANKMARK
767 // save a copy of the current line- for the 'U" command
768 if (begin_line(dot) != cur_line) {
769 cur_line = begin_line(dot);
770 text_yank(begin_line(dot), end_line(dot), Ureg);
772 #endif
773 #if ENABLE_FEATURE_VI_DOT_CMD
774 // These are commands that change text[].
775 // Remember the input for the "." command
776 if (!adding2q && ioq_start == NULL
777 && cmd_mode == 0 // command mode
778 && c > '\0' // exclude NUL and non-ASCII chars
779 && c < 0x7f // (Unicode and such)
780 && strchr(modifying_cmds, c)
782 start_new_cmd_q(c);
784 #endif
785 do_cmd(c); // execute the user command
787 // poll to see if there is input already waiting. if we are
788 // not able to display output fast enough to keep up, skip
789 // the display update until we catch up with input.
790 if (!readbuffer[0] && mysleep(0) == 0) {
791 // no input pending - so update output
792 refresh(FALSE);
793 show_status_line();
795 #if ENABLE_FEATURE_VI_CRASHME
796 if (crashme > 0)
797 crash_test(); // test editor variables
798 #endif
800 //-------------------------------------------------------------------
802 go_bottom_and_clear_to_eol();
803 cookmode();
804 #undef cur_line
807 //----- The Colon commands -------------------------------------
808 #if ENABLE_FEATURE_VI_COLON
809 static char *get_one_address(char *p, int *addr) // get colon addr, if present
811 int st;
812 char *q;
813 IF_FEATURE_VI_YANKMARK(char c;)
814 IF_FEATURE_VI_SEARCH(char *pat;)
816 *addr = -1; // assume no addr
817 if (*p == '.') { // the current line
818 p++;
819 q = begin_line(dot);
820 *addr = count_lines(text, q);
822 #if ENABLE_FEATURE_VI_YANKMARK
823 else if (*p == '\'') { // is this a mark addr
824 p++;
825 c = tolower(*p);
826 p++;
827 if (c >= 'a' && c <= 'z') {
828 // we have a mark
829 c = c - 'a';
830 q = mark[(unsigned char) c];
831 if (q != NULL) { // is mark valid
832 *addr = count_lines(text, q);
836 #endif
837 #if ENABLE_FEATURE_VI_SEARCH
838 else if (*p == '/') { // a search pattern
839 q = strchrnul(++p, '/');
840 pat = xstrndup(p, q - p); // save copy of pattern
841 p = q;
842 if (*p == '/')
843 p++;
844 q = char_search(dot, pat, FORWARD, FULL);
845 if (q != NULL) {
846 *addr = count_lines(text, q);
848 free(pat);
850 #endif
851 else if (*p == '$') { // the last line in file
852 p++;
853 q = begin_line(end - 1);
854 *addr = count_lines(text, q);
855 } else if (isdigit(*p)) { // specific line number
856 sscanf(p, "%d%n", addr, &st);
857 p += st;
858 } else {
859 // unrecognized address - assume -1
860 *addr = -1;
862 return p;
865 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
867 //----- get the address' i.e., 1,3 'a,'b -----
868 // get FIRST addr, if present
869 while (isblank(*p))
870 p++; // skip over leading spaces
871 if (*p == '%') { // alias for 1,$
872 p++;
873 *b = 1;
874 *e = count_lines(text, end-1);
875 goto ga0;
877 p = get_one_address(p, b);
878 while (isblank(*p))
879 p++;
880 if (*p == ',') { // is there a address separator
881 p++;
882 while (isblank(*p))
883 p++;
884 // get SECOND addr, if present
885 p = get_one_address(p, e);
887 ga0:
888 while (isblank(*p))
889 p++; // skip over trailing spaces
890 return p;
893 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
894 static void setops(const char *args, const char *opname, int flg_no,
895 const char *short_opname, int opt)
897 const char *a = args + flg_no;
898 int l = strlen(opname) - 1; /* opname have + ' ' */
900 // maybe strncmp? we had tons of erroneous strncasecmp's...
901 if (strncasecmp(a, opname, l) == 0
902 || strncasecmp(a, short_opname, 2) == 0
904 if (flg_no)
905 vi_setops &= ~opt;
906 else
907 vi_setops |= opt;
910 #endif
912 // buf must be no longer than MAX_INPUT_LEN!
913 static void colon(char *buf)
915 char c, *orig_buf, *buf1, *q, *r;
916 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
917 int i, l, li, ch, b, e;
918 int useforce, forced = FALSE;
920 // :3154 // if (-e line 3154) goto it else stay put
921 // :4,33w! foo // write a portion of buffer to file "foo"
922 // :w // write all of buffer to current file
923 // :q // quit
924 // :q! // quit- dont care about modified file
925 // :'a,'z!sort -u // filter block through sort
926 // :'f // goto mark "f"
927 // :'fl // list literal the mark "f" line
928 // :.r bar // read file "bar" into buffer before dot
929 // :/123/,/abc/d // delete lines from "123" line to "abc" line
930 // :/xyz/ // goto the "xyz" line
931 // :s/find/replace/ // substitute pattern "find" with "replace"
932 // :!<cmd> // run <cmd> then return
935 if (!buf[0])
936 goto ret;
937 if (*buf == ':')
938 buf++; // move past the ':'
940 li = ch = i = 0;
941 b = e = -1;
942 q = text; // assume 1,$ for the range
943 r = end - 1;
944 li = count_lines(text, end - 1);
945 fn = current_filename;
947 // look for optional address(es) :. :1 :1,9 :'q,'a :%
948 buf = get_address(buf, &b, &e);
950 // remember orig command line
951 orig_buf = buf;
953 // get the COMMAND into cmd[]
954 buf1 = cmd;
955 while (*buf != '\0') {
956 if (isspace(*buf))
957 break;
958 *buf1++ = *buf++;
960 *buf1 = '\0';
961 // get any ARGuments
962 while (isblank(*buf))
963 buf++;
964 strcpy(args, buf);
965 useforce = FALSE;
966 buf1 = last_char_is(cmd, '!');
967 if (buf1) {
968 useforce = TRUE;
969 *buf1 = '\0'; // get rid of !
971 if (b >= 0) {
972 // if there is only one addr, then the addr
973 // is the line number of the single line the
974 // user wants. So, reset the end
975 // pointer to point at end of the "b" line
976 q = find_line(b); // what line is #b
977 r = end_line(q);
978 li = 1;
980 if (e >= 0) {
981 // we were given two addrs. change the
982 // end pointer to the addr given by user.
983 r = find_line(e); // what line is #e
984 r = end_line(r);
985 li = e - b + 1;
987 // ------------ now look for the command ------------
988 i = strlen(cmd);
989 if (i == 0) { // :123CR goto line #123
990 if (b >= 0) {
991 dot = find_line(b); // what line is #b
992 dot_skip_over_ws();
995 #if ENABLE_FEATURE_ALLOW_EXEC
996 else if (cmd[0] == '!') { // run a cmd
997 int retcode;
998 // :!ls run the <cmd>
999 go_bottom_and_clear_to_eol();
1000 cookmode();
1001 retcode = system(orig_buf + 1); // run the cmd
1002 if (retcode)
1003 printf("\nshell returned %i\n\n", retcode);
1004 rawmode();
1005 Hit_Return(); // let user see results
1007 #endif
1008 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1009 if (b < 0) { // no addr given- use defaults
1010 b = e = count_lines(text, dot);
1012 status_line("%d", b);
1013 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1014 if (b < 0) { // no addr given- use defaults
1015 q = begin_line(dot); // assume .,. for the range
1016 r = end_line(dot);
1018 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1019 dot_skip_over_ws();
1020 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1021 // don't edit, if the current file has been modified
1022 if (file_modified && !useforce) {
1023 status_line_bold("No write since last change (:%s! overrides)", cmd);
1024 goto ret;
1026 if (args[0]) {
1027 // the user supplied a file name
1028 fn = args;
1029 } else if (current_filename && current_filename[0]) {
1030 // no user supplied name- use the current filename
1031 // fn = current_filename; was set by default
1032 } else {
1033 // no user file name, no current name- punt
1034 status_line_bold("No current filename");
1035 goto ret;
1038 if (init_text_buffer(fn) < 0)
1039 goto ret;
1041 #if ENABLE_FEATURE_VI_YANKMARK
1042 if (Ureg >= 0 && Ureg < 28) {
1043 free(reg[Ureg]); // free orig line reg- for 'U'
1044 reg[Ureg] = NULL;
1046 if (YDreg >= 0 && YDreg < 28) {
1047 free(reg[YDreg]); // free default yank/delete register
1048 reg[YDreg] = NULL;
1050 #endif
1051 // how many lines in text[]?
1052 li = count_lines(text, end - 1);
1053 status_line("\"%s\"%s"
1054 IF_FEATURE_VI_READONLY("%s")
1055 " %dL, %dC", current_filename,
1056 (file_size(fn) < 0 ? " [New file]" : ""),
1057 IF_FEATURE_VI_READONLY(
1058 ((readonly_mode) ? " [Readonly]" : ""),
1060 li, ch);
1061 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1062 if (b != -1 || e != -1) {
1063 status_line_bold("No address allowed on this command");
1064 goto ret;
1066 if (args[0]) {
1067 // user wants a new filename
1068 free(current_filename);
1069 current_filename = xstrdup(args);
1070 } else {
1071 // user wants file status info
1072 last_status_cksum = 0; // force status update
1074 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1075 // print out values of all features
1076 go_bottom_and_clear_to_eol();
1077 cookmode();
1078 show_help();
1079 rawmode();
1080 Hit_Return();
1081 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1082 if (b < 0) { // no addr given- use defaults
1083 q = begin_line(dot); // assume .,. for the range
1084 r = end_line(dot);
1086 go_bottom_and_clear_to_eol();
1087 puts("\r");
1088 for (; q <= r; q++) {
1089 int c_is_no_print;
1091 c = *q;
1092 c_is_no_print = (c & 0x80) && !Isprint(c);
1093 if (c_is_no_print) {
1094 c = '.';
1095 standout_start();
1097 if (c == '\n') {
1098 write1("$\r");
1099 } else if (c < ' ' || c == 127) {
1100 bb_putchar('^');
1101 if (c == 127)
1102 c = '?';
1103 else
1104 c += '@';
1106 bb_putchar(c);
1107 if (c_is_no_print)
1108 standout_end();
1110 Hit_Return();
1111 } else if (strncmp(cmd, "quit", i) == 0 // quit
1112 || strncmp(cmd, "next", i) == 0 // edit next file
1113 || strncmp(cmd, "prev", i) == 0 // edit previous file
1115 int n;
1116 if (useforce) {
1117 if (*cmd == 'q') {
1118 // force end of argv list
1119 optind = save_argc;
1121 editing = 0;
1122 goto ret;
1124 // don't exit if the file been modified
1125 if (file_modified) {
1126 status_line_bold("No write since last change (:%s! overrides)", cmd);
1127 goto ret;
1129 // are there other file to edit
1130 n = save_argc - optind - 1;
1131 if (*cmd == 'q' && n > 0) {
1132 status_line_bold("%d more file(s) to edit", n);
1133 goto ret;
1135 if (*cmd == 'n' && n <= 0) {
1136 status_line_bold("No more files to edit");
1137 goto ret;
1139 if (*cmd == 'p') {
1140 // are there previous files to edit
1141 if (optind < 1) {
1142 status_line_bold("No previous files to edit");
1143 goto ret;
1145 optind -= 2;
1147 editing = 0;
1148 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1149 fn = args;
1150 if (!fn[0]) {
1151 status_line_bold("No filename given");
1152 goto ret;
1154 if (b < 0) { // no addr given- use defaults
1155 q = begin_line(dot); // assume "dot"
1157 // read after current line- unless user said ":0r foo"
1158 if (b != 0)
1159 q = next_line(q);
1160 { // dance around potentially-reallocated text[]
1161 uintptr_t ofs = q - text;
1162 ch = file_insert(fn, q, 0);
1163 q = text + ofs;
1165 if (ch < 0)
1166 goto ret; // nothing was inserted
1167 // how many lines in text[]?
1168 li = count_lines(q, q + ch - 1);
1169 status_line("\"%s\""
1170 IF_FEATURE_VI_READONLY("%s")
1171 " %dL, %dC", fn,
1172 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1173 li, ch);
1174 if (ch > 0) {
1175 // if the insert is before "dot" then we need to update
1176 if (q <= dot)
1177 dot += ch;
1178 /*file_modified++; - done by file_insert */
1180 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1181 if (file_modified && !useforce) {
1182 status_line_bold("No write since last change (:%s! overrides)", cmd);
1183 } else {
1184 // reset the filenames to edit
1185 optind = -1; /* start from 0th file */
1186 editing = 0;
1188 #if ENABLE_FEATURE_VI_SET
1189 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1190 #if ENABLE_FEATURE_VI_SETOPTS
1191 char *argp;
1192 #endif
1193 i = 0; // offset into args
1194 // only blank is regarded as args delmiter. What about tab '\t' ?
1195 if (!args[0] || strcasecmp(args, "all") == 0) {
1196 // print out values of all options
1197 #if ENABLE_FEATURE_VI_SETOPTS
1198 status_line_bold(
1199 "%sautoindent "
1200 "%sflash "
1201 "%signorecase "
1202 "%sshowmatch "
1203 "tabstop=%u",
1204 autoindent ? "" : "no",
1205 err_method ? "" : "no",
1206 ignorecase ? "" : "no",
1207 showmatch ? "" : "no",
1208 tabstop
1210 #endif
1211 goto ret;
1213 #if ENABLE_FEATURE_VI_SETOPTS
1214 argp = args;
1215 while (*argp) {
1216 if (strncmp(argp, "no", 2) == 0)
1217 i = 2; // ":set noautoindent"
1218 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1219 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1220 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1221 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1222 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1223 int t = 0;
1224 sscanf(argp + i+8, "%u", &t);
1225 if (t > 0 && t <= MAX_TABSTOP)
1226 tabstop = t;
1228 argp = skip_non_whitespace(argp);
1229 argp = skip_whitespace(argp);
1231 #endif /* FEATURE_VI_SETOPTS */
1232 #endif /* FEATURE_VI_SET */
1233 #if ENABLE_FEATURE_VI_SEARCH
1234 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1235 char *F, *R, *flags;
1236 size_t len_F, len_R;
1237 int gflag; // global replace flag
1239 // F points to the "find" pattern
1240 // R points to the "replace" pattern
1241 // replace the cmd line delimiters "/" with NULs
1242 c = orig_buf[1]; // what is the delimiter
1243 F = orig_buf + 2; // start of "find"
1244 R = strchr(F, c); // middle delimiter
1245 if (!R)
1246 goto colon_s_fail;
1247 len_F = R - F;
1248 *R++ = '\0'; // terminate "find"
1249 flags = strchr(R, c);
1250 if (!flags)
1251 goto colon_s_fail;
1252 len_R = flags - R;
1253 *flags++ = '\0'; // terminate "replace"
1254 gflag = *flags;
1256 q = begin_line(q);
1257 if (b < 0) { // maybe :s/foo/bar/
1258 q = begin_line(dot); // start with cur line
1259 b = count_lines(text, q); // cur line number
1261 if (e < 0)
1262 e = b; // maybe :.s/foo/bar/
1264 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1265 char *ls = q; // orig line start
1266 char *found;
1267 vc4:
1268 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1269 if (found) {
1270 uintptr_t bias;
1271 // we found the "find" pattern - delete it
1272 text_hole_delete(found, found + len_F - 1);
1273 // inset the "replace" patern
1274 bias = string_insert(found, R); // insert the string
1275 found += bias;
1276 ls += bias;
1277 /*q += bias; - recalculated anyway */
1278 // check for "global" :s/foo/bar/g
1279 if (gflag == 'g') {
1280 if ((found + len_R) < end_line(ls)) {
1281 q = found + len_R;
1282 goto vc4; // don't let q move past cur line
1286 q = next_line(ls);
1288 #endif /* FEATURE_VI_SEARCH */
1289 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1290 status_line(BB_VER " " BB_BT);
1291 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1292 || strncmp(cmd, "wq", i) == 0
1293 || strncmp(cmd, "wn", i) == 0
1294 || (cmd[0] == 'x' && !cmd[1])
1296 // is there a file name to write to?
1297 if (args[0]) {
1298 fn = args;
1300 #if ENABLE_FEATURE_VI_READONLY
1301 if (readonly_mode && !useforce) {
1302 status_line_bold("\"%s\" File is read only", fn);
1303 goto ret;
1305 #endif
1306 // how many lines in text[]?
1307 li = count_lines(q, r);
1308 ch = r - q + 1;
1309 // see if file exists- if not, its just a new file request
1310 if (useforce) {
1311 // if "fn" is not write-able, chmod u+w
1312 // sprintf(syscmd, "chmod u+w %s", fn);
1313 // system(syscmd);
1314 forced = TRUE;
1316 l = file_write(fn, q, r);
1317 if (useforce && forced) {
1318 // chmod u-w
1319 // sprintf(syscmd, "chmod u-w %s", fn);
1320 // system(syscmd);
1321 forced = FALSE;
1323 if (l < 0) {
1324 if (l == -1)
1325 status_line_bold("\"%s\" %s", fn, strerror(errno));
1326 } else {
1327 status_line("\"%s\" %dL, %dC", fn, li, l);
1328 if (q == text && r == end - 1 && l == ch) {
1329 file_modified = 0;
1330 last_file_modified = -1;
1332 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1333 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1335 && l == ch
1337 editing = 0;
1340 #if ENABLE_FEATURE_VI_YANKMARK
1341 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1342 if (b < 0) { // no addr given- use defaults
1343 q = begin_line(dot); // assume .,. for the range
1344 r = end_line(dot);
1346 text_yank(q, r, YDreg);
1347 li = count_lines(q, r);
1348 status_line("Yank %d lines (%d chars) into [%c]",
1349 li, strlen(reg[YDreg]), what_reg());
1350 #endif
1351 } else {
1352 // cmd unknown
1353 not_implemented(cmd);
1355 ret:
1356 dot = bound_dot(dot); // make sure "dot" is valid
1357 return;
1358 #if ENABLE_FEATURE_VI_SEARCH
1359 colon_s_fail:
1360 status_line(":s expression missing delimiters");
1361 #endif
1364 #endif /* FEATURE_VI_COLON */
1366 static void Hit_Return(void)
1368 int c;
1370 standout_start();
1371 write1("[Hit return to continue]");
1372 standout_end();
1373 while ((c = get_one_char()) != '\n' && c != '\r')
1374 continue;
1375 redraw(TRUE); // force redraw all
1378 static int next_tabstop(int col)
1380 return col + ((tabstop - 1) - (col % tabstop));
1383 //----- Synchronize the cursor to Dot --------------------------
1384 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1386 char *beg_cur; // begin and end of "d" line
1387 char *tp;
1388 int cnt, ro, co;
1390 beg_cur = begin_line(d); // first char of cur line
1392 if (beg_cur < screenbegin) {
1393 // "d" is before top line on screen
1394 // how many lines do we have to move
1395 cnt = count_lines(beg_cur, screenbegin);
1396 sc1:
1397 screenbegin = beg_cur;
1398 if (cnt > (rows - 1) / 2) {
1399 // we moved too many lines. put "dot" in middle of screen
1400 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1401 screenbegin = prev_line(screenbegin);
1404 } else {
1405 char *end_scr; // begin and end of screen
1406 end_scr = end_screen(); // last char of screen
1407 if (beg_cur > end_scr) {
1408 // "d" is after bottom line on screen
1409 // how many lines do we have to move
1410 cnt = count_lines(end_scr, beg_cur);
1411 if (cnt > (rows - 1) / 2)
1412 goto sc1; // too many lines
1413 for (ro = 0; ro < cnt - 1; ro++) {
1414 // move screen begin the same amount
1415 screenbegin = next_line(screenbegin);
1416 // now, move the end of screen
1417 end_scr = next_line(end_scr);
1418 end_scr = end_line(end_scr);
1422 // "d" is on screen- find out which row
1423 tp = screenbegin;
1424 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1425 if (tp == beg_cur)
1426 break;
1427 tp = next_line(tp);
1430 // find out what col "d" is on
1431 co = 0;
1432 while (tp < d) { // drive "co" to correct column
1433 if (*tp == '\n') //vda || *tp == '\0')
1434 break;
1435 if (*tp == '\t') {
1436 // handle tabs like real vi
1437 if (d == tp && cmd_mode) {
1438 break;
1440 co = next_tabstop(co);
1441 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1442 co++; // display as ^X, use 2 columns
1444 co++;
1445 tp++;
1448 // "co" is the column where "dot" is.
1449 // The screen has "columns" columns.
1450 // The currently displayed columns are 0+offset -- columns+ofset
1451 // |-------------------------------------------------------------|
1452 // ^ ^ ^
1453 // offset | |------- columns ----------------|
1455 // If "co" is already in this range then we do not have to adjust offset
1456 // but, we do have to subtract the "offset" bias from "co".
1457 // If "co" is outside this range then we have to change "offset".
1458 // If the first char of a line is a tab the cursor will try to stay
1459 // in column 7, but we have to set offset to 0.
1461 if (co < 0 + offset) {
1462 offset = co;
1464 if (co >= columns + offset) {
1465 offset = co - columns + 1;
1467 // if the first char of the line is a tab, and "dot" is sitting on it
1468 // force offset to 0.
1469 if (d == beg_cur && *d == '\t') {
1470 offset = 0;
1472 co -= offset;
1474 *row = ro;
1475 *col = co;
1478 //----- Text Movement Routines ---------------------------------
1479 static char *begin_line(char *p) // return pointer to first char cur line
1481 if (p > text) {
1482 p = memrchr(text, '\n', p - text);
1483 if (!p)
1484 return text;
1485 return p + 1;
1487 return p;
1490 static char *end_line(char *p) // return pointer to NL of cur line
1492 if (p < end - 1) {
1493 p = memchr(p, '\n', end - p - 1);
1494 if (!p)
1495 return end - 1;
1497 return p;
1500 static char *dollar_line(char *p) // return pointer to just before NL line
1502 p = end_line(p);
1503 // Try to stay off of the Newline
1504 if (*p == '\n' && (p - begin_line(p)) > 0)
1505 p--;
1506 return p;
1509 static char *prev_line(char *p) // return pointer first char prev line
1511 p = begin_line(p); // goto begining of cur line
1512 if (p > text && p[-1] == '\n')
1513 p--; // step to prev line
1514 p = begin_line(p); // goto begining of prev line
1515 return p;
1518 static char *next_line(char *p) // return pointer first char next line
1520 p = end_line(p);
1521 if (p < end - 1 && *p == '\n')
1522 p++; // step to next line
1523 return p;
1526 //----- Text Information Routines ------------------------------
1527 static char *end_screen(void)
1529 char *q;
1530 int cnt;
1532 // find new bottom line
1533 q = screenbegin;
1534 for (cnt = 0; cnt < rows - 2; cnt++)
1535 q = next_line(q);
1536 q = end_line(q);
1537 return q;
1540 // count line from start to stop
1541 static int count_lines(char *start, char *stop)
1543 char *q;
1544 int cnt;
1546 if (stop < start) { // start and stop are backwards- reverse them
1547 q = start;
1548 start = stop;
1549 stop = q;
1551 cnt = 0;
1552 stop = end_line(stop);
1553 while (start <= stop && start <= end - 1) {
1554 start = end_line(start);
1555 if (*start == '\n')
1556 cnt++;
1557 start++;
1559 return cnt;
1562 static char *find_line(int li) // find begining of line #li
1564 char *q;
1566 for (q = text; li > 1; li--) {
1567 q = next_line(q);
1569 return q;
1572 //----- Dot Movement Routines ----------------------------------
1573 static void dot_left(void)
1575 if (dot > text && dot[-1] != '\n')
1576 dot--;
1579 static void dot_right(void)
1581 if (dot < end - 1 && *dot != '\n')
1582 dot++;
1585 static void dot_begin(void)
1587 dot = begin_line(dot); // return pointer to first char cur line
1590 static void dot_end(void)
1592 dot = end_line(dot); // return pointer to last char cur line
1595 static char *move_to_col(char *p, int l)
1597 int co;
1599 p = begin_line(p);
1600 co = 0;
1601 while (co < l && p < end) {
1602 if (*p == '\n') //vda || *p == '\0')
1603 break;
1604 if (*p == '\t') {
1605 co = next_tabstop(co);
1606 } else if (*p < ' ' || *p == 127) {
1607 co++; // display as ^X, use 2 columns
1609 co++;
1610 p++;
1612 return p;
1615 static void dot_next(void)
1617 dot = next_line(dot);
1620 static void dot_prev(void)
1622 dot = prev_line(dot);
1625 static void dot_scroll(int cnt, int dir)
1627 char *q;
1629 for (; cnt > 0; cnt--) {
1630 if (dir < 0) {
1631 // scroll Backwards
1632 // ctrl-Y scroll up one line
1633 screenbegin = prev_line(screenbegin);
1634 } else {
1635 // scroll Forwards
1636 // ctrl-E scroll down one line
1637 screenbegin = next_line(screenbegin);
1640 // make sure "dot" stays on the screen so we dont scroll off
1641 if (dot < screenbegin)
1642 dot = screenbegin;
1643 q = end_screen(); // find new bottom line
1644 if (dot > q)
1645 dot = begin_line(q); // is dot is below bottom line?
1646 dot_skip_over_ws();
1649 static void dot_skip_over_ws(void)
1651 // skip WS
1652 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1653 dot++;
1656 static void dot_delete(void) // delete the char at 'dot'
1658 text_hole_delete(dot, dot);
1661 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1663 if (p >= end && end > text) {
1664 p = end - 1;
1665 indicate_error('1');
1667 if (p < text) {
1668 p = text;
1669 indicate_error('2');
1671 return p;
1674 //----- Helper Utility Routines --------------------------------
1676 //----------------------------------------------------------------
1677 //----- Char Routines --------------------------------------------
1678 /* Chars that are part of a word-
1679 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1680 * Chars that are Not part of a word (stoppers)
1681 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1682 * Chars that are WhiteSpace
1683 * TAB NEWLINE VT FF RETURN SPACE
1684 * DO NOT COUNT NEWLINE AS WHITESPACE
1687 static char *new_screen(int ro, int co)
1689 int li;
1691 free(screen);
1692 screensize = ro * co + 8;
1693 screen = xmalloc(screensize);
1694 // initialize the new screen. assume this will be a empty file.
1695 screen_erase();
1696 // non-existent text[] lines start with a tilde (~).
1697 for (li = 1; li < ro - 1; li++) {
1698 screen[(li * co) + 0] = '~';
1700 return screen;
1703 #if ENABLE_FEATURE_VI_SEARCH
1705 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1707 // search for pattern starting at p
1708 static char *char_search(char *p, const char *pat, int dir, int range)
1710 char *q;
1711 struct re_pattern_buffer preg;
1712 int i;
1713 int size;
1715 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1716 preg.translate = 0;
1717 preg.fastmap = 0;
1718 preg.buffer = 0;
1719 preg.allocated = 0;
1721 // assume a LIMITED forward search
1722 q = next_line(p);
1723 q = end_line(q);
1724 q = end - 1;
1725 if (dir == BACK) {
1726 q = prev_line(p);
1727 q = text;
1729 // count the number of chars to search over, forward or backward
1730 size = q - p;
1731 if (size < 0)
1732 size = p - q;
1733 // RANGE could be negative if we are searching backwards
1734 range = q - p;
1736 q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1737 if (q != 0) {
1738 // The pattern was not compiled
1739 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1740 i = 0; // return p if pattern not compiled
1741 goto cs1;
1744 q = p;
1745 if (range < 0) {
1746 q = p - size;
1747 if (q < text)
1748 q = text;
1750 // search for the compiled pattern, preg, in p[]
1751 // range < 0- search backward
1752 // range > 0- search forward
1753 // 0 < start < size
1754 // re_search() < 0 not found or error
1755 // re_search() > 0 index of found pattern
1756 // struct pattern char int int int struct reg
1757 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1758 i = re_search(&preg, q, size, 0, range, 0);
1759 if (i == -1) {
1760 p = 0;
1761 i = 0; // return NULL if pattern not found
1763 cs1:
1764 if (dir == FORWARD) {
1765 p = p + i;
1766 } else {
1767 p = p - i;
1769 return p;
1772 # else
1774 # if ENABLE_FEATURE_VI_SETOPTS
1775 static int mycmp(const char *s1, const char *s2, int len)
1777 if (ignorecase) {
1778 return strncasecmp(s1, s2, len);
1780 return strncmp(s1, s2, len);
1782 # else
1783 # define mycmp strncmp
1784 # endif
1786 static char *char_search(char *p, const char *pat, int dir, int range)
1788 char *start, *stop;
1789 int len;
1791 len = strlen(pat);
1792 if (dir == FORWARD) {
1793 stop = end - 1; // assume range is p - end-1
1794 if (range == LIMITED)
1795 stop = next_line(p); // range is to next line
1796 for (start = p; start < stop; start++) {
1797 if (mycmp(start, pat, len) == 0) {
1798 return start;
1801 } else if (dir == BACK) {
1802 stop = text; // assume range is text - p
1803 if (range == LIMITED)
1804 stop = prev_line(p); // range is to prev line
1805 for (start = p - len; start >= stop; start--) {
1806 if (mycmp(start, pat, len) == 0) {
1807 return start;
1811 // pattern not found
1812 return NULL;
1815 # endif
1817 #endif /* FEATURE_VI_SEARCH */
1819 static char *char_insert(char *p, char c) // insert the char c at 'p'
1821 if (c == 22) { // Is this an ctrl-V?
1822 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1823 refresh(FALSE); // show the ^
1824 c = get_one_char();
1825 *p = c;
1826 p++;
1827 file_modified++;
1828 } else if (c == 27) { // Is this an ESC?
1829 cmd_mode = 0;
1830 cmdcnt = 0;
1831 end_cmd_q(); // stop adding to q
1832 last_status_cksum = 0; // force status update
1833 if ((p[-1] != '\n') && (dot > text)) {
1834 p--;
1836 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1837 // 123456789
1838 if ((p[-1] != '\n') && (dot>text)) {
1839 p--;
1840 p = text_hole_delete(p, p); // shrink buffer 1 char
1842 } else {
1843 #if ENABLE_FEATURE_VI_SETOPTS
1844 // insert a char into text[]
1845 char *sp; // "save p"
1846 #endif
1848 if (c == 13)
1849 c = '\n'; // translate \r to \n
1850 #if ENABLE_FEATURE_VI_SETOPTS
1851 sp = p; // remember addr of insert
1852 #endif
1853 p += 1 + stupid_insert(p, c); // insert the char
1854 #if ENABLE_FEATURE_VI_SETOPTS
1855 if (showmatch && strchr(")]}", *sp) != NULL) {
1856 showmatching(sp);
1858 if (autoindent && c == '\n') { // auto indent the new line
1859 char *q;
1860 size_t len;
1861 q = prev_line(p); // use prev line as template
1862 len = strspn(q, " \t"); // space or tab
1863 if (len) {
1864 uintptr_t bias;
1865 bias = text_hole_make(p, len);
1866 p += bias;
1867 q += bias;
1868 memcpy(p, q, len);
1869 p += len;
1872 #endif
1874 return p;
1877 // might reallocate text[]! use p += stupid_insert(p, ...),
1878 // and be careful to not use pointers into potentially freed text[]!
1879 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1881 uintptr_t bias;
1882 bias = text_hole_make(p, 1);
1883 p += bias;
1884 *p = c;
1885 //file_modified++; - done by text_hole_make()
1886 return bias;
1889 static int find_range(char **start, char **stop, char c)
1891 char *save_dot, *p, *q, *t;
1892 int cnt, multiline = 0;
1894 save_dot = dot;
1895 p = q = dot;
1897 if (strchr("cdy><", c)) {
1898 // these cmds operate on whole lines
1899 p = q = begin_line(p);
1900 for (cnt = 1; cnt < cmdcnt; cnt++) {
1901 q = next_line(q);
1903 q = end_line(q);
1904 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1905 // These cmds operate on char positions
1906 do_cmd(c); // execute movement cmd
1907 q = dot;
1908 } else if (strchr("wW", c)) {
1909 do_cmd(c); // execute movement cmd
1910 // if we are at the next word's first char
1911 // step back one char
1912 // but check the possibilities when it is true
1913 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1914 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1915 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1916 dot--; // move back off of next word
1917 if (dot > text && *dot == '\n')
1918 dot--; // stay off NL
1919 q = dot;
1920 } else if (strchr("H-k{", c)) {
1921 // these operate on multi-lines backwards
1922 q = end_line(dot); // find NL
1923 do_cmd(c); // execute movement cmd
1924 dot_begin();
1925 p = dot;
1926 } else if (strchr("L+j}\r\n", c)) {
1927 // these operate on multi-lines forwards
1928 p = begin_line(dot);
1929 do_cmd(c); // execute movement cmd
1930 dot_end(); // find NL
1931 q = dot;
1932 } else {
1933 // nothing -- this causes any other values of c to
1934 // represent the one-character range under the
1935 // cursor. this is correct for ' ' and 'l', but
1936 // perhaps no others.
1939 if (q < p) {
1940 t = q;
1941 q = p;
1942 p = t;
1945 // backward char movements don't include start position
1946 if (q > p && strchr("^0bBh\b\177", c)) q--;
1948 multiline = 0;
1949 for (t = p; t <= q; t++) {
1950 if (*t == '\n') {
1951 multiline = 1;
1952 break;
1956 *start = p;
1957 *stop = q;
1958 dot = save_dot;
1959 return multiline;
1962 static int st_test(char *p, int type, int dir, char *tested)
1964 char c, c0, ci;
1965 int test, inc;
1967 inc = dir;
1968 c = c0 = p[0];
1969 ci = p[inc];
1970 test = 0;
1972 if (type == S_BEFORE_WS) {
1973 c = ci;
1974 test = (!isspace(c) || c == '\n');
1976 if (type == S_TO_WS) {
1977 c = c0;
1978 test = (!isspace(c) || c == '\n');
1980 if (type == S_OVER_WS) {
1981 c = c0;
1982 test = isspace(c);
1984 if (type == S_END_PUNCT) {
1985 c = ci;
1986 test = ispunct(c);
1988 if (type == S_END_ALNUM) {
1989 c = ci;
1990 test = (isalnum(c) || c == '_');
1992 *tested = c;
1993 return test;
1996 static char *skip_thing(char *p, int linecnt, int dir, int type)
1998 char c;
2000 while (st_test(p, type, dir, &c)) {
2001 // make sure we limit search to correct number of lines
2002 if (c == '\n' && --linecnt < 1)
2003 break;
2004 if (dir >= 0 && p >= end - 1)
2005 break;
2006 if (dir < 0 && p <= text)
2007 break;
2008 p += dir; // move to next char
2010 return p;
2013 // find matching char of pair () [] {}
2014 static char *find_pair(char *p, const char c)
2016 char match, *q;
2017 int dir, level;
2019 match = ')';
2020 level = 1;
2021 dir = 1; // assume forward
2022 switch (c) {
2023 case '(': match = ')'; break;
2024 case '[': match = ']'; break;
2025 case '{': match = '}'; break;
2026 case ')': match = '('; dir = -1; break;
2027 case ']': match = '['; dir = -1; break;
2028 case '}': match = '{'; dir = -1; break;
2030 for (q = p + dir; text <= q && q < end; q += dir) {
2031 // look for match, count levels of pairs (( ))
2032 if (*q == c)
2033 level++; // increase pair levels
2034 if (*q == match)
2035 level--; // reduce pair level
2036 if (level == 0)
2037 break; // found matching pair
2039 if (level != 0)
2040 q = NULL; // indicate no match
2041 return q;
2044 #if ENABLE_FEATURE_VI_SETOPTS
2045 // show the matching char of a pair, () [] {}
2046 static void showmatching(char *p)
2048 char *q, *save_dot;
2050 // we found half of a pair
2051 q = find_pair(p, *p); // get loc of matching char
2052 if (q == NULL) {
2053 indicate_error('3'); // no matching char
2054 } else {
2055 // "q" now points to matching pair
2056 save_dot = dot; // remember where we are
2057 dot = q; // go to new loc
2058 refresh(FALSE); // let the user see it
2059 mysleep(40); // give user some time
2060 dot = save_dot; // go back to old loc
2061 refresh(FALSE);
2064 #endif /* FEATURE_VI_SETOPTS */
2066 // open a hole in text[]
2067 // might reallocate text[]! use p += text_hole_make(p, ...),
2068 // and be careful to not use pointers into potentially freed text[]!
2069 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2071 uintptr_t bias = 0;
2073 if (size <= 0)
2074 return bias;
2075 end += size; // adjust the new END
2076 if (end >= (text + text_size)) {
2077 char *new_text;
2078 text_size += end - (text + text_size) + 10240;
2079 new_text = xrealloc(text, text_size);
2080 bias = (new_text - text);
2081 screenbegin += bias;
2082 dot += bias;
2083 end += bias;
2084 p += bias;
2085 #if ENABLE_FEATURE_VI_YANKMARK
2087 int i;
2088 for (i = 0; i < ARRAY_SIZE(mark); i++)
2089 if (mark[i])
2090 mark[i] += bias;
2092 #endif
2093 text = new_text;
2095 memmove(p + size, p, end - size - p);
2096 memset(p, ' ', size); // clear new hole
2097 file_modified++;
2098 return bias;
2101 // close a hole in text[]
2102 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2104 char *src, *dest;
2105 int cnt, hole_size;
2107 // move forwards, from beginning
2108 // assume p <= q
2109 src = q + 1;
2110 dest = p;
2111 if (q < p) { // they are backward- swap them
2112 src = p + 1;
2113 dest = q;
2115 hole_size = q - p + 1;
2116 cnt = end - src;
2117 if (src < text || src > end)
2118 goto thd0;
2119 if (dest < text || dest >= end)
2120 goto thd0;
2121 if (src >= end)
2122 goto thd_atend; // just delete the end of the buffer
2123 memmove(dest, src, cnt);
2124 thd_atend:
2125 end = end - hole_size; // adjust the new END
2126 if (dest >= end)
2127 dest = end - 1; // make sure dest in below end-1
2128 if (end <= text)
2129 dest = end = text; // keep pointers valid
2130 file_modified++;
2131 thd0:
2132 return dest;
2135 // copy text into register, then delete text.
2136 // if dist <= 0, do not include, or go past, a NewLine
2138 static char *yank_delete(char *start, char *stop, int dist, int yf)
2140 char *p;
2142 // make sure start <= stop
2143 if (start > stop) {
2144 // they are backwards, reverse them
2145 p = start;
2146 start = stop;
2147 stop = p;
2149 if (dist <= 0) {
2150 // we cannot cross NL boundaries
2151 p = start;
2152 if (*p == '\n')
2153 return p;
2154 // dont go past a NewLine
2155 for (; p + 1 <= stop; p++) {
2156 if (p[1] == '\n') {
2157 stop = p; // "stop" just before NewLine
2158 break;
2162 p = start;
2163 #if ENABLE_FEATURE_VI_YANKMARK
2164 text_yank(start, stop, YDreg);
2165 #endif
2166 if (yf == YANKDEL) {
2167 p = text_hole_delete(start, stop);
2168 } // delete lines
2169 return p;
2172 static void show_help(void)
2174 puts("These features are available:"
2175 #if ENABLE_FEATURE_VI_SEARCH
2176 "\n\tPattern searches with / and ?"
2177 #endif
2178 #if ENABLE_FEATURE_VI_DOT_CMD
2179 "\n\tLast command repeat with \'.\'"
2180 #endif
2181 #if ENABLE_FEATURE_VI_YANKMARK
2182 "\n\tLine marking with 'x"
2183 "\n\tNamed buffers with \"x"
2184 #endif
2185 #if ENABLE_FEATURE_VI_READONLY
2186 //not implemented: "\n\tReadonly if vi is called as \"view\""
2187 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2188 #endif
2189 #if ENABLE_FEATURE_VI_SET
2190 "\n\tSome colon mode commands with \':\'"
2191 #endif
2192 #if ENABLE_FEATURE_VI_SETOPTS
2193 "\n\tSettable options with \":set\""
2194 #endif
2195 #if ENABLE_FEATURE_VI_USE_SIGNALS
2196 "\n\tSignal catching- ^C"
2197 "\n\tJob suspend and resume with ^Z"
2198 #endif
2199 #if ENABLE_FEATURE_VI_WIN_RESIZE
2200 "\n\tAdapt to window re-sizes"
2201 #endif
2205 #if ENABLE_FEATURE_VI_DOT_CMD
2206 static void start_new_cmd_q(char c)
2208 // get buffer for new cmd
2209 // if there is a current cmd count put it in the buffer first
2210 if (cmdcnt > 0) {
2211 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2212 } else { // just save char c onto queue
2213 last_modifying_cmd[0] = c;
2214 lmc_len = 1;
2216 adding2q = 1;
2219 static void end_cmd_q(void)
2221 #if ENABLE_FEATURE_VI_YANKMARK
2222 YDreg = 26; // go back to default Yank/Delete reg
2223 #endif
2224 adding2q = 0;
2226 #endif /* FEATURE_VI_DOT_CMD */
2228 #if ENABLE_FEATURE_VI_YANKMARK \
2229 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2230 || ENABLE_FEATURE_VI_CRASHME
2231 // might reallocate text[]! use p += string_insert(p, ...),
2232 // and be careful to not use pointers into potentially freed text[]!
2233 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2235 uintptr_t bias;
2236 int i;
2238 i = strlen(s);
2239 bias = text_hole_make(p, i);
2240 p += bias;
2241 memcpy(p, s, i);
2242 #if ENABLE_FEATURE_VI_YANKMARK
2244 int cnt;
2245 for (cnt = 0; *s != '\0'; s++) {
2246 if (*s == '\n')
2247 cnt++;
2249 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2251 #endif
2252 return bias;
2254 #endif
2256 #if ENABLE_FEATURE_VI_YANKMARK
2257 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2259 int cnt = q - p;
2260 if (cnt < 0) { // they are backwards- reverse them
2261 p = q;
2262 cnt = -cnt;
2264 free(reg[dest]); // if already a yank register, free it
2265 reg[dest] = xstrndup(p, cnt + 1);
2266 return p;
2269 static char what_reg(void)
2271 char c;
2273 c = 'D'; // default to D-reg
2274 if (0 <= YDreg && YDreg <= 25)
2275 c = 'a' + (char) YDreg;
2276 if (YDreg == 26)
2277 c = 'D';
2278 if (YDreg == 27)
2279 c = 'U';
2280 return c;
2283 static void check_context(char cmd)
2285 // A context is defined to be "modifying text"
2286 // Any modifying command establishes a new context.
2288 if (dot < context_start || dot > context_end) {
2289 if (strchr(modifying_cmds, cmd) != NULL) {
2290 // we are trying to modify text[]- make this the current context
2291 mark[27] = mark[26]; // move cur to prev
2292 mark[26] = dot; // move local to cur
2293 context_start = prev_line(prev_line(dot));
2294 context_end = next_line(next_line(dot));
2295 //loiter= start_loiter= now;
2300 static char *swap_context(char *p) // goto new context for '' command make this the current context
2302 char *tmp;
2304 // the current context is in mark[26]
2305 // the previous context is in mark[27]
2306 // only swap context if other context is valid
2307 if (text <= mark[27] && mark[27] <= end - 1) {
2308 tmp = mark[27];
2309 mark[27] = mark[26];
2310 mark[26] = tmp;
2311 p = mark[26]; // where we are going- previous context
2312 context_start = prev_line(prev_line(prev_line(p)));
2313 context_end = next_line(next_line(next_line(p)));
2315 return p;
2317 #endif /* FEATURE_VI_YANKMARK */
2319 //----- Set terminal attributes --------------------------------
2320 static void rawmode(void)
2322 tcgetattr(0, &term_orig);
2323 term_vi = term_orig;
2324 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2325 term_vi.c_iflag &= (~IXON & ~ICRNL);
2326 term_vi.c_oflag &= (~ONLCR);
2327 term_vi.c_cc[VMIN] = 1;
2328 term_vi.c_cc[VTIME] = 0;
2329 erase_char = term_vi.c_cc[VERASE];
2330 tcsetattr_stdin_TCSANOW(&term_vi);
2333 static void cookmode(void)
2335 fflush_all();
2336 tcsetattr_stdin_TCSANOW(&term_orig);
2339 #if ENABLE_FEATURE_VI_USE_SIGNALS
2340 //----- Come here when we get a window resize signal ---------
2341 static void winch_sig(int sig UNUSED_PARAM)
2343 int save_errno = errno;
2344 // FIXME: do it in main loop!!!
2345 signal(SIGWINCH, winch_sig);
2346 query_screen_dimensions();
2347 new_screen(rows, columns); // get memory for virtual screen
2348 redraw(TRUE); // re-draw the screen
2349 errno = save_errno;
2352 //----- Come here when we get a continue signal -------------------
2353 static void cont_sig(int sig UNUSED_PARAM)
2355 int save_errno = errno;
2356 rawmode(); // terminal to "raw"
2357 last_status_cksum = 0; // force status update
2358 redraw(TRUE); // re-draw the screen
2360 signal(SIGTSTP, suspend_sig);
2361 signal(SIGCONT, SIG_DFL);
2362 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2363 errno = save_errno;
2366 //----- Come here when we get a Suspend signal -------------------
2367 static void suspend_sig(int sig UNUSED_PARAM)
2369 int save_errno = errno;
2370 go_bottom_and_clear_to_eol();
2371 cookmode(); // terminal to "cooked"
2373 signal(SIGCONT, cont_sig);
2374 signal(SIGTSTP, SIG_DFL);
2375 kill(my_pid, SIGTSTP);
2376 errno = save_errno;
2379 //----- Come here when we get a signal ---------------------------
2380 static void catch_sig(int sig)
2382 signal(SIGINT, catch_sig);
2383 siglongjmp(restart, sig);
2385 #endif /* FEATURE_VI_USE_SIGNALS */
2387 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2389 struct pollfd pfd[1];
2391 pfd[0].fd = STDIN_FILENO;
2392 pfd[0].events = POLLIN;
2393 return safe_poll(pfd, 1, hund*10) > 0;
2396 //----- IO Routines --------------------------------------------
2397 static int readit(void) // read (maybe cursor) key from stdin
2399 int c;
2401 fflush_all();
2402 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2403 if (c == -1) { // EOF/error
2404 go_bottom_and_clear_to_eol();
2405 cookmode(); // terminal to "cooked"
2406 bb_error_msg_and_die("can't read user input");
2408 return c;
2411 //----- IO Routines --------------------------------------------
2412 static int get_one_char(void)
2414 int c;
2416 #if ENABLE_FEATURE_VI_DOT_CMD
2417 if (!adding2q) {
2418 // we are not adding to the q.
2419 // but, we may be reading from a q
2420 if (ioq == 0) {
2421 // there is no current q, read from STDIN
2422 c = readit(); // get the users input
2423 } else {
2424 // there is a queue to get chars from first
2425 // careful with correct sign expansion!
2426 c = (unsigned char)*ioq++;
2427 if (c == '\0') {
2428 // the end of the q, read from STDIN
2429 free(ioq_start);
2430 ioq_start = ioq = 0;
2431 c = readit(); // get the users input
2434 } else {
2435 // adding STDIN chars to q
2436 c = readit(); // get the users input
2437 if (lmc_len >= MAX_INPUT_LEN - 1) {
2438 status_line_bold("last_modifying_cmd overrun");
2439 } else {
2440 // add new char to q
2441 last_modifying_cmd[lmc_len++] = c;
2444 #else
2445 c = readit(); // get the users input
2446 #endif /* FEATURE_VI_DOT_CMD */
2447 return c;
2450 // Get input line (uses "status line" area)
2451 static char *get_input_line(const char *prompt)
2453 // char [MAX_INPUT_LEN]
2454 #define buf get_input_line__buf
2456 int c;
2457 int i;
2459 strcpy(buf, prompt);
2460 last_status_cksum = 0; // force status update
2461 go_bottom_and_clear_to_eol();
2462 write1(prompt); // write out the :, /, or ? prompt
2464 i = strlen(buf);
2465 while (i < MAX_INPUT_LEN) {
2466 c = get_one_char();
2467 if (c == '\n' || c == '\r' || c == 27)
2468 break; // this is end of input
2469 if (c == erase_char || c == 8 || c == 127) {
2470 // user wants to erase prev char
2471 buf[--i] = '\0';
2472 write1("\b \b"); // erase char on screen
2473 if (i <= 0) // user backs up before b-o-l, exit
2474 break;
2475 } else if (c > 0 && c < 256) { // exclude Unicode
2476 // (TODO: need to handle Unicode)
2477 buf[i] = c;
2478 buf[++i] = '\0';
2479 bb_putchar(c);
2482 refresh(FALSE);
2483 return buf;
2484 #undef buf
2487 static int file_size(const char *fn) // what is the byte size of "fn"
2489 struct stat st_buf;
2490 int cnt;
2492 cnt = -1;
2493 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2494 cnt = (int) st_buf.st_size;
2495 return cnt;
2498 // might reallocate text[]!
2499 static int file_insert(const char *fn, char *p, int update_ro_status)
2501 int cnt = -1;
2502 int fd, size;
2503 struct stat statbuf;
2505 /* Validate file */
2506 if (stat(fn, &statbuf) < 0) {
2507 status_line_bold("\"%s\" %s", fn, strerror(errno));
2508 goto fi0;
2510 if (!S_ISREG(statbuf.st_mode)) {
2511 // This is not a regular file
2512 status_line_bold("\"%s\" Not a regular file", fn);
2513 goto fi0;
2515 if (p < text || p > end) {
2516 status_line_bold("Trying to insert file outside of memory");
2517 goto fi0;
2520 // read file to buffer
2521 fd = open(fn, O_RDONLY);
2522 if (fd < 0) {
2523 status_line_bold("\"%s\" %s", fn, strerror(errno));
2524 goto fi0;
2526 size = statbuf.st_size;
2527 p += text_hole_make(p, size);
2528 cnt = safe_read(fd, p, size);
2529 if (cnt < 0) {
2530 status_line_bold("\"%s\" %s", fn, strerror(errno));
2531 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2532 } else if (cnt < size) {
2533 // There was a partial read, shrink unused space text[]
2534 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2535 status_line_bold("can't read all of file \"%s\"", fn);
2537 if (cnt >= size)
2538 file_modified++;
2539 close(fd);
2540 fi0:
2541 #if ENABLE_FEATURE_VI_READONLY
2542 if (update_ro_status
2543 && ((access(fn, W_OK) < 0) ||
2544 /* root will always have access()
2545 * so we check fileperms too */
2546 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2549 SET_READONLY_FILE(readonly_mode);
2551 #endif
2552 return cnt;
2555 static int file_write(char *fn, char *first, char *last)
2557 int fd, cnt, charcnt;
2559 if (fn == 0) {
2560 status_line_bold("No current filename");
2561 return -2;
2563 /* By popular request we do not open file with O_TRUNC,
2564 * but instead ftruncate() it _after_ successful write.
2565 * Might reduce amount of data lost on power fail etc.
2567 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2568 if (fd < 0)
2569 return -1;
2570 cnt = last - first + 1;
2571 charcnt = full_write(fd, first, cnt);
2572 ftruncate(fd, charcnt);
2573 if (charcnt == cnt) {
2574 // good write
2575 //file_modified = FALSE;
2576 } else {
2577 charcnt = 0;
2579 close(fd);
2580 return charcnt;
2583 //----- Terminal Drawing ---------------------------------------
2584 // The terminal is made up of 'rows' line of 'columns' columns.
2585 // classically this would be 24 x 80.
2586 // screen coordinates
2587 // 0,0 ... 0,79
2588 // 1,0 ... 1,79
2589 // . ... .
2590 // . ... .
2591 // 22,0 ... 22,79
2592 // 23,0 ... 23,79 <- status line
2594 //----- Move the cursor to row x col (count from 0, not 1) -------
2595 static void place_cursor(int row, int col, int optimize)
2597 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2598 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2599 enum {
2600 SZ_UP = sizeof(CMup),
2601 SZ_DN = sizeof(CMdown),
2602 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2604 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2605 #endif
2606 char *cm;
2608 if (row < 0) row = 0;
2609 if (row >= rows) row = rows - 1;
2610 if (col < 0) col = 0;
2611 if (col >= columns) col = columns - 1;
2613 //----- 1. Try the standard terminal ESC sequence
2614 sprintf(cm1, CMrc, row + 1, col + 1);
2615 cm = cm1;
2617 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2618 if (optimize && col < 16) {
2619 char *screenp;
2620 int Rrow = last_row;
2621 int diff = Rrow - row;
2623 if (diff < -5 || diff > 5)
2624 goto skip;
2626 //----- find the minimum # of chars to move cursor -------------
2627 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2628 cm2[0] = '\0';
2630 // move to the correct row
2631 while (row < Rrow) {
2632 // the cursor has to move up
2633 strcat(cm2, CMup);
2634 Rrow--;
2636 while (row > Rrow) {
2637 // the cursor has to move down
2638 strcat(cm2, CMdown);
2639 Rrow++;
2642 // now move to the correct column
2643 strcat(cm2, "\r"); // start at col 0
2644 // just send out orignal source char to get to correct place
2645 screenp = &screen[row * columns]; // start of screen line
2646 strncat(cm2, screenp, col);
2648 // pick the shortest cursor motion to send out
2649 if (strlen(cm2) < strlen(cm)) {
2650 cm = cm2;
2652 skip: ;
2654 last_row = row;
2655 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2656 write1(cm);
2659 //----- Erase from cursor to end of line -----------------------
2660 static void clear_to_eol(void)
2662 write1(Ceol); // Erase from cursor to end of line
2665 static void go_bottom_and_clear_to_eol(void)
2667 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2668 clear_to_eol(); // erase to end of line
2671 //----- Erase from cursor to end of screen -----------------------
2672 static void clear_to_eos(void)
2674 write1(Ceos); // Erase from cursor to end of screen
2677 //----- Start standout mode ------------------------------------
2678 static void standout_start(void) // send "start reverse video" sequence
2680 write1(SOs); // Start reverse video mode
2683 //----- End standout mode --------------------------------------
2684 static void standout_end(void) // send "end reverse video" sequence
2686 write1(SOn); // End reverse video mode
2689 //----- Flash the screen --------------------------------------
2690 static void flash(int h)
2692 standout_start(); // send "start reverse video" sequence
2693 redraw(TRUE);
2694 mysleep(h);
2695 standout_end(); // send "end reverse video" sequence
2696 redraw(TRUE);
2699 static void Indicate_Error(void)
2701 #if ENABLE_FEATURE_VI_CRASHME
2702 if (crashme > 0)
2703 return; // generate a random command
2704 #endif
2705 if (!err_method) {
2706 write1(bell); // send out a bell character
2707 } else {
2708 flash(10);
2712 //----- Screen[] Routines --------------------------------------
2713 //----- Erase the Screen[] memory ------------------------------
2714 static void screen_erase(void)
2716 memset(screen, ' ', screensize); // clear new screen
2719 static int bufsum(char *buf, int count)
2721 int sum = 0;
2722 char *e = buf + count;
2724 while (buf < e)
2725 sum += (unsigned char) *buf++;
2726 return sum;
2729 //----- Draw the status line at bottom of the screen -------------
2730 static void show_status_line(void)
2732 int cnt = 0, cksum = 0;
2734 // either we already have an error or status message, or we
2735 // create one.
2736 if (!have_status_msg) {
2737 cnt = format_edit_status();
2738 cksum = bufsum(status_buffer, cnt);
2740 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2741 last_status_cksum = cksum; // remember if we have seen this line
2742 go_bottom_and_clear_to_eol();
2743 write1(status_buffer);
2744 if (have_status_msg) {
2745 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2746 (columns - 1) ) {
2747 have_status_msg = 0;
2748 Hit_Return();
2750 have_status_msg = 0;
2752 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2754 fflush_all();
2757 //----- format the status buffer, the bottom line of screen ------
2758 // format status buffer, with STANDOUT mode
2759 static void status_line_bold(const char *format, ...)
2761 va_list args;
2763 va_start(args, format);
2764 strcpy(status_buffer, SOs); // Terminal standout mode on
2765 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2766 strcat(status_buffer, SOn); // Terminal standout mode off
2767 va_end(args);
2769 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2772 // format status buffer
2773 static void status_line(const char *format, ...)
2775 va_list args;
2777 va_start(args, format);
2778 vsprintf(status_buffer, format, args);
2779 va_end(args);
2781 have_status_msg = 1;
2784 // copy s to buf, convert unprintable
2785 static void print_literal(char *buf, const char *s)
2787 char *d;
2788 unsigned char c;
2790 buf[0] = '\0';
2791 if (!s[0])
2792 s = "(NULL)";
2794 d = buf;
2795 for (; *s; s++) {
2796 int c_is_no_print;
2798 c = *s;
2799 c_is_no_print = (c & 0x80) && !Isprint(c);
2800 if (c_is_no_print) {
2801 strcpy(d, SOn);
2802 d += sizeof(SOn)-1;
2803 c = '.';
2805 if (c < ' ' || c == 0x7f) {
2806 *d++ = '^';
2807 c |= '@'; /* 0x40 */
2808 if (c == 0x7f)
2809 c = '?';
2811 *d++ = c;
2812 *d = '\0';
2813 if (c_is_no_print) {
2814 strcpy(d, SOs);
2815 d += sizeof(SOs)-1;
2817 if (*s == '\n') {
2818 *d++ = '$';
2819 *d = '\0';
2821 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2822 break;
2826 static void not_implemented(const char *s)
2828 char buf[MAX_INPUT_LEN];
2830 print_literal(buf, s);
2831 status_line_bold("\'%s\' is not implemented", buf);
2834 // show file status on status line
2835 static int format_edit_status(void)
2837 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2839 #define tot format_edit_status__tot
2841 int cur, percent, ret, trunc_at;
2843 // file_modified is now a counter rather than a flag. this
2844 // helps reduce the amount of line counting we need to do.
2845 // (this will cause a mis-reporting of modified status
2846 // once every MAXINT editing operations.)
2848 // it would be nice to do a similar optimization here -- if
2849 // we haven't done a motion that could have changed which line
2850 // we're on, then we shouldn't have to do this count_lines()
2851 cur = count_lines(text, dot);
2853 // reduce counting -- the total lines can't have
2854 // changed if we haven't done any edits.
2855 if (file_modified != last_file_modified) {
2856 tot = cur + count_lines(dot, end - 1) - 1;
2857 last_file_modified = file_modified;
2860 // current line percent
2861 // ------------- ~~ ----------
2862 // total lines 100
2863 if (tot > 0) {
2864 percent = (100 * cur) / tot;
2865 } else {
2866 cur = tot = 0;
2867 percent = 100;
2870 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2871 columns : STATUS_BUFFER_LEN-1;
2873 ret = snprintf(status_buffer, trunc_at+1,
2874 #if ENABLE_FEATURE_VI_READONLY
2875 "%c %s%s%s %d/%d %d%%",
2876 #else
2877 "%c %s%s %d/%d %d%%",
2878 #endif
2879 cmd_mode_indicator[cmd_mode & 3],
2880 (current_filename != NULL ? current_filename : "No file"),
2881 #if ENABLE_FEATURE_VI_READONLY
2882 (readonly_mode ? " [Readonly]" : ""),
2883 #endif
2884 (file_modified ? " [Modified]" : ""),
2885 cur, tot, percent);
2887 if (ret >= 0 && ret < trunc_at)
2888 return ret; /* it all fit */
2890 return trunc_at; /* had to truncate */
2891 #undef tot
2894 //----- Force refresh of all Lines -----------------------------
2895 static void redraw(int full_screen)
2897 place_cursor(0, 0, FALSE); // put cursor in correct place
2898 clear_to_eos(); // tell terminal to erase display
2899 screen_erase(); // erase the internal screen buffer
2900 last_status_cksum = 0; // force status update
2901 refresh(full_screen); // this will redraw the entire display
2902 show_status_line();
2905 //----- Format a text[] line into a buffer ---------------------
2906 static char* format_line(char *src /*, int li*/)
2908 unsigned char c;
2909 int co;
2910 int ofs = offset;
2911 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2913 c = '~'; // char in col 0 in non-existent lines is '~'
2914 co = 0;
2915 while (co < columns + tabstop) {
2916 // have we gone past the end?
2917 if (src < end) {
2918 c = *src++;
2919 if (c == '\n')
2920 break;
2921 if ((c & 0x80) && !Isprint(c)) {
2922 c = '.';
2924 if (c < ' ' || c == 0x7f) {
2925 if (c == '\t') {
2926 c = ' ';
2927 // co % 8 != 7
2928 while ((co % tabstop) != (tabstop - 1)) {
2929 dest[co++] = c;
2931 } else {
2932 dest[co++] = '^';
2933 if (c == 0x7f)
2934 c = '?';
2935 else
2936 c += '@'; // Ctrl-X -> 'X'
2940 dest[co++] = c;
2941 // discard scrolled-off-to-the-left portion,
2942 // in tabstop-sized pieces
2943 if (ofs >= tabstop && co >= tabstop) {
2944 memmove(dest, dest + tabstop, co);
2945 co -= tabstop;
2946 ofs -= tabstop;
2948 if (src >= end)
2949 break;
2951 // check "short line, gigantic offset" case
2952 if (co < ofs)
2953 ofs = co;
2954 // discard last scrolled off part
2955 co -= ofs;
2956 dest += ofs;
2957 // fill the rest with spaces
2958 if (co < columns)
2959 memset(&dest[co], ' ', columns - co);
2960 return dest;
2963 //----- Refresh the changed screen lines -----------------------
2964 // Copy the source line from text[] into the buffer and note
2965 // if the current screenline is different from the new buffer.
2966 // If they differ then that line needs redrawing on the terminal.
2968 static void refresh(int full_screen)
2970 #define old_offset refresh__old_offset
2972 int li, changed;
2973 char *tp, *sp; // pointer into text[] and screen[]
2975 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2976 unsigned c = columns, r = rows;
2977 query_screen_dimensions();
2978 full_screen |= (c - columns) | (r - rows);
2980 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2981 tp = screenbegin; // index into text[] of top line
2983 // compare text[] to screen[] and mark screen[] lines that need updating
2984 for (li = 0; li < rows - 1; li++) {
2985 int cs, ce; // column start & end
2986 char *out_buf;
2987 // format current text line
2988 out_buf = format_line(tp /*, li*/);
2990 // skip to the end of the current text[] line
2991 if (tp < end) {
2992 char *t = memchr(tp, '\n', end - tp);
2993 if (!t) t = end - 1;
2994 tp = t + 1;
2997 // see if there are any changes between vitual screen and out_buf
2998 changed = FALSE; // assume no change
2999 cs = 0;
3000 ce = columns - 1;
3001 sp = &screen[li * columns]; // start of screen line
3002 if (full_screen) {
3003 // force re-draw of every single column from 0 - columns-1
3004 goto re0;
3006 // compare newly formatted buffer with virtual screen
3007 // look forward for first difference between buf and screen
3008 for (; cs <= ce; cs++) {
3009 if (out_buf[cs] != sp[cs]) {
3010 changed = TRUE; // mark for redraw
3011 break;
3015 // look backward for last difference between out_buf and screen
3016 for (; ce >= cs; ce--) {
3017 if (out_buf[ce] != sp[ce]) {
3018 changed = TRUE; // mark for redraw
3019 break;
3022 // now, cs is index of first diff, and ce is index of last diff
3024 // if horz offset has changed, force a redraw
3025 if (offset != old_offset) {
3026 re0:
3027 changed = TRUE;
3030 // make a sanity check of columns indexes
3031 if (cs < 0) cs = 0;
3032 if (ce > columns - 1) ce = columns - 1;
3033 if (cs > ce) { cs = 0; ce = columns - 1; }
3034 // is there a change between vitual screen and out_buf
3035 if (changed) {
3036 // copy changed part of buffer to virtual screen
3037 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3039 // move cursor to column of first change
3040 //if (offset != old_offset) {
3041 // // place_cursor is still too stupid
3042 // // to handle offsets correctly
3043 // place_cursor(li, cs, FALSE);
3044 //} else {
3045 place_cursor(li, cs, TRUE);
3048 // write line out to terminal
3049 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3053 place_cursor(crow, ccol, TRUE);
3055 old_offset = offset;
3056 #undef old_offset
3059 //---------------------------------------------------------------------
3060 //----- the Ascii Chart -----------------------------------------------
3062 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3063 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3064 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3065 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3066 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3067 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3068 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3069 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3070 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3071 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3072 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3073 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3074 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3075 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3076 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3077 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3078 //---------------------------------------------------------------------
3080 //----- Execute a Vi Command -----------------------------------
3081 static void do_cmd(int c)
3083 char *p, *q, *save_dot;
3084 char buf[12];
3085 int dir;
3086 int cnt, i, j;
3087 int c1;
3089 // c1 = c; // quiet the compiler
3090 // cnt = yf = 0; // quiet the compiler
3091 // p = q = save_dot = buf; // quiet the compiler
3092 memset(buf, '\0', sizeof(buf));
3094 show_status_line();
3096 /* if this is a cursor key, skip these checks */
3097 switch (c) {
3098 case KEYCODE_UP:
3099 case KEYCODE_DOWN:
3100 case KEYCODE_LEFT:
3101 case KEYCODE_RIGHT:
3102 case KEYCODE_HOME:
3103 case KEYCODE_END:
3104 case KEYCODE_PAGEUP:
3105 case KEYCODE_PAGEDOWN:
3106 case KEYCODE_DELETE:
3107 goto key_cmd_mode;
3110 if (cmd_mode == 2) {
3111 // flip-flop Insert/Replace mode
3112 if (c == KEYCODE_INSERT)
3113 goto dc_i;
3114 // we are 'R'eplacing the current *dot with new char
3115 if (*dot == '\n') {
3116 // don't Replace past E-o-l
3117 cmd_mode = 1; // convert to insert
3118 } else {
3119 if (1 <= c || Isprint(c)) {
3120 if (c != 27)
3121 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3122 dot = char_insert(dot, c); // insert new char
3124 goto dc1;
3127 if (cmd_mode == 1) {
3128 // hitting "Insert" twice means "R" replace mode
3129 if (c == KEYCODE_INSERT) goto dc5;
3130 // insert the char c at "dot"
3131 if (1 <= c || Isprint(c)) {
3132 dot = char_insert(dot, c);
3134 goto dc1;
3137 key_cmd_mode:
3138 switch (c) {
3139 //case 0x01: // soh
3140 //case 0x09: // ht
3141 //case 0x0b: // vt
3142 //case 0x0e: // so
3143 //case 0x0f: // si
3144 //case 0x10: // dle
3145 //case 0x11: // dc1
3146 //case 0x13: // dc3
3147 #if ENABLE_FEATURE_VI_CRASHME
3148 case 0x14: // dc4 ctrl-T
3149 crashme = (crashme == 0) ? 1 : 0;
3150 break;
3151 #endif
3152 //case 0x16: // syn
3153 //case 0x17: // etb
3154 //case 0x18: // can
3155 //case 0x1c: // fs
3156 //case 0x1d: // gs
3157 //case 0x1e: // rs
3158 //case 0x1f: // us
3159 //case '!': // !-
3160 //case '#': // #-
3161 //case '&': // &-
3162 //case '(': // (-
3163 //case ')': // )-
3164 //case '*': // *-
3165 //case '=': // =-
3166 //case '@': // @-
3167 //case 'F': // F-
3168 //case 'K': // K-
3169 //case 'Q': // Q-
3170 //case 'S': // S-
3171 //case 'T': // T-
3172 //case 'V': // V-
3173 //case '[': // [-
3174 //case '\\': // \-
3175 //case ']': // ]-
3176 //case '_': // _-
3177 //case '`': // `-
3178 //case 'u': // u- FIXME- there is no undo
3179 //case 'v': // v-
3180 default: // unrecognized command
3181 buf[0] = c;
3182 buf[1] = '\0';
3183 not_implemented(buf);
3184 end_cmd_q(); // stop adding to q
3185 case 0x00: // nul- ignore
3186 break;
3187 case 2: // ctrl-B scroll up full screen
3188 case KEYCODE_PAGEUP: // Cursor Key Page Up
3189 dot_scroll(rows - 2, -1);
3190 break;
3191 case 4: // ctrl-D scroll down half screen
3192 dot_scroll((rows - 2) / 2, 1);
3193 break;
3194 case 5: // ctrl-E scroll down one line
3195 dot_scroll(1, 1);
3196 break;
3197 case 6: // ctrl-F scroll down full screen
3198 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3199 dot_scroll(rows - 2, 1);
3200 break;
3201 case 7: // ctrl-G show current status
3202 last_status_cksum = 0; // force status update
3203 break;
3204 case 'h': // h- move left
3205 case KEYCODE_LEFT: // cursor key Left
3206 case 8: // ctrl-H- move left (This may be ERASE char)
3207 case 0x7f: // DEL- move left (This may be ERASE char)
3208 do {
3209 dot_left();
3210 } while (--cmdcnt > 0);
3211 break;
3212 case 10: // Newline ^J
3213 case 'j': // j- goto next line, same col
3214 case KEYCODE_DOWN: // cursor key Down
3215 do {
3216 dot_next(); // go to next B-o-l
3217 // try stay in same col
3218 dot = move_to_col(dot, ccol + offset);
3219 } while (--cmdcnt > 0);
3220 break;
3221 case 12: // ctrl-L force redraw whole screen
3222 case 18: // ctrl-R force redraw
3223 place_cursor(0, 0, FALSE); // put cursor in correct place
3224 clear_to_eos(); // tel terminal to erase display
3225 mysleep(10);
3226 screen_erase(); // erase the internal screen buffer
3227 last_status_cksum = 0; // force status update
3228 refresh(TRUE); // this will redraw the entire display
3229 break;
3230 case 13: // Carriage Return ^M
3231 case '+': // +- goto next line
3232 do {
3233 dot_next();
3234 dot_skip_over_ws();
3235 } while (--cmdcnt > 0);
3236 break;
3237 case 21: // ctrl-U scroll up half screen
3238 dot_scroll((rows - 2) / 2, -1);
3239 break;
3240 case 25: // ctrl-Y scroll up one line
3241 dot_scroll(1, -1);
3242 break;
3243 case 27: // esc
3244 if (cmd_mode == 0)
3245 indicate_error(c);
3246 cmd_mode = 0; // stop insrting
3247 end_cmd_q();
3248 last_status_cksum = 0; // force status update
3249 break;
3250 case ' ': // move right
3251 case 'l': // move right
3252 case KEYCODE_RIGHT: // Cursor Key Right
3253 do {
3254 dot_right();
3255 } while (--cmdcnt > 0);
3256 break;
3257 #if ENABLE_FEATURE_VI_YANKMARK
3258 case '"': // "- name a register to use for Delete/Yank
3259 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3260 if ((unsigned)c1 <= 25) { // a-z?
3261 YDreg = c1;
3262 } else {
3263 indicate_error(c);
3265 break;
3266 case '\'': // '- goto a specific mark
3267 c1 = (get_one_char() | 0x20) - 'a';
3268 if ((unsigned)c1 <= 25) { // a-z?
3269 // get the b-o-l
3270 q = mark[c1];
3271 if (text <= q && q < end) {
3272 dot = q;
3273 dot_begin(); // go to B-o-l
3274 dot_skip_over_ws();
3276 } else if (c1 == '\'') { // goto previous context
3277 dot = swap_context(dot); // swap current and previous context
3278 dot_begin(); // go to B-o-l
3279 dot_skip_over_ws();
3280 } else {
3281 indicate_error(c);
3283 break;
3284 case 'm': // m- Mark a line
3285 // this is really stupid. If there are any inserts or deletes
3286 // between text[0] and dot then this mark will not point to the
3287 // correct location! It could be off by many lines!
3288 // Well..., at least its quick and dirty.
3289 c1 = (get_one_char() | 0x20) - 'a';
3290 if ((unsigned)c1 <= 25) { // a-z?
3291 // remember the line
3292 mark[c1] = dot;
3293 } else {
3294 indicate_error(c);
3296 break;
3297 case 'P': // P- Put register before
3298 case 'p': // p- put register after
3299 p = reg[YDreg];
3300 if (p == NULL) {
3301 status_line_bold("Nothing in register %c", what_reg());
3302 break;
3304 // are we putting whole lines or strings
3305 if (strchr(p, '\n') != NULL) {
3306 if (c == 'P') {
3307 dot_begin(); // putting lines- Put above
3309 if (c == 'p') {
3310 // are we putting after very last line?
3311 if (end_line(dot) == (end - 1)) {
3312 dot = end; // force dot to end of text[]
3313 } else {
3314 dot_next(); // next line, then put before
3317 } else {
3318 if (c == 'p')
3319 dot_right(); // move to right, can move to NL
3321 string_insert(dot, p); // insert the string
3322 end_cmd_q(); // stop adding to q
3323 break;
3324 case 'U': // U- Undo; replace current line with original version
3325 if (reg[Ureg] != NULL) {
3326 p = begin_line(dot);
3327 q = end_line(dot);
3328 p = text_hole_delete(p, q); // delete cur line
3329 p += string_insert(p, reg[Ureg]); // insert orig line
3330 dot = p;
3331 dot_skip_over_ws();
3333 break;
3334 #endif /* FEATURE_VI_YANKMARK */
3335 case '$': // $- goto end of line
3336 case KEYCODE_END: // Cursor Key End
3337 for (;;) {
3338 dot = end_line(dot);
3339 if (--cmdcnt <= 0)
3340 break;
3341 dot_next();
3343 break;
3344 case '%': // %- find matching char of pair () [] {}
3345 for (q = dot; q < end && *q != '\n'; q++) {
3346 if (strchr("()[]{}", *q) != NULL) {
3347 // we found half of a pair
3348 p = find_pair(q, *q);
3349 if (p == NULL) {
3350 indicate_error(c);
3351 } else {
3352 dot = p;
3354 break;
3357 if (*q == '\n')
3358 indicate_error(c);
3359 break;
3360 case 'f': // f- forward to a user specified char
3361 last_forward_char = get_one_char(); // get the search char
3363 // dont separate these two commands. 'f' depends on ';'
3365 //**** fall through to ... ';'
3366 case ';': // ;- look at rest of line for last forward char
3367 do {
3368 if (last_forward_char == 0)
3369 break;
3370 q = dot + 1;
3371 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3372 q++;
3374 if (*q == last_forward_char)
3375 dot = q;
3376 } while (--cmdcnt > 0);
3377 break;
3378 case ',': // repeat latest 'f' in opposite direction
3379 if (last_forward_char == 0)
3380 break;
3381 do {
3382 q = dot - 1;
3383 while (q >= text && *q != '\n' && *q != last_forward_char) {
3384 q--;
3386 if (q >= text && *q == last_forward_char)
3387 dot = q;
3388 } while (--cmdcnt > 0);
3389 break;
3391 case '-': // -- goto prev line
3392 do {
3393 dot_prev();
3394 dot_skip_over_ws();
3395 } while (--cmdcnt > 0);
3396 break;
3397 #if ENABLE_FEATURE_VI_DOT_CMD
3398 case '.': // .- repeat the last modifying command
3399 // Stuff the last_modifying_cmd back into stdin
3400 // and let it be re-executed.
3401 if (lmc_len > 0) {
3402 last_modifying_cmd[lmc_len] = 0;
3403 ioq = ioq_start = xstrdup(last_modifying_cmd);
3405 break;
3406 #endif
3407 #if ENABLE_FEATURE_VI_SEARCH
3408 case '?': // /- search for a pattern
3409 case '/': // /- search for a pattern
3410 buf[0] = c;
3411 buf[1] = '\0';
3412 q = get_input_line(buf); // get input line- use "status line"
3413 if (q[0] && !q[1]) {
3414 if (last_search_pattern[0])
3415 last_search_pattern[0] = c;
3416 goto dc3; // if no pat re-use old pat
3418 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3419 // there is a new pat
3420 free(last_search_pattern);
3421 last_search_pattern = xstrdup(q);
3422 goto dc3; // now find the pattern
3424 // user changed mind and erased the "/"- do nothing
3425 break;
3426 case 'N': // N- backward search for last pattern
3427 dir = BACK; // assume BACKWARD search
3428 p = dot - 1;
3429 if (last_search_pattern[0] == '?') {
3430 dir = FORWARD;
3431 p = dot + 1;
3433 goto dc4; // now search for pattern
3434 break;
3435 case 'n': // n- repeat search for last pattern
3436 // search rest of text[] starting at next char
3437 // if search fails return orignal "p" not the "p+1" address
3438 do {
3439 const char *msg;
3440 dc3:
3441 dir = FORWARD; // assume FORWARD search
3442 p = dot + 1;
3443 if (last_search_pattern[0] == '?') {
3444 dir = BACK;
3445 p = dot - 1;
3447 dc4:
3448 q = char_search(p, last_search_pattern + 1, dir, FULL);
3449 if (q != NULL) {
3450 dot = q; // good search, update "dot"
3451 msg = NULL;
3452 goto dc2;
3454 // no pattern found between "dot" and "end"- continue at top
3455 p = text;
3456 if (dir == BACK) {
3457 p = end - 1;
3459 q = char_search(p, last_search_pattern + 1, dir, FULL);
3460 if (q != NULL) { // found something
3461 dot = q; // found new pattern- goto it
3462 msg = "search hit BOTTOM, continuing at TOP";
3463 if (dir == BACK) {
3464 msg = "search hit TOP, continuing at BOTTOM";
3466 } else {
3467 msg = "Pattern not found";
3469 dc2:
3470 if (msg)
3471 status_line_bold("%s", msg);
3472 } while (--cmdcnt > 0);
3473 break;
3474 case '{': // {- move backward paragraph
3475 q = char_search(dot, "\n\n", BACK, FULL);
3476 if (q != NULL) { // found blank line
3477 dot = next_line(q); // move to next blank line
3479 break;
3480 case '}': // }- move forward paragraph
3481 q = char_search(dot, "\n\n", FORWARD, FULL);
3482 if (q != NULL) { // found blank line
3483 dot = next_line(q); // move to next blank line
3485 break;
3486 #endif /* FEATURE_VI_SEARCH */
3487 case '0': // 0- goto begining of line
3488 case '1': // 1-
3489 case '2': // 2-
3490 case '3': // 3-
3491 case '4': // 4-
3492 case '5': // 5-
3493 case '6': // 6-
3494 case '7': // 7-
3495 case '8': // 8-
3496 case '9': // 9-
3497 if (c == '0' && cmdcnt < 1) {
3498 dot_begin(); // this was a standalone zero
3499 } else {
3500 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3502 break;
3503 case ':': // :- the colon mode commands
3504 p = get_input_line(":"); // get input line- use "status line"
3505 #if ENABLE_FEATURE_VI_COLON
3506 colon(p); // execute the command
3507 #else
3508 if (*p == ':')
3509 p++; // move past the ':'
3510 cnt = strlen(p);
3511 if (cnt <= 0)
3512 break;
3513 if (strncmp(p, "quit", cnt) == 0
3514 || strncmp(p, "q!", cnt) == 0 // delete lines
3516 if (file_modified && p[1] != '!') {
3517 status_line_bold("No write since last change (:%s! overrides)", p);
3518 } else {
3519 editing = 0;
3521 } else if (strncmp(p, "write", cnt) == 0
3522 || strncmp(p, "wq", cnt) == 0
3523 || strncmp(p, "wn", cnt) == 0
3524 || (p[0] == 'x' && !p[1])
3526 cnt = file_write(current_filename, text, end - 1);
3527 if (cnt < 0) {
3528 if (cnt == -1)
3529 status_line_bold("Write error: %s", strerror(errno));
3530 } else {
3531 file_modified = 0;
3532 last_file_modified = -1;
3533 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3534 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3535 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3537 editing = 0;
3540 } else if (strncmp(p, "file", cnt) == 0) {
3541 last_status_cksum = 0; // force status update
3542 } else if (sscanf(p, "%d", &j) > 0) {
3543 dot = find_line(j); // go to line # j
3544 dot_skip_over_ws();
3545 } else { // unrecognized cmd
3546 not_implemented(p);
3548 #endif /* !FEATURE_VI_COLON */
3549 break;
3550 case '<': // <- Left shift something
3551 case '>': // >- Right shift something
3552 cnt = count_lines(text, dot); // remember what line we are on
3553 c1 = get_one_char(); // get the type of thing to delete
3554 find_range(&p, &q, c1);
3555 yank_delete(p, q, 1, YANKONLY); // save copy before change
3556 p = begin_line(p);
3557 q = end_line(q);
3558 i = count_lines(p, q); // # of lines we are shifting
3559 for ( ; i > 0; i--, p = next_line(p)) {
3560 if (c == '<') {
3561 // shift left- remove tab or 8 spaces
3562 if (*p == '\t') {
3563 // shrink buffer 1 char
3564 text_hole_delete(p, p);
3565 } else if (*p == ' ') {
3566 // we should be calculating columns, not just SPACE
3567 for (j = 0; *p == ' ' && j < tabstop; j++) {
3568 text_hole_delete(p, p);
3571 } else if (c == '>') {
3572 // shift right -- add tab or 8 spaces
3573 char_insert(p, '\t');
3576 dot = find_line(cnt); // what line were we on
3577 dot_skip_over_ws();
3578 end_cmd_q(); // stop adding to q
3579 break;
3580 case 'A': // A- append at e-o-l
3581 dot_end(); // go to e-o-l
3582 //**** fall through to ... 'a'
3583 case 'a': // a- append after current char
3584 if (*dot != '\n')
3585 dot++;
3586 goto dc_i;
3587 break;
3588 case 'B': // B- back a blank-delimited Word
3589 case 'E': // E- end of a blank-delimited word
3590 case 'W': // W- forward a blank-delimited word
3591 dir = FORWARD;
3592 if (c == 'B')
3593 dir = BACK;
3594 do {
3595 if (c == 'W' || isspace(dot[dir])) {
3596 dot = skip_thing(dot, 1, dir, S_TO_WS);
3597 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3599 if (c != 'W')
3600 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3601 } while (--cmdcnt > 0);
3602 break;
3603 case 'C': // C- Change to e-o-l
3604 case 'D': // D- delete to e-o-l
3605 save_dot = dot;
3606 dot = dollar_line(dot); // move to before NL
3607 // copy text into a register and delete
3608 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3609 if (c == 'C')
3610 goto dc_i; // start inserting
3611 #if ENABLE_FEATURE_VI_DOT_CMD
3612 if (c == 'D')
3613 end_cmd_q(); // stop adding to q
3614 #endif
3615 break;
3616 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3617 c1 = get_one_char();
3618 if (c1 != 'g') {
3619 buf[0] = 'g';
3620 buf[1] = c1; // TODO: if Unicode?
3621 buf[2] = '\0';
3622 not_implemented(buf);
3623 break;
3625 if (cmdcnt == 0)
3626 cmdcnt = 1;
3627 /* fall through */
3628 case 'G': // G- goto to a line number (default= E-O-F)
3629 dot = end - 1; // assume E-O-F
3630 if (cmdcnt > 0) {
3631 dot = find_line(cmdcnt); // what line is #cmdcnt
3633 dot_skip_over_ws();
3634 break;
3635 case 'H': // H- goto top line on screen
3636 dot = screenbegin;
3637 if (cmdcnt > (rows - 1)) {
3638 cmdcnt = (rows - 1);
3640 if (--cmdcnt > 0) {
3641 do_cmd('+');
3643 dot_skip_over_ws();
3644 break;
3645 case 'I': // I- insert before first non-blank
3646 dot_begin(); // 0
3647 dot_skip_over_ws();
3648 //**** fall through to ... 'i'
3649 case 'i': // i- insert before current char
3650 case KEYCODE_INSERT: // Cursor Key Insert
3651 dc_i:
3652 cmd_mode = 1; // start inserting
3653 break;
3654 case 'J': // J- join current and next lines together
3655 do {
3656 dot_end(); // move to NL
3657 if (dot < end - 1) { // make sure not last char in text[]
3658 *dot++ = ' '; // replace NL with space
3659 file_modified++;
3660 while (isblank(*dot)) { // delete leading WS
3661 dot_delete();
3664 } while (--cmdcnt > 0);
3665 end_cmd_q(); // stop adding to q
3666 break;
3667 case 'L': // L- goto bottom line on screen
3668 dot = end_screen();
3669 if (cmdcnt > (rows - 1)) {
3670 cmdcnt = (rows - 1);
3672 if (--cmdcnt > 0) {
3673 do_cmd('-');
3675 dot_begin();
3676 dot_skip_over_ws();
3677 break;
3678 case 'M': // M- goto middle line on screen
3679 dot = screenbegin;
3680 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3681 dot = next_line(dot);
3682 break;
3683 case 'O': // O- open a empty line above
3684 // 0i\n ESC -i
3685 p = begin_line(dot);
3686 if (p[-1] == '\n') {
3687 dot_prev();
3688 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3689 dot_end();
3690 dot = char_insert(dot, '\n');
3691 } else {
3692 dot_begin(); // 0
3693 dot = char_insert(dot, '\n'); // i\n ESC
3694 dot_prev(); // -
3696 goto dc_i;
3697 break;
3698 case 'R': // R- continuous Replace char
3699 dc5:
3700 cmd_mode = 2;
3701 break;
3702 case KEYCODE_DELETE:
3703 c = 'x';
3704 // fall through
3705 case 'X': // X- delete char before dot
3706 case 'x': // x- delete the current char
3707 case 's': // s- substitute the current char
3708 dir = 0;
3709 if (c == 'X')
3710 dir = -1;
3711 do {
3712 if (dot[dir] != '\n') {
3713 if (c == 'X')
3714 dot--; // delete prev char
3715 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3717 } while (--cmdcnt > 0);
3718 end_cmd_q(); // stop adding to q
3719 if (c == 's')
3720 goto dc_i; // start inserting
3721 break;
3722 case 'Z': // Z- if modified, {write}; exit
3723 // ZZ means to save file (if necessary), then exit
3724 c1 = get_one_char();
3725 if (c1 != 'Z') {
3726 indicate_error(c);
3727 break;
3729 if (file_modified) {
3730 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3731 status_line_bold("\"%s\" File is read only", current_filename);
3732 break;
3734 cnt = file_write(current_filename, text, end - 1);
3735 if (cnt < 0) {
3736 if (cnt == -1)
3737 status_line_bold("Write error: %s", strerror(errno));
3738 } else if (cnt == (end - 1 - text + 1)) {
3739 editing = 0;
3741 } else {
3742 editing = 0;
3744 break;
3745 case '^': // ^- move to first non-blank on line
3746 dot_begin();
3747 dot_skip_over_ws();
3748 break;
3749 case 'b': // b- back a word
3750 case 'e': // e- end of word
3751 dir = FORWARD;
3752 if (c == 'b')
3753 dir = BACK;
3754 do {
3755 if ((dot + dir) < text || (dot + dir) > end - 1)
3756 break;
3757 dot += dir;
3758 if (isspace(*dot)) {
3759 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3761 if (isalnum(*dot) || *dot == '_') {
3762 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3763 } else if (ispunct(*dot)) {
3764 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3766 } while (--cmdcnt > 0);
3767 break;
3768 case 'c': // c- change something
3769 case 'd': // d- delete something
3770 #if ENABLE_FEATURE_VI_YANKMARK
3771 case 'y': // y- yank something
3772 case 'Y': // Y- Yank a line
3773 #endif
3775 int yf, ml, whole = 0;
3776 yf = YANKDEL; // assume either "c" or "d"
3777 #if ENABLE_FEATURE_VI_YANKMARK
3778 if (c == 'y' || c == 'Y')
3779 yf = YANKONLY;
3780 #endif
3781 c1 = 'y';
3782 if (c != 'Y')
3783 c1 = get_one_char(); // get the type of thing to delete
3784 // determine range, and whether it spans lines
3785 ml = find_range(&p, &q, c1);
3786 if (c1 == 27) { // ESC- user changed mind and wants out
3787 c = c1 = 27; // Escape- do nothing
3788 } else if (strchr("wW", c1)) {
3789 if (c == 'c') {
3790 // don't include trailing WS as part of word
3791 while (isblank(*q)) {
3792 if (q <= text || q[-1] == '\n')
3793 break;
3794 q--;
3797 dot = yank_delete(p, q, ml, yf); // delete word
3798 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3799 // partial line copy text into a register and delete
3800 dot = yank_delete(p, q, ml, yf); // delete word
3801 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3802 // whole line copy text into a register and delete
3803 dot = yank_delete(p, q, ml, yf); // delete lines
3804 whole = 1;
3805 } else {
3806 // could not recognize object
3807 c = c1 = 27; // error-
3808 ml = 0;
3809 indicate_error(c);
3811 if (ml && whole) {
3812 if (c == 'c') {
3813 dot = char_insert(dot, '\n');
3814 // on the last line of file don't move to prev line
3815 if (whole && dot != (end-1)) {
3816 dot_prev();
3818 } else if (c == 'd') {
3819 dot_begin();
3820 dot_skip_over_ws();
3823 if (c1 != 27) {
3824 // if CHANGING, not deleting, start inserting after the delete
3825 if (c == 'c') {
3826 strcpy(buf, "Change");
3827 goto dc_i; // start inserting
3829 if (c == 'd') {
3830 strcpy(buf, "Delete");
3832 #if ENABLE_FEATURE_VI_YANKMARK
3833 if (c == 'y' || c == 'Y') {
3834 strcpy(buf, "Yank");
3836 p = reg[YDreg];
3837 q = p + strlen(p);
3838 for (cnt = 0; p <= q; p++) {
3839 if (*p == '\n')
3840 cnt++;
3842 status_line("%s %d lines (%d chars) using [%c]",
3843 buf, cnt, strlen(reg[YDreg]), what_reg());
3844 #endif
3845 end_cmd_q(); // stop adding to q
3847 break;
3849 case 'k': // k- goto prev line, same col
3850 case KEYCODE_UP: // cursor key Up
3851 do {
3852 dot_prev();
3853 dot = move_to_col(dot, ccol + offset); // try stay in same col
3854 } while (--cmdcnt > 0);
3855 break;
3856 case 'r': // r- replace the current char with user input
3857 c1 = get_one_char(); // get the replacement char
3858 if (*dot != '\n') {
3859 *dot = c1;
3860 file_modified++;
3862 end_cmd_q(); // stop adding to q
3863 break;
3864 case 't': // t- move to char prior to next x
3865 last_forward_char = get_one_char();
3866 do_cmd(';');
3867 if (*dot == last_forward_char)
3868 dot_left();
3869 last_forward_char = 0;
3870 break;
3871 case 'w': // w- forward a word
3872 do {
3873 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3874 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3875 } else if (ispunct(*dot)) { // we are on PUNCT
3876 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3878 if (dot < end - 1)
3879 dot++; // move over word
3880 if (isspace(*dot)) {
3881 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3883 } while (--cmdcnt > 0);
3884 break;
3885 case 'z': // z-
3886 c1 = get_one_char(); // get the replacement char
3887 cnt = 0;
3888 if (c1 == '.')
3889 cnt = (rows - 2) / 2; // put dot at center
3890 if (c1 == '-')
3891 cnt = rows - 2; // put dot at bottom
3892 screenbegin = begin_line(dot); // start dot at top
3893 dot_scroll(cnt, -1);
3894 break;
3895 case '|': // |- move to column "cmdcnt"
3896 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3897 break;
3898 case '~': // ~- flip the case of letters a-z -> A-Z
3899 do {
3900 if (islower(*dot)) {
3901 *dot = toupper(*dot);
3902 file_modified++;
3903 } else if (isupper(*dot)) {
3904 *dot = tolower(*dot);
3905 file_modified++;
3907 dot_right();
3908 } while (--cmdcnt > 0);
3909 end_cmd_q(); // stop adding to q
3910 break;
3911 //----- The Cursor and Function Keys -----------------------------
3912 case KEYCODE_HOME: // Cursor Key Home
3913 dot_begin();
3914 break;
3915 // The Fn keys could point to do_macro which could translate them
3916 #if 0
3917 case KEYCODE_FUN1: // Function Key F1
3918 case KEYCODE_FUN2: // Function Key F2
3919 case KEYCODE_FUN3: // Function Key F3
3920 case KEYCODE_FUN4: // Function Key F4
3921 case KEYCODE_FUN5: // Function Key F5
3922 case KEYCODE_FUN6: // Function Key F6
3923 case KEYCODE_FUN7: // Function Key F7
3924 case KEYCODE_FUN8: // Function Key F8
3925 case KEYCODE_FUN9: // Function Key F9
3926 case KEYCODE_FUN10: // Function Key F10
3927 case KEYCODE_FUN11: // Function Key F11
3928 case KEYCODE_FUN12: // Function Key F12
3929 break;
3930 #endif
3933 dc1:
3934 // if text[] just became empty, add back an empty line
3935 if (end == text) {
3936 char_insert(text, '\n'); // start empty buf with dummy line
3937 dot = text;
3939 // it is OK for dot to exactly equal to end, otherwise check dot validity
3940 if (dot != end) {
3941 dot = bound_dot(dot); // make sure "dot" is valid
3943 #if ENABLE_FEATURE_VI_YANKMARK
3944 check_context(c); // update the current context
3945 #endif
3947 if (!isdigit(c))
3948 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3949 cnt = dot - begin_line(dot);
3950 // Try to stay off of the Newline
3951 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3952 dot--;
3955 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3956 #if ENABLE_FEATURE_VI_CRASHME
3957 static int totalcmds = 0;
3958 static int Mp = 85; // Movement command Probability
3959 static int Np = 90; // Non-movement command Probability
3960 static int Dp = 96; // Delete command Probability
3961 static int Ip = 97; // Insert command Probability
3962 static int Yp = 98; // Yank command Probability
3963 static int Pp = 99; // Put command Probability
3964 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3965 static const char chars[20] = "\t012345 abcdABCD-=.$";
3966 static const char *const words[20] = {
3967 "this", "is", "a", "test",
3968 "broadcast", "the", "emergency", "of",
3969 "system", "quick", "brown", "fox",
3970 "jumped", "over", "lazy", "dogs",
3971 "back", "January", "Febuary", "March"
3973 static const char *const lines[20] = {
3974 "You should have received a copy of the GNU General Public License\n",
3975 "char c, cm, *cmd, *cmd1;\n",
3976 "generate a command by percentages\n",
3977 "Numbers may be typed as a prefix to some commands.\n",
3978 "Quit, discarding changes!\n",
3979 "Forced write, if permission originally not valid.\n",
3980 "In general, any ex or ed command (such as substitute or delete).\n",
3981 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3982 "Please get w/ me and I will go over it with you.\n",
3983 "The following is a list of scheduled, committed changes.\n",
3984 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3985 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3986 "Any question about transactions please contact Sterling Huxley.\n",
3987 "I will try to get back to you by Friday, December 31.\n",
3988 "This Change will be implemented on Friday.\n",
3989 "Let me know if you have problems accessing this;\n",
3990 "Sterling Huxley recently added you to the access list.\n",
3991 "Would you like to go to lunch?\n",
3992 "The last command will be automatically run.\n",
3993 "This is too much english for a computer geek.\n",
3995 static char *multilines[20] = {
3996 "You should have received a copy of the GNU General Public License\n",
3997 "char c, cm, *cmd, *cmd1;\n",
3998 "generate a command by percentages\n",
3999 "Numbers may be typed as a prefix to some commands.\n",
4000 "Quit, discarding changes!\n",
4001 "Forced write, if permission originally not valid.\n",
4002 "In general, any ex or ed command (such as substitute or delete).\n",
4003 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4004 "Please get w/ me and I will go over it with you.\n",
4005 "The following is a list of scheduled, committed changes.\n",
4006 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4007 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4008 "Any question about transactions please contact Sterling Huxley.\n",
4009 "I will try to get back to you by Friday, December 31.\n",
4010 "This Change will be implemented on Friday.\n",
4011 "Let me know if you have problems accessing this;\n",
4012 "Sterling Huxley recently added you to the access list.\n",
4013 "Would you like to go to lunch?\n",
4014 "The last command will be automatically run.\n",
4015 "This is too much english for a computer geek.\n",
4018 // create a random command to execute
4019 static void crash_dummy()
4021 static int sleeptime; // how long to pause between commands
4022 char c, cm, *cmd, *cmd1;
4023 int i, cnt, thing, rbi, startrbi, percent;
4025 // "dot" movement commands
4026 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4028 // is there already a command running?
4029 if (readbuffer[0] > 0)
4030 goto cd1;
4031 cd0:
4032 readbuffer[0] = 'X';
4033 startrbi = rbi = 1;
4034 sleeptime = 0; // how long to pause between commands
4035 memset(readbuffer, '\0', sizeof(readbuffer));
4036 // generate a command by percentages
4037 percent = (int) lrand48() % 100; // get a number from 0-99
4038 if (percent < Mp) { // Movement commands
4039 // available commands
4040 cmd = cmd1;
4041 M++;
4042 } else if (percent < Np) { // non-movement commands
4043 cmd = "mz<>\'\""; // available commands
4044 N++;
4045 } else if (percent < Dp) { // Delete commands
4046 cmd = "dx"; // available commands
4047 D++;
4048 } else if (percent < Ip) { // Inset commands
4049 cmd = "iIaAsrJ"; // available commands
4050 I++;
4051 } else if (percent < Yp) { // Yank commands
4052 cmd = "yY"; // available commands
4053 Y++;
4054 } else if (percent < Pp) { // Put commands
4055 cmd = "pP"; // available commands
4056 P++;
4057 } else {
4058 // We do not know how to handle this command, try again
4059 U++;
4060 goto cd0;
4062 // randomly pick one of the available cmds from "cmd[]"
4063 i = (int) lrand48() % strlen(cmd);
4064 cm = cmd[i];
4065 if (strchr(":\024", cm))
4066 goto cd0; // dont allow colon or ctrl-T commands
4067 readbuffer[rbi++] = cm; // put cmd into input buffer
4069 // now we have the command-
4070 // there are 1, 2, and multi char commands
4071 // find out which and generate the rest of command as necessary
4072 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4073 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4074 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4075 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4077 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4078 c = cmd1[thing];
4079 readbuffer[rbi++] = c; // add movement to input buffer
4081 if (strchr("iIaAsc", cm)) { // multi-char commands
4082 if (cm == 'c') {
4083 // change some thing
4084 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4085 c = cmd1[thing];
4086 readbuffer[rbi++] = c; // add movement to input buffer
4088 thing = (int) lrand48() % 4; // what thing to insert
4089 cnt = (int) lrand48() % 10; // how many to insert
4090 for (i = 0; i < cnt; i++) {
4091 if (thing == 0) { // insert chars
4092 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4093 } else if (thing == 1) { // insert words
4094 strcat(readbuffer, words[(int) lrand48() % 20]);
4095 strcat(readbuffer, " ");
4096 sleeptime = 0; // how fast to type
4097 } else if (thing == 2) { // insert lines
4098 strcat(readbuffer, lines[(int) lrand48() % 20]);
4099 sleeptime = 0; // how fast to type
4100 } else { // insert multi-lines
4101 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4102 sleeptime = 0; // how fast to type
4105 strcat(readbuffer, "\033");
4107 readbuffer[0] = strlen(readbuffer + 1);
4108 cd1:
4109 totalcmds++;
4110 if (sleeptime > 0)
4111 mysleep(sleeptime); // sleep 1/100 sec
4114 // test to see if there are any errors
4115 static void crash_test()
4117 static time_t oldtim;
4119 time_t tim;
4120 char d[2], msg[80];
4122 msg[0] = '\0';
4123 if (end < text) {
4124 strcat(msg, "end<text ");
4126 if (end > textend) {
4127 strcat(msg, "end>textend ");
4129 if (dot < text) {
4130 strcat(msg, "dot<text ");
4132 if (dot > end) {
4133 strcat(msg, "dot>end ");
4135 if (screenbegin < text) {
4136 strcat(msg, "screenbegin<text ");
4138 if (screenbegin > end - 1) {
4139 strcat(msg, "screenbegin>end-1 ");
4142 if (msg[0]) {
4143 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4144 totalcmds, last_input_char, msg, SOs, SOn);
4145 fflush_all();
4146 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4147 if (d[0] == '\n' || d[0] == '\r')
4148 break;
4151 tim = time(NULL);
4152 if (tim >= (oldtim + 3)) {
4153 sprintf(status_buffer,
4154 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4155 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4156 oldtim = tim;
4159 #endif