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>
5  *
6  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
7  */
8 //
9 //Things To Do:
10 //	./.exrc
11 //	add magic to search	/foo.*bar
12 //	add :help command
13 //	:map macros
14 //	if mark[] values were line numbers rather than pointers
15 //	it would be easier to change the mark when add/delete lines
16 //	More intelligence in refresh()
17 //	":r !cmd"  and  "!cmd"  to filter text through an external command
18 //	An "ex" line oriented mode- maybe using "cmdedit"
19 
20 //config:config VI
21 //config:	bool "vi (23 kb)"
22 //config:	default y
23 //config:	help
24 //config:	'vi' is a text editor. More specifically, it is the One True
25 //config:	text editor <grin>. It does, however, have a rather steep
26 //config:	learning curve. If you are not already comfortable with 'vi'
27 //config:	you may wish to use something else.
28 //config:
29 //config:config FEATURE_VI_MAX_LEN
30 //config:	int "Maximum screen width"
31 //config:	range 256 16384
32 //config:	default 4096
33 //config:	depends on VI
34 //config:	help
35 //config:	Contrary to what you may think, this is not eating much.
36 //config:	Make it smaller than 4k only if you are very limited on memory.
37 //config:
38 //config:config FEATURE_VI_8BIT
39 //config:	bool "Allow to display 8-bit chars (otherwise shows dots)"
40 //config:	default n
41 //config:	depends on VI
42 //config:	help
43 //config:	If your terminal can display characters with high bit set,
44 //config:	you may want to enable this. Note: vi is not Unicode-capable.
45 //config:	If your terminal combines several 8-bit bytes into one character
46 //config:	(as in Unicode mode), this will not work properly.
47 //config:
48 //config:config FEATURE_VI_COLON
49 //config:	bool "Enable \":\" colon commands (no \"ex\" mode)"
50 //config:	default y
51 //config:	depends on VI
52 //config:	help
53 //config:	Enable a limited set of colon commands. This does not
54 //config:	provide an "ex" mode.
55 //config:
56 //config:config FEATURE_VI_COLON_EXPAND
57 //config:	bool "Expand \"%\" and \"#\" in colon commands"
58 //config:	default y
59 //config:	depends on FEATURE_VI_COLON
60 //config:	help
61 //config:	Expand the special characters \"%\" (current filename)
62 //config:	and \"#\" (alternate filename) in colon commands.
63 //config:
64 //config:config FEATURE_VI_YANKMARK
65 //config:	bool "Enable yank/put commands and mark cmds"
66 //config:	default y
67 //config:	depends on VI
68 //config:	help
69 //config:	This enables you to use yank and put, as well as mark.
70 //config:
71 //config:config FEATURE_VI_SEARCH
72 //config:	bool "Enable search and replace cmds"
73 //config:	default y
74 //config:	depends on VI
75 //config:	help
76 //config:	Select this if you wish to be able to do search and replace.
77 //config:
78 //config:config FEATURE_VI_REGEX_SEARCH
79 //config:	bool "Enable regex in search and replace"
80 //config:	default n   # Uses GNU regex, which may be unavailable. FIXME
81 //config:	depends on FEATURE_VI_SEARCH
82 //config:	help
83 //config:	Use extended regex search.
84 //config:
85 //config:config FEATURE_VI_USE_SIGNALS
86 //config:	bool "Catch signals"
87 //config:	default y
88 //config:	depends on VI
89 //config:	help
90 //config:	Selecting this option will make vi signal aware. This will support
91 //config:	SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
92 //config:
93 //config:config FEATURE_VI_DOT_CMD
94 //config:	bool "Remember previous cmd and \".\" cmd"
95 //config:	default y
96 //config:	depends on VI
97 //config:	help
98 //config:	Make vi remember the last command and be able to repeat it.
99 //config:
100 //config:config FEATURE_VI_READONLY
101 //config:	bool "Enable -R option and \"view\" mode"
102 //config:	default y
103 //config:	depends on VI
104 //config:	help
105 //config:	Enable the read-only command line option, which allows the user to
106 //config:	open a file in read-only mode.
107 //config:
108 //config:config FEATURE_VI_SETOPTS
109 //config:	bool "Enable settable options, ai ic showmatch"
110 //config:	default y
111 //config:	depends on VI
112 //config:	help
113 //config:	Enable the editor to set some (ai, ic, showmatch) options.
114 //config:
115 //config:config FEATURE_VI_SET
116 //config:	bool "Support :set"
117 //config:	default y
118 //config:	depends on VI
119 //config:
120 //config:config FEATURE_VI_WIN_RESIZE
121 //config:	bool "Handle window resize"
122 //config:	default y
123 //config:	depends on VI
124 //config:	help
125 //config:	Behave nicely with terminals that get resized.
126 //config:
127 //config:config FEATURE_VI_ASK_TERMINAL
128 //config:	bool "Use 'tell me cursor position' ESC sequence to measure window"
129 //config:	default y
130 //config:	depends on VI
131 //config:	help
132 //config:	If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
133 //config:	this option makes vi perform a last-ditch effort to find it:
134 //config:	position cursor to 999,999 and ask terminal to report real
135 //config:	cursor position using "ESC [ 6 n" escape sequence, then read stdin.
136 //config:	This is not clean but helps a lot on serial lines and such.
137 //config:
138 //config:config FEATURE_VI_UNDO
139 //config:	bool "Support undo command \"u\""
140 //config:	default y
141 //config:	depends on VI
142 //config:	help
143 //config:	Support the 'u' command to undo insertion, deletion, and replacement
144 //config:	of text.
145 //config:
146 //config:config FEATURE_VI_UNDO_QUEUE
147 //config:	bool "Enable undo operation queuing"
148 //config:	default y
149 //config:	depends on FEATURE_VI_UNDO
150 //config:	help
151 //config:	The vi undo functions can use an intermediate queue to greatly lower
152 //config:	malloc() calls and overhead. When the maximum size of this queue is
153 //config:	reached, the contents of the queue are committed to the undo stack.
154 //config:	This increases the size of the undo code and allows some undo
155 //config:	operations (especially un-typing/backspacing) to be far more useful.
156 //config:
157 //config:config FEATURE_VI_UNDO_QUEUE_MAX
158 //config:	int "Maximum undo character queue size"
159 //config:	default 256
160 //config:	range 32 65536
161 //config:	depends on FEATURE_VI_UNDO_QUEUE
162 //config:	help
163 //config:	This option sets the number of bytes used at runtime for the queue.
164 //config:	Smaller values will create more undo objects and reduce the amount
165 //config:	of typed or backspaced characters that are grouped into one undo
166 //config:	operation; larger values increase the potential size of each undo
167 //config:	and will generally malloc() larger objects and less frequently.
168 //config:	Unless you want more (or less) frequent "undo points" while typing,
169 //config:	you should probably leave this unchanged.
170 //config:
171 //config:config FEATURE_VI_VERBOSE_STATUS
172 //config:	bool "Enable verbose status reporting"
173 //config:	default y
174 //config:	depends on VI
175 //config:	help
176 //config:	Enable more verbose reporting of the results of yank, change,
177 //config:	delete, undo and substitution commands.
178 
179 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
180 
181 //kbuild:lib-$(CONFIG_VI) += vi.o
182 
183 //usage:#define vi_trivial_usage
184 //usage:       IF_FEATURE_VI_COLON("[-c CMD] ")IF_FEATURE_VI_READONLY("[-R] ")"[-H] [FILE]..."
185 //usage:#define vi_full_usage "\n\n"
186 //usage:       "Edit FILE\n"
187 //usage:	IF_FEATURE_VI_COLON(
188 //usage:     "\n	-c CMD	Initial command to run ($EXINIT and ~/.exrc also available)"
189 //usage:	)
190 //usage:	IF_FEATURE_VI_READONLY(
191 //usage:     "\n	-R	Read-only"
192 //usage:	)
193 //usage:     "\n	-H	List available features"
194 // note: non-standard, "vim -H" is Hebrew mode (bidi support)
195 
196 #include "libbb.h"
197 // Should be after libbb.h: on some systems regex.h needs sys/types.h:
198 #if ENABLE_FEATURE_VI_REGEX_SEARCH
199 # include <regex.h>
200 #endif
201 
202 // the CRASHME code is unmaintained, and doesn't currently build
203 #define ENABLE_FEATURE_VI_CRASHME 0
204 #define IF_FEATURE_VI_CRASHME(...)
205 
206 
207 #if ENABLE_LOCALE_SUPPORT
208 
209 #if ENABLE_FEATURE_VI_8BIT
210 //FIXME: this does not work properly for Unicode anyway
211 # define Isprint(c) (isprint)(c)
212 #else
213 # define Isprint(c) isprint_asciionly(c)
214 #endif
215 
216 #else
217 
218 // 0x9b is Meta-ESC
219 #if ENABLE_FEATURE_VI_8BIT
220 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
221 #else
222 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
223 #endif
224 
225 #endif
226 
227 
228 enum {
229 	MAX_TABSTOP = 32, // sanity limit
230 	// User input len. Need not be extra big.
231 	// Lines in file being edited *can* be bigger than this.
232 	MAX_INPUT_LEN = 128,
233 	// Sanity limits. We have only one buffer of this size.
234 	MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
235 	MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
236 };
237 
238 // VT102 ESC sequences.
239 // See "Xterm Control Sequences"
240 // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
241 #define ESC "\033"
242 // Inverse/Normal text
243 #define ESC_BOLD_TEXT ESC"[7m"
244 #define ESC_NORM_TEXT ESC"[m"
245 // Bell
246 #define ESC_BELL "\007"
247 // Clear-to-end-of-line
248 #define ESC_CLEAR2EOL ESC"[K"
249 // Clear-to-end-of-screen.
250 // (We use default param here.
251 // Full sequence is "ESC [ <num> J",
252 // <num> is 0/1/2 = "erase below/above/all".)
253 #define ESC_CLEAR2EOS          ESC"[J"
254 // Cursor to given coordinate (1,1: top left)
255 #define ESC_SET_CURSOR_POS     ESC"[%u;%uH"
256 #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
257 //UNUSED
258 //// Cursor up and down
259 //#define ESC_CURSOR_UP   ESC"[A"
260 //#define ESC_CURSOR_DOWN "\n"
261 
262 #if ENABLE_FEATURE_VI_DOT_CMD
263 // cmds modifying text[]
264 static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
265 #endif
266 
267 enum {
268 	YANKONLY = FALSE,
269 	YANKDEL = TRUE,
270 	FORWARD = 1,	// code depends on "1"  for array index
271 	BACK = -1,	// code depends on "-1" for array index
272 	LIMITED = 0,	// char_search() only current line
273 	FULL = 1,	// char_search() to the end/beginning of entire text
274 	PARTIAL = 0,	// buffer contains partial line
275 	WHOLE = 1,	// buffer contains whole lines
276 	MULTI = 2,	// buffer may include newlines
277 
278 	S_BEFORE_WS = 1,	// used in skip_thing() for moving "dot"
279 	S_TO_WS = 2,		// used in skip_thing() for moving "dot"
280 	S_OVER_WS = 3,		// used in skip_thing() for moving "dot"
281 	S_END_PUNCT = 4,	// used in skip_thing() for moving "dot"
282 	S_END_ALNUM = 5,	// used in skip_thing() for moving "dot"
283 
284 	C_END = -1,	// cursor is at end of line due to '$' command
285 };
286 
287 
288 // vi.c expects chars to be unsigned.
289 // busybox build system provides that, but it's better
290 // to audit and fix the source
291 
292 struct globals {
293 	// many references - keep near the top of globals
294 	char *text, *end;       // pointers to the user data in memory
295 	char *dot;              // where all the action takes place
296 	int text_size;		// size of the allocated buffer
297 
298 	// the rest
299 #if ENABLE_FEATURE_VI_SETOPTS
300 	smallint vi_setops;     // set by setops()
301 #define VI_AUTOINDENT (1 << 0)
302 #define VI_EXPANDTAB  (1 << 1)
303 #define VI_ERR_METHOD (1 << 2)
304 #define VI_IGNORECASE (1 << 3)
305 #define VI_SHOWMATCH  (1 << 4)
306 #define VI_TABSTOP    (1 << 5)
307 #define autoindent (vi_setops & VI_AUTOINDENT)
308 #define expandtab  (vi_setops & VI_EXPANDTAB )
309 #define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash
310 #define ignorecase (vi_setops & VI_IGNORECASE)
311 #define showmatch  (vi_setops & VI_SHOWMATCH )
312 // order of constants and strings must match
313 #define OPTS_STR \
314 		"ai\0""autoindent\0" \
315 		"et\0""expandtab\0" \
316 		"fl\0""flash\0" \
317 		"ic\0""ignorecase\0" \
318 		"sm\0""showmatch\0" \
319 		"ts\0""tabstop\0"
320 #else
321 #define autoindent (0)
322 #define expandtab  (0)
323 #define err_method (0)
324 #define ignorecase (0)
325 #endif
326 
327 #if ENABLE_FEATURE_VI_READONLY
328 	smallint readonly_mode;
329 #define SET_READONLY_FILE(flags)        ((flags) |= 0x01)
330 #define SET_READONLY_MODE(flags)        ((flags) |= 0x02)
331 #define UNSET_READONLY_FILE(flags)      ((flags) &= 0xfe)
332 #else
333 #define SET_READONLY_FILE(flags)        ((void)0)
334 #define SET_READONLY_MODE(flags)        ((void)0)
335 #define UNSET_READONLY_FILE(flags)      ((void)0)
336 #endif
337 
338 	smallint editing;        // >0 while we are editing a file
339 	                         // [code audit says "can be 0, 1 or 2 only"]
340 	smallint cmd_mode;       // 0=command  1=insert 2=replace
341 	int modified_count;      // buffer contents changed if !0
342 	int last_modified_count; // = -1;
343 	int cmdline_filecnt;     // how many file names on cmd line
344 	int cmdcnt;              // repetition count
345 	unsigned rows, columns;	 // the terminal screen is this size
346 #if ENABLE_FEATURE_VI_ASK_TERMINAL
347 	int get_rowcol_error;
348 #endif
349 	int crow, ccol;          // cursor is on Crow x Ccol
350 	int offset;              // chars scrolled off the screen to the left
351 	int have_status_msg;     // is default edit status needed?
352 	                         // [don't make smallint!]
353 	int last_status_cksum;   // hash of current status line
354 	char *current_filename;
355 #if ENABLE_FEATURE_VI_COLON_EXPAND
356 	char *alt_filename;
357 #endif
358 	char *screenbegin;       // index into text[], of top line on the screen
359 	char *screen;            // pointer to the virtual screen buffer
360 	int screensize;          //            and its size
361 	int tabstop;
362 	int last_search_char;    // last char searched for (int because of Unicode)
363 	smallint last_search_cmd;    // command used to invoke last char search
364 #if ENABLE_FEATURE_VI_CRASHME
365 	char last_input_char;    // last char read from user
366 #endif
367 #if ENABLE_FEATURE_VI_UNDO_QUEUE
368 	char undo_queue_state;   // One of UNDO_INS, UNDO_DEL, UNDO_EMPTY
369 #endif
370 
371 #if ENABLE_FEATURE_VI_DOT_CMD
372 	smallint adding2q;	 // are we currently adding user input to q
373 	int lmc_len;             // length of last_modifying_cmd
374 	char *ioq, *ioq_start;   // pointer to string for get_one_char to "read"
375 	int dotcnt;              // number of times to repeat '.' command
376 #endif
377 #if ENABLE_FEATURE_VI_SEARCH
378 	char *last_search_pattern; // last pattern from a '/' or '?' search
379 #endif
380 #if ENABLE_FEATURE_VI_SETOPTS
381 	int indentcol;		// column of recently autoindent, 0 or -1
382 #endif
383 	smallint cmd_error;
384 
385 	// former statics
386 #if ENABLE_FEATURE_VI_YANKMARK
387 	char *edit_file__cur_line;
388 #endif
389 	int refresh__old_offset;
390 	int format_edit_status__tot;
391 
392 	// a few references only
393 #if ENABLE_FEATURE_VI_YANKMARK
394 	smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
395 #define Ureg 27
396 	char *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
397 	char regtype[28];       // buffer type: WHOLE, MULTI or PARTIAL
398 	char *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
399 #endif
400 #if ENABLE_FEATURE_VI_USE_SIGNALS
401 	sigjmp_buf restart;     // int_handler() jumps to location remembered here
402 #endif
403 	struct termios term_orig; // remember what the cooked mode was
404 	int cindex;               // saved character index for up/down motion
405 	smallint keep_index;      // retain saved character index
406 #if ENABLE_FEATURE_VI_COLON
407 	llist_t *initial_cmds;
408 #endif
409 	// Should be just enough to hold a key sequence,
410 	// but CRASHME mode uses it as generated command buffer too
411 #if ENABLE_FEATURE_VI_CRASHME
412 	char readbuffer[128];
413 #else
414 	char readbuffer[KEYCODE_BUFFER_SIZE];
415 #endif
416 #define STATUS_BUFFER_LEN  200
417 	char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
418 #if ENABLE_FEATURE_VI_DOT_CMD
419 	char last_modifying_cmd[MAX_INPUT_LEN];	// last modifying cmd for "."
420 #endif
421 	char get_input_line__buf[MAX_INPUT_LEN]; // former static
422 
423 	char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
424 
425 #if ENABLE_FEATURE_VI_UNDO
426 // undo_push() operations
427 #define UNDO_INS         0
428 #define UNDO_DEL         1
429 #define UNDO_INS_CHAIN   2
430 #define UNDO_DEL_CHAIN   3
431 # if ENABLE_FEATURE_VI_UNDO_QUEUE
432 #define UNDO_INS_QUEUED  4
433 #define UNDO_DEL_QUEUED  5
434 # endif
435 
436 // Pass-through flags for functions that can be undone
437 #define NO_UNDO          0
438 #define ALLOW_UNDO       1
439 #define ALLOW_UNDO_CHAIN 2
440 # if ENABLE_FEATURE_VI_UNDO_QUEUE
441 #define ALLOW_UNDO_QUEUED 3
442 # else
443 // If undo queuing disabled, don't invoke the missing queue logic
444 #define ALLOW_UNDO_QUEUED ALLOW_UNDO
445 # endif
446 
447 	struct undo_object {
448 		struct undo_object *prev;	// Linking back avoids list traversal (LIFO)
449 		int start;		// Offset where the data should be restored/deleted
450 		int length;		// total data size
451 		uint8_t u_type;		// 0=deleted, 1=inserted, 2=swapped
452 		char undo_text[1];	// text that was deleted (if deletion)
453 	} *undo_stack_tail;
454 # if ENABLE_FEATURE_VI_UNDO_QUEUE
455 #define UNDO_USE_SPOS   32
456 #define UNDO_EMPTY      64
457 	char *undo_queue_spos;	// Start position of queued operation
458 	int undo_q;
459 	char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
460 # endif
461 #endif /* ENABLE_FEATURE_VI_UNDO */
462 };
463 #define G (*ptr_to_globals)
464 #define text           (G.text          )
465 #define text_size      (G.text_size     )
466 #define end            (G.end           )
467 #define dot            (G.dot           )
468 #define reg            (G.reg           )
469 
470 #define vi_setops               (G.vi_setops          )
471 #define editing                 (G.editing            )
472 #define cmd_mode                (G.cmd_mode           )
473 #define modified_count          (G.modified_count     )
474 #define last_modified_count     (G.last_modified_count)
475 #define cmdline_filecnt         (G.cmdline_filecnt    )
476 #define cmdcnt                  (G.cmdcnt             )
477 #define rows                    (G.rows               )
478 #define columns                 (G.columns            )
479 #define crow                    (G.crow               )
480 #define ccol                    (G.ccol               )
481 #define offset                  (G.offset             )
482 #define status_buffer           (G.status_buffer      )
483 #define have_status_msg         (G.have_status_msg    )
484 #define last_status_cksum       (G.last_status_cksum  )
485 #define current_filename        (G.current_filename   )
486 #define alt_filename            (G.alt_filename       )
487 #define screen                  (G.screen             )
488 #define screensize              (G.screensize         )
489 #define screenbegin             (G.screenbegin        )
490 #define tabstop                 (G.tabstop            )
491 #define last_search_char        (G.last_search_char   )
492 #define last_search_cmd         (G.last_search_cmd    )
493 #if ENABLE_FEATURE_VI_CRASHME
494 #define last_input_char         (G.last_input_char    )
495 #endif
496 #if ENABLE_FEATURE_VI_READONLY
497 #define readonly_mode           (G.readonly_mode      )
498 #else
499 #define readonly_mode           0
500 #endif
501 #define adding2q                (G.adding2q           )
502 #define lmc_len                 (G.lmc_len            )
503 #define ioq                     (G.ioq                )
504 #define ioq_start               (G.ioq_start          )
505 #define dotcnt                  (G.dotcnt             )
506 #define last_search_pattern     (G.last_search_pattern)
507 #define indentcol               (G.indentcol          )
508 #define cmd_error               (G.cmd_error          )
509 
510 #define edit_file__cur_line     (G.edit_file__cur_line)
511 #define refresh__old_offset     (G.refresh__old_offset)
512 #define format_edit_status__tot (G.format_edit_status__tot)
513 
514 #define YDreg          (G.YDreg         )
515 //#define Ureg           (G.Ureg          )
516 #define regtype        (G.regtype       )
517 #define mark           (G.mark          )
518 #define restart        (G.restart       )
519 #define term_orig      (G.term_orig     )
520 #define cindex         (G.cindex        )
521 #define keep_index     (G.keep_index    )
522 #define initial_cmds   (G.initial_cmds  )
523 #define readbuffer     (G.readbuffer    )
524 #define scr_out_buf    (G.scr_out_buf   )
525 #define last_modifying_cmd  (G.last_modifying_cmd )
526 #define get_input_line__buf (G.get_input_line__buf)
527 
528 #if ENABLE_FEATURE_VI_UNDO
529 #define undo_stack_tail  (G.undo_stack_tail )
530 # if ENABLE_FEATURE_VI_UNDO_QUEUE
531 #define undo_queue_state (G.undo_queue_state)
532 #define undo_q           (G.undo_q          )
533 #define undo_queue       (G.undo_queue      )
534 #define undo_queue_spos  (G.undo_queue_spos )
535 # endif
536 #endif
537 
538 #define INIT_G() do { \
539 	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
540 	last_modified_count = -1; \
541 	/* "" but has space for 2 chars: */ \
542 	IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
543 	tabstop = 8; \
544 } while (0)
545 
546 #if ENABLE_FEATURE_VI_CRASHME
547 static int crashme = 0;
548 #endif
549 
550 static void show_status_line(void);	// put a message on the bottom line
551 static void status_line_bold(const char *, ...);
552 
show_help(void)553 static void show_help(void)
554 {
555 	puts("These features are available:"
556 #if ENABLE_FEATURE_VI_SEARCH
557 	"\n\tPattern searches with / and ?"
558 #endif
559 #if ENABLE_FEATURE_VI_DOT_CMD
560 	"\n\tLast command repeat with ."
561 #endif
562 #if ENABLE_FEATURE_VI_YANKMARK
563 	"\n\tLine marking with 'x"
564 	"\n\tNamed buffers with \"x"
565 #endif
566 #if ENABLE_FEATURE_VI_READONLY
567 	//not implemented: "\n\tReadonly if vi is called as \"view\""
568 	//redundant: usage text says this too: "\n\tReadonly with -R command line arg"
569 #endif
570 #if ENABLE_FEATURE_VI_SET
571 	"\n\tSome colon mode commands with :"
572 #endif
573 #if ENABLE_FEATURE_VI_SETOPTS
574 	"\n\tSettable options with \":set\""
575 #endif
576 #if ENABLE_FEATURE_VI_USE_SIGNALS
577 	"\n\tSignal catching- ^C"
578 	"\n\tJob suspend and resume with ^Z"
579 #endif
580 #if ENABLE_FEATURE_VI_WIN_RESIZE
581 	"\n\tAdapt to window re-sizes"
582 #endif
583 	);
584 }
585 
write1(const char * out)586 static void write1(const char *out)
587 {
588 	fputs_stdout(out);
589 }
590 
591 #if ENABLE_FEATURE_VI_WIN_RESIZE
query_screen_dimensions(void)592 static int query_screen_dimensions(void)
593 {
594 	int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
595 	if (rows > MAX_SCR_ROWS)
596 		rows = MAX_SCR_ROWS;
597 	if (columns > MAX_SCR_COLS)
598 		columns = MAX_SCR_COLS;
599 	return err;
600 }
601 #else
query_screen_dimensions(void)602 static ALWAYS_INLINE int query_screen_dimensions(void)
603 {
604 	return 0;
605 }
606 #endif
607 
608 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
mysleep(int hund)609 static int mysleep(int hund)
610 {
611 	struct pollfd pfd[1];
612 
613 	if (hund != 0)
614 		fflush_all();
615 
616 	pfd[0].fd = STDIN_FILENO;
617 	pfd[0].events = POLLIN;
618 	return safe_poll(pfd, 1, hund*10) > 0;
619 }
620 
621 //----- Set terminal attributes --------------------------------
rawmode(void)622 static void rawmode(void)
623 {
624 	// no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
625 	set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
626 }
627 
cookmode(void)628 static void cookmode(void)
629 {
630 	fflush_all();
631 	tcsetattr_stdin_TCSANOW(&term_orig);
632 }
633 
634 //----- Terminal Drawing ---------------------------------------
635 // The terminal is made up of 'rows' line of 'columns' columns.
636 // classically this would be 24 x 80.
637 //  screen coordinates
638 //  0,0     ...     0,79
639 //  1,0     ...     1,79
640 //  .       ...     .
641 //  .       ...     .
642 //  22,0    ...     22,79
643 //  23,0    ...     23,79   <- status line
644 
645 //----- Move the cursor to row x col (count from 0, not 1) -------
place_cursor(int row,int col)646 static void place_cursor(int row, int col)
647 {
648 	char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
649 
650 	if (row < 0) row = 0;
651 	if (row >= rows) row = rows - 1;
652 	if (col < 0) col = 0;
653 	if (col >= columns) col = columns - 1;
654 
655 	sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
656 	write1(cm1);
657 }
658 
659 //----- Erase from cursor to end of line -----------------------
clear_to_eol(void)660 static void clear_to_eol(void)
661 {
662 	write1(ESC_CLEAR2EOL);
663 }
664 
go_bottom_and_clear_to_eol(void)665 static void go_bottom_and_clear_to_eol(void)
666 {
667 	place_cursor(rows - 1, 0);
668 	clear_to_eol();
669 }
670 
671 //----- Start standout mode ------------------------------------
standout_start(void)672 static void standout_start(void)
673 {
674 	write1(ESC_BOLD_TEXT);
675 }
676 
677 //----- End standout mode --------------------------------------
standout_end(void)678 static void standout_end(void)
679 {
680 	write1(ESC_NORM_TEXT);
681 }
682 
683 //----- Text Movement Routines ---------------------------------
begin_line(char * p)684 static char *begin_line(char *p) // return pointer to first char cur line
685 {
686 	if (p > text) {
687 		p = memrchr(text, '\n', p - text);
688 		if (!p)
689 			return text;
690 		return p + 1;
691 	}
692 	return p;
693 }
694 
end_line(char * p)695 static char *end_line(char *p) // return pointer to NL of cur line
696 {
697 	if (p < end - 1) {
698 		p = memchr(p, '\n', end - p - 1);
699 		if (!p)
700 			return end - 1;
701 	}
702 	return p;
703 }
704 
dollar_line(char * p)705 static char *dollar_line(char *p) // return pointer to just before NL line
706 {
707 	p = end_line(p);
708 	// Try to stay off of the Newline
709 	if (*p == '\n' && (p - begin_line(p)) > 0)
710 		p--;
711 	return p;
712 }
713 
prev_line(char * p)714 static char *prev_line(char *p) // return pointer first char prev line
715 {
716 	p = begin_line(p);	// goto beginning of cur line
717 	if (p > text && p[-1] == '\n')
718 		p--;			// step to prev line
719 	p = begin_line(p);	// goto beginning of prev line
720 	return p;
721 }
722 
next_line(char * p)723 static char *next_line(char *p) // return pointer first char next line
724 {
725 	p = end_line(p);
726 	if (p < end - 1 && *p == '\n')
727 		p++;			// step to next line
728 	return p;
729 }
730 
731 //----- Text Information Routines ------------------------------
end_screen(void)732 static char *end_screen(void)
733 {
734 	char *q;
735 	int cnt;
736 
737 	// find new bottom line
738 	q = screenbegin;
739 	for (cnt = 0; cnt < rows - 2; cnt++)
740 		q = next_line(q);
741 	q = end_line(q);
742 	return q;
743 }
744 
745 // count line from start to stop
count_lines(char * start,char * stop)746 static int count_lines(char *start, char *stop)
747 {
748 	char *q;
749 	int cnt;
750 
751 	if (stop < start) { // start and stop are backwards- reverse them
752 		q = start;
753 		start = stop;
754 		stop = q;
755 	}
756 	cnt = 0;
757 	stop = end_line(stop);
758 	while (start <= stop && start <= end - 1) {
759 		start = end_line(start);
760 		if (*start == '\n')
761 			cnt++;
762 		start++;
763 	}
764 	return cnt;
765 }
766 
find_line(int li)767 static char *find_line(int li)	// find beginning of line #li
768 {
769 	char *q;
770 
771 	for (q = text; li > 1; li--) {
772 		q = next_line(q);
773 	}
774 	return q;
775 }
776 
next_tabstop(int col)777 static int next_tabstop(int col)
778 {
779 	return col + ((tabstop - 1) - (col % tabstop));
780 }
781 
prev_tabstop(int col)782 static int prev_tabstop(int col)
783 {
784 	return col - ((col % tabstop) ?: tabstop);
785 }
786 
next_column(char c,int co)787 static int next_column(char c, int co)
788 {
789 	if (c == '\t')
790 		co = next_tabstop(co);
791 	else if ((unsigned char)c < ' ' || c == 0x7f)
792 		co++; // display as ^X, use 2 columns
793 	return co + 1;
794 }
795 
get_column(char * p)796 static int get_column(char *p)
797 {
798 	const char *r;
799 	int co = 0;
800 
801 	for (r = begin_line(p); r < p; r++)
802 		co = next_column(*r, co);
803 	return co;
804 }
805 
806 //----- Erase the Screen[] memory ------------------------------
screen_erase(void)807 static void screen_erase(void)
808 {
809 	memset(screen, ' ', screensize);	// clear new screen
810 }
811 
new_screen(int ro,int co)812 static void new_screen(int ro, int co)
813 {
814 	char *s;
815 
816 	free(screen);
817 	screensize = ro * co + 8;
818 	s = screen = xmalloc(screensize);
819 	// initialize the new screen. assume this will be a empty file.
820 	screen_erase();
821 	// non-existent text[] lines start with a tilde (~).
822 	//screen[(1 * co) + 0] = '~';
823 	//screen[(2 * co) + 0] = '~';
824 	//..
825 	//screen[((ro-2) * co) + 0] = '~';
826 	ro -= 2;
827 	while (--ro >= 0) {
828 		s += co;
829 		*s = '~';
830 	}
831 }
832 
833 //----- Synchronize the cursor to Dot --------------------------
sync_cursor(char * d,int * row,int * col)834 static void sync_cursor(char *d, int *row, int *col)
835 {
836 	char *beg_cur;	// begin and end of "d" line
837 	char *tp;
838 	int cnt, ro, co;
839 
840 	beg_cur = begin_line(d);	// first char of cur line
841 
842 	if (beg_cur < screenbegin) {
843 		// "d" is before top line on screen
844 		// how many lines do we have to move
845 		cnt = count_lines(beg_cur, screenbegin);
846  sc1:
847 		screenbegin = beg_cur;
848 		if (cnt > (rows - 1) / 2) {
849 			// we moved too many lines. put "dot" in middle of screen
850 			for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
851 				screenbegin = prev_line(screenbegin);
852 			}
853 		}
854 	} else {
855 		char *end_scr;	// begin and end of screen
856 		end_scr = end_screen();	// last char of screen
857 		if (beg_cur > end_scr) {
858 			// "d" is after bottom line on screen
859 			// how many lines do we have to move
860 			cnt = count_lines(end_scr, beg_cur);
861 			if (cnt > (rows - 1) / 2)
862 				goto sc1;	// too many lines
863 			for (ro = 0; ro < cnt - 1; ro++) {
864 				// move screen begin the same amount
865 				screenbegin = next_line(screenbegin);
866 				// now, move the end of screen
867 				end_scr = next_line(end_scr);
868 				end_scr = end_line(end_scr);
869 			}
870 		}
871 	}
872 	// "d" is on screen- find out which row
873 	tp = screenbegin;
874 	for (ro = 0; ro < rows - 1; ro++) {	// drive "ro" to correct row
875 		if (tp == beg_cur)
876 			break;
877 		tp = next_line(tp);
878 	}
879 
880 	// find out what col "d" is on
881 	co = 0;
882 	do { // drive "co" to correct column
883 		if (*tp == '\n') //vda || *tp == '\0')
884 			break;
885 		co = next_column(*tp, co) - 1;
886 		// inserting text before a tab, don't include its position
887 		if (cmd_mode && tp == d - 1 && *d == '\t') {
888 			co++;
889 			break;
890 		}
891 	} while (tp++ < d && ++co);
892 
893 	// "co" is the column where "dot" is.
894 	// The screen has "columns" columns.
895 	// The currently displayed columns are  0+offset -- columns+ofset
896 	// |-------------------------------------------------------------|
897 	//               ^ ^                                ^
898 	//        offset | |------- columns ----------------|
899 	//
900 	// If "co" is already in this range then we do not have to adjust offset
901 	//      but, we do have to subtract the "offset" bias from "co".
902 	// If "co" is outside this range then we have to change "offset".
903 	// If the first char of a line is a tab the cursor will try to stay
904 	//  in column 7, but we have to set offset to 0.
905 
906 	if (co < 0 + offset) {
907 		offset = co;
908 	}
909 	if (co >= columns + offset) {
910 		offset = co - columns + 1;
911 	}
912 	// if the first char of the line is a tab, and "dot" is sitting on it
913 	//  force offset to 0.
914 	if (d == beg_cur && *d == '\t') {
915 		offset = 0;
916 	}
917 	co -= offset;
918 
919 	*row = ro;
920 	*col = co;
921 }
922 
923 //----- Format a text[] line into a buffer ---------------------
format_line(char * src)924 static char* format_line(char *src /*, int li*/)
925 {
926 	unsigned char c;
927 	int co;
928 	int ofs = offset;
929 	char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
930 
931 	c = '~'; // char in col 0 in non-existent lines is '~'
932 	co = 0;
933 	while (co < columns + tabstop) {
934 		// have we gone past the end?
935 		if (src < end) {
936 			c = *src++;
937 			if (c == '\n')
938 				break;
939 			if ((c & 0x80) && !Isprint(c)) {
940 				c = '.';
941 			}
942 			if (c < ' ' || c == 0x7f) {
943 				if (c == '\t') {
944 					c = ' ';
945 					//      co %    8     !=     7
946 					while ((co % tabstop) != (tabstop - 1)) {
947 						dest[co++] = c;
948 					}
949 				} else {
950 					dest[co++] = '^';
951 					if (c == 0x7f)
952 						c = '?';
953 					else
954 						c += '@'; // Ctrl-X -> 'X'
955 				}
956 			}
957 		}
958 		dest[co++] = c;
959 		// discard scrolled-off-to-the-left portion,
960 		// in tabstop-sized pieces
961 		if (ofs >= tabstop && co >= tabstop) {
962 			memmove(dest, dest + tabstop, co);
963 			co -= tabstop;
964 			ofs -= tabstop;
965 		}
966 		if (src >= end)
967 			break;
968 	}
969 	// check "short line, gigantic offset" case
970 	if (co < ofs)
971 		ofs = co;
972 	// discard last scrolled off part
973 	co -= ofs;
974 	dest += ofs;
975 	// fill the rest with spaces
976 	if (co < columns)
977 		memset(&dest[co], ' ', columns - co);
978 	return dest;
979 }
980 
981 //----- Refresh the changed screen lines -----------------------
982 // Copy the source line from text[] into the buffer and note
983 // if the current screenline is different from the new buffer.
984 // If they differ then that line needs redrawing on the terminal.
985 //
refresh(int full_screen)986 static void refresh(int full_screen)
987 {
988 #define old_offset refresh__old_offset
989 
990 	int li, changed;
991 	char *tp, *sp;		// pointer into text[] and screen[]
992 
993 	if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
994 		unsigned c = columns, r = rows;
995 		query_screen_dimensions();
996 #if ENABLE_FEATURE_VI_USE_SIGNALS
997 		full_screen |= (c - columns) | (r - rows);
998 #else
999 		if (c != columns || r != rows) {
1000 			full_screen = TRUE;
1001 			// update screen memory since SIGWINCH won't have done it
1002 			new_screen(rows, columns);
1003 		}
1004 #endif
1005 	}
1006 	sync_cursor(dot, &crow, &ccol);	// where cursor will be (on "dot")
1007 	tp = screenbegin;	// index into text[] of top line
1008 
1009 	// compare text[] to screen[] and mark screen[] lines that need updating
1010 	for (li = 0; li < rows - 1; li++) {
1011 		int cs, ce;				// column start & end
1012 		char *out_buf;
1013 		// format current text line
1014 		out_buf = format_line(tp /*, li*/);
1015 
1016 		// skip to the end of the current text[] line
1017 		if (tp < end) {
1018 			char *t = memchr(tp, '\n', end - tp);
1019 			if (!t) t = end - 1;
1020 			tp = t + 1;
1021 		}
1022 
1023 		// see if there are any changes between virtual screen and out_buf
1024 		changed = FALSE;	// assume no change
1025 		cs = 0;
1026 		ce = columns - 1;
1027 		sp = &screen[li * columns];	// start of screen line
1028 		if (full_screen) {
1029 			// force re-draw of every single column from 0 - columns-1
1030 			goto re0;
1031 		}
1032 		// compare newly formatted buffer with virtual screen
1033 		// look forward for first difference between buf and screen
1034 		for (; cs <= ce; cs++) {
1035 			if (out_buf[cs] != sp[cs]) {
1036 				changed = TRUE;	// mark for redraw
1037 				break;
1038 			}
1039 		}
1040 
1041 		// look backward for last difference between out_buf and screen
1042 		for (; ce >= cs; ce--) {
1043 			if (out_buf[ce] != sp[ce]) {
1044 				changed = TRUE;	// mark for redraw
1045 				break;
1046 			}
1047 		}
1048 		// now, cs is index of first diff, and ce is index of last diff
1049 
1050 		// if horz offset has changed, force a redraw
1051 		if (offset != old_offset) {
1052  re0:
1053 			changed = TRUE;
1054 		}
1055 
1056 		// make a sanity check of columns indexes
1057 		if (cs < 0) cs = 0;
1058 		if (ce > columns - 1) ce = columns - 1;
1059 		if (cs > ce) { cs = 0; ce = columns - 1; }
1060 		// is there a change between virtual screen and out_buf
1061 		if (changed) {
1062 			// copy changed part of buffer to virtual screen
1063 			memcpy(sp+cs, out_buf+cs, ce-cs+1);
1064 			place_cursor(li, cs);
1065 			// write line out to terminal
1066 			fwrite(&sp[cs], ce - cs + 1, 1, stdout);
1067 		}
1068 	}
1069 
1070 	place_cursor(crow, ccol);
1071 
1072 	if (!keep_index)
1073 		cindex = ccol + offset;
1074 
1075 	old_offset = offset;
1076 #undef old_offset
1077 }
1078 
1079 //----- Force refresh of all Lines -----------------------------
redraw(int full_screen)1080 static void redraw(int full_screen)
1081 {
1082 	// cursor to top,left; clear to the end of screen
1083 	write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
1084 	screen_erase();		// erase the internal screen buffer
1085 	last_status_cksum = 0;	// force status update
1086 	refresh(full_screen);	// this will redraw the entire display
1087 	show_status_line();
1088 }
1089 
1090 //----- Flash the screen  --------------------------------------
flash(int h)1091 static void flash(int h)
1092 {
1093 	standout_start();
1094 	redraw(TRUE);
1095 	mysleep(h);
1096 	standout_end();
1097 	redraw(TRUE);
1098 }
1099 
indicate_error(void)1100 static void indicate_error(void)
1101 {
1102 #if ENABLE_FEATURE_VI_CRASHME
1103 	if (crashme > 0)
1104 		return;
1105 #endif
1106 	cmd_error = TRUE;
1107 	if (!err_method) {
1108 		write1(ESC_BELL);
1109 	} else {
1110 		flash(10);
1111 	}
1112 }
1113 
1114 //----- IO Routines --------------------------------------------
readit(void)1115 static int readit(void) // read (maybe cursor) key from stdin
1116 {
1117 	int c;
1118 
1119 	fflush_all();
1120 
1121 	// Wait for input. TIMEOUT = -1 makes read_key wait even
1122 	// on nonblocking stdin.
1123 	// Note: read_key sets errno to 0 on success.
1124  again:
1125 	c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1126 	if (c == -1) { // EOF/error
1127 		if (errno == EAGAIN) // paranoia
1128 			goto again;
1129 		go_bottom_and_clear_to_eol();
1130 		cookmode(); // terminal to "cooked"
1131 		bb_simple_error_msg_and_die("can't read user input");
1132 	}
1133 	return c;
1134 }
1135 
1136 #if ENABLE_FEATURE_VI_DOT_CMD
get_one_char(void)1137 static int get_one_char(void)
1138 {
1139 	int c;
1140 
1141 	if (!adding2q) {
1142 		// we are not adding to the q.
1143 		// but, we may be reading from a saved q.
1144 		// (checking "ioq" for NULL is wrong, it's not reset to NULL
1145 		// when done - "ioq_start" is reset instead).
1146 		if (ioq_start != NULL) {
1147 			// there is a queue to get chars from.
1148 			// careful with correct sign expansion!
1149 			c = (unsigned char)*ioq++;
1150 			if (c != '\0')
1151 				return c;
1152 			// the end of the q
1153 			free(ioq_start);
1154 			ioq_start = NULL;
1155 			// read from STDIN:
1156 		}
1157 		return readit();
1158 	}
1159 	// we are adding STDIN chars to q.
1160 	c = readit();
1161 	if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 2) {
1162 		// last_modifying_cmd[] is too small, can't remember the cmd
1163 		// - drop it
1164 		adding2q = 0;
1165 		lmc_len = 0;
1166 	} else {
1167 		last_modifying_cmd[lmc_len++] = c;
1168 	}
1169 	return c;
1170 }
1171 #else
1172 # define get_one_char() readit()
1173 #endif
1174 
1175 // Get type of thing to operate on and adjust count
get_motion_char(void)1176 static int get_motion_char(void)
1177 {
1178 	int c, cnt;
1179 
1180 	c = get_one_char();
1181 	if (isdigit(c)) {
1182 		if (c != '0') {
1183 			// get any non-zero motion count
1184 			for (cnt = 0; isdigit(c); c = get_one_char())
1185 				cnt = cnt * 10 + (c - '0');
1186 			cmdcnt = (cmdcnt ?: 1) * cnt;
1187 		} else {
1188 			// ensure standalone '0' works
1189 			cmdcnt = 0;
1190 		}
1191 	}
1192 
1193 	return c;
1194 }
1195 
1196 // Get input line (uses "status line" area)
get_input_line(const char * prompt)1197 static char *get_input_line(const char *prompt)
1198 {
1199 	// char [MAX_INPUT_LEN]
1200 #define buf get_input_line__buf
1201 
1202 	int c;
1203 	int i;
1204 
1205 	strcpy(buf, prompt);
1206 	last_status_cksum = 0;	// force status update
1207 	go_bottom_and_clear_to_eol();
1208 	write1(buf);      // write out the :, /, or ? prompt
1209 
1210 	i = strlen(buf);
1211 	while (i < MAX_INPUT_LEN - 1) {
1212 		c = get_one_char();
1213 		if (c == '\n' || c == '\r' || c == 27)
1214 			break;		// this is end of input
1215 		if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
1216 			// user wants to erase prev char
1217 			write1("\b \b"); // erase char on screen
1218 			buf[--i] = '\0';
1219 			if (i <= 0) // user backs up before b-o-l, exit
1220 				break;
1221 		} else if (c > 0 && c < 256) { // exclude Unicode
1222 			// (TODO: need to handle Unicode)
1223 			buf[i] = c;
1224 			buf[++i] = '\0';
1225 			bb_putchar(c);
1226 		}
1227 	}
1228 	refresh(FALSE);
1229 	return buf;
1230 #undef buf
1231 }
1232 
Hit_Return(void)1233 static void Hit_Return(void)
1234 {
1235 	int c;
1236 
1237 	standout_start();
1238 	write1("[Hit return to continue]");
1239 	standout_end();
1240 	while ((c = get_one_char()) != '\n' && c != '\r')
1241 		continue;
1242 	redraw(TRUE);		// force redraw all
1243 }
1244 
1245 //----- Draw the status line at bottom of the screen -------------
1246 // show file status on status line
format_edit_status(void)1247 static int format_edit_status(void)
1248 {
1249 	static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1250 
1251 #define tot format_edit_status__tot
1252 
1253 	int cur, percent, ret, trunc_at;
1254 
1255 	// modified_count is now a counter rather than a flag.  this
1256 	// helps reduce the amount of line counting we need to do.
1257 	// (this will cause a mis-reporting of modified status
1258 	// once every MAXINT editing operations.)
1259 
1260 	// it would be nice to do a similar optimization here -- if
1261 	// we haven't done a motion that could have changed which line
1262 	// we're on, then we shouldn't have to do this count_lines()
1263 	cur = count_lines(text, dot);
1264 
1265 	// count_lines() is expensive.
1266 	// Call it only if something was changed since last time
1267 	// we were here:
1268 	if (modified_count != last_modified_count) {
1269 		tot = cur + count_lines(dot, end - 1) - 1;
1270 		last_modified_count = modified_count;
1271 	}
1272 
1273 	//    current line         percent
1274 	//   -------------    ~~ ----------
1275 	//    total lines            100
1276 	if (tot > 0) {
1277 		percent = (100 * cur) / tot;
1278 	} else {
1279 		cur = tot = 0;
1280 		percent = 100;
1281 	}
1282 
1283 	trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1284 		columns : STATUS_BUFFER_LEN-1;
1285 
1286 	ret = snprintf(status_buffer, trunc_at+1,
1287 #if ENABLE_FEATURE_VI_READONLY
1288 		"%c %s%s%s %d/%d %d%%",
1289 #else
1290 		"%c %s%s %d/%d %d%%",
1291 #endif
1292 		cmd_mode_indicator[cmd_mode & 3],
1293 		(current_filename != NULL ? current_filename : "No file"),
1294 #if ENABLE_FEATURE_VI_READONLY
1295 		(readonly_mode ? " [Readonly]" : ""),
1296 #endif
1297 		(modified_count ? " [Modified]" : ""),
1298 		cur, tot, percent);
1299 
1300 	if (ret >= 0 && ret < trunc_at)
1301 		return ret;  // it all fit
1302 
1303 	return trunc_at;  // had to truncate
1304 #undef tot
1305 }
1306 
bufsum(char * buf,int count)1307 static int bufsum(char *buf, int count)
1308 {
1309 	int sum = 0;
1310 	char *e = buf + count;
1311 	while (buf < e)
1312 		sum += (unsigned char) *buf++;
1313 	return sum;
1314 }
1315 
show_status_line(void)1316 static void show_status_line(void)
1317 {
1318 	int cnt = 0, cksum = 0;
1319 
1320 	// either we already have an error or status message, or we
1321 	// create one.
1322 	if (!have_status_msg) {
1323 		cnt = format_edit_status();
1324 		cksum = bufsum(status_buffer, cnt);
1325 	}
1326 	if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1327 		last_status_cksum = cksum;		// remember if we have seen this line
1328 		go_bottom_and_clear_to_eol();
1329 		write1(status_buffer);
1330 		if (have_status_msg) {
1331 			if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1332 					(columns - 1) ) {
1333 				have_status_msg = 0;
1334 				Hit_Return();
1335 			}
1336 			have_status_msg = 0;
1337 		}
1338 		place_cursor(crow, ccol);  // put cursor back in correct place
1339 	}
1340 	fflush_all();
1341 }
1342 
1343 //----- format the status buffer, the bottom line of screen ------
status_line(const char * format,...)1344 static void status_line(const char *format, ...)
1345 {
1346 	va_list args;
1347 
1348 	va_start(args, format);
1349 	vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1350 	va_end(args);
1351 
1352 	have_status_msg = 1;
1353 }
status_line_bold(const char * format,...)1354 static void status_line_bold(const char *format, ...)
1355 {
1356 	va_list args;
1357 
1358 	va_start(args, format);
1359 	strcpy(status_buffer, ESC_BOLD_TEXT);
1360 	vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1361 		STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1362 		format, args
1363 	);
1364 	strcat(status_buffer, ESC_NORM_TEXT);
1365 	va_end(args);
1366 
1367 	have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
1368 }
status_line_bold_errno(const char * fn)1369 static void status_line_bold_errno(const char *fn)
1370 {
1371 	status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1372 }
1373 
1374 // copy s to buf, convert unprintable
print_literal(char * buf,const char * s)1375 static void print_literal(char *buf, const char *s)
1376 {
1377 	char *d;
1378 	unsigned char c;
1379 
1380 	if (!s[0])
1381 		s = "(NULL)";
1382 
1383 	d = buf;
1384 	for (; *s; s++) {
1385 		c = *s;
1386 		if ((c & 0x80) && !Isprint(c))
1387 			c = '?';
1388 		if (c < ' ' || c == 0x7f) {
1389 			*d++ = '^';
1390 			c |= '@'; // 0x40
1391 			if (c == 0x7f)
1392 				c = '?';
1393 		}
1394 		*d++ = c;
1395 		*d = '\0';
1396 		if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1397 			break;
1398 	}
1399 }
not_implemented(const char * s)1400 static void not_implemented(const char *s)
1401 {
1402 	char buf[MAX_INPUT_LEN];
1403 	print_literal(buf, s);
1404 	status_line_bold("'%s' is not implemented", buf);
1405 }
1406 
1407 //----- Block insert/delete, undo ops --------------------------
1408 #if ENABLE_FEATURE_VI_YANKMARK
1409 // copy text into a register
text_yank(char * p,char * q,int dest,int buftype)1410 static char *text_yank(char *p, char *q, int dest, int buftype)
1411 {
1412 	char *oldreg = reg[dest];
1413 	int cnt = q - p;
1414 	if (cnt < 0) {		// they are backwards- reverse them
1415 		p = q;
1416 		cnt = -cnt;
1417 	}
1418 	// Don't free register yet.  This prevents the memory allocator
1419 	// from reusing the free block so we can detect if it's changed.
1420 	reg[dest] = xstrndup(p, cnt + 1);
1421 	regtype[dest] = buftype;
1422 	free(oldreg);
1423 	return p;
1424 }
1425 
what_reg(void)1426 static char what_reg(void)
1427 {
1428 	char c;
1429 
1430 	c = 'D';			// default to D-reg
1431 	if (YDreg <= 25)
1432 		c = 'a' + (char) YDreg;
1433 	if (YDreg == 26)
1434 		c = 'D';
1435 	if (YDreg == 27)
1436 		c = 'U';
1437 	return c;
1438 }
1439 
check_context(char cmd)1440 static void check_context(char cmd)
1441 {
1442 	// Certain movement commands update the context.
1443 	if (strchr(":%{}'GHLMz/?Nn", cmd) != NULL) {
1444 		mark[27] = mark[26];	// move cur to prev
1445 		mark[26] = dot;	// move local to cur
1446 	}
1447 }
1448 
swap_context(char * p)1449 static char *swap_context(char *p) // goto new context for '' command make this the current context
1450 {
1451 	char *tmp;
1452 
1453 	// the current context is in mark[26]
1454 	// the previous context is in mark[27]
1455 	// only swap context if other context is valid
1456 	if (text <= mark[27] && mark[27] <= end - 1) {
1457 		tmp = mark[27];
1458 		mark[27] = p;
1459 		mark[26] = p = tmp;
1460 	}
1461 	return p;
1462 }
1463 
1464 # if ENABLE_FEATURE_VI_VERBOSE_STATUS
yank_status(const char * op,const char * p,int cnt)1465 static void yank_status(const char *op, const char *p, int cnt)
1466 {
1467 	int lines, chars;
1468 
1469 	lines = chars = 0;
1470 	while (*p) {
1471 		++chars;
1472 		if (*p++ == '\n')
1473 			++lines;
1474 	}
1475 	status_line("%s %d lines (%d chars) from [%c]",
1476 				op, lines * cnt, chars * cnt, what_reg());
1477 }
1478 # endif
1479 #endif /* FEATURE_VI_YANKMARK */
1480 
1481 #if ENABLE_FEATURE_VI_UNDO
1482 static void undo_push(char *, unsigned, int);
1483 #endif
1484 
1485 // open a hole in text[]
1486 // might reallocate text[]! use p += text_hole_make(p, ...),
1487 // and be careful to not use pointers into potentially freed text[]!
text_hole_make(char * p,int size)1488 static uintptr_t text_hole_make(char *p, int size)	// at "p", make a 'size' byte hole
1489 {
1490 	uintptr_t bias = 0;
1491 
1492 	if (size <= 0)
1493 		return bias;
1494 	end += size;		// adjust the new END
1495 	if (end >= (text + text_size)) {
1496 		char *new_text;
1497 		text_size += end - (text + text_size) + 10240;
1498 		new_text = xrealloc(text, text_size);
1499 		bias = (new_text - text);
1500 		screenbegin += bias;
1501 		dot         += bias;
1502 		end         += bias;
1503 		p           += bias;
1504 #if ENABLE_FEATURE_VI_YANKMARK
1505 		{
1506 			int i;
1507 			for (i = 0; i < ARRAY_SIZE(mark); i++)
1508 				if (mark[i])
1509 					mark[i] += bias;
1510 		}
1511 #endif
1512 		text = new_text;
1513 	}
1514 	memmove(p + size, p, end - size - p);
1515 	memset(p, ' ', size);	// clear new hole
1516 	return bias;
1517 }
1518 
1519 // close a hole in text[] - delete "p" through "q", inclusive
1520 // "undo" value indicates if this operation should be undo-able
1521 #if !ENABLE_FEATURE_VI_UNDO
1522 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
1523 #endif
text_hole_delete(char * p,char * q,int undo)1524 static char *text_hole_delete(char *p, char *q, int undo)
1525 {
1526 	char *src, *dest;
1527 	int cnt, hole_size;
1528 
1529 	// move forwards, from beginning
1530 	// assume p <= q
1531 	src = q + 1;
1532 	dest = p;
1533 	if (q < p) {		// they are backward- swap them
1534 		src = p + 1;
1535 		dest = q;
1536 	}
1537 	hole_size = q - p + 1;
1538 	cnt = end - src;
1539 #if ENABLE_FEATURE_VI_UNDO
1540 	switch (undo) {
1541 		case NO_UNDO:
1542 			break;
1543 		case ALLOW_UNDO:
1544 			undo_push(p, hole_size, UNDO_DEL);
1545 			break;
1546 		case ALLOW_UNDO_CHAIN:
1547 			undo_push(p, hole_size, UNDO_DEL_CHAIN);
1548 			break;
1549 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1550 		case ALLOW_UNDO_QUEUED:
1551 			undo_push(p, hole_size, UNDO_DEL_QUEUED);
1552 			break;
1553 # endif
1554 	}
1555 	modified_count--;
1556 #endif
1557 	if (src < text || src > end)
1558 		goto thd0;
1559 	if (dest < text || dest >= end)
1560 		goto thd0;
1561 	modified_count++;
1562 	if (src >= end)
1563 		goto thd_atend;	// just delete the end of the buffer
1564 	memmove(dest, src, cnt);
1565  thd_atend:
1566 	end = end - hole_size;	// adjust the new END
1567 	if (dest >= end)
1568 		dest = end - 1;	// make sure dest in below end-1
1569 	if (end <= text)
1570 		dest = end = text;	// keep pointers valid
1571  thd0:
1572 	return dest;
1573 }
1574 
1575 #if ENABLE_FEATURE_VI_UNDO
1576 
1577 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1578 // Flush any queued objects to the undo stack
undo_queue_commit(void)1579 static void undo_queue_commit(void)
1580 {
1581 	// Pushes the queue object onto the undo stack
1582 	if (undo_q > 0) {
1583 		// Deleted character undo events grow from the end
1584 		undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1585 			undo_q,
1586 			(undo_queue_state | UNDO_USE_SPOS)
1587 		);
1588 		undo_queue_state = UNDO_EMPTY;
1589 		undo_q = 0;
1590 	}
1591 }
1592 # else
1593 #  define undo_queue_commit() ((void)0)
1594 # endif
1595 
flush_undo_data(void)1596 static void flush_undo_data(void)
1597 {
1598 	struct undo_object *undo_entry;
1599 
1600 	while (undo_stack_tail) {
1601 		undo_entry = undo_stack_tail;
1602 		undo_stack_tail = undo_entry->prev;
1603 		free(undo_entry);
1604 	}
1605 }
1606 
1607 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1608 // Add to the undo stack
undo_push(char * src,unsigned length,int u_type)1609 static void undo_push(char *src, unsigned length, int u_type)
1610 {
1611 	struct undo_object *undo_entry;
1612 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1613 	int use_spos = u_type & UNDO_USE_SPOS;
1614 # endif
1615 
1616 	// "u_type" values
1617 	// UNDO_INS: insertion, undo will remove from buffer
1618 	// UNDO_DEL: deleted text, undo will restore to buffer
1619 	// UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1620 	// The CHAIN operations are for handling multiple operations that the user
1621 	// performs with a single action, i.e. REPLACE mode or find-and-replace commands
1622 	// UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1623 	// for the INS/DEL operation.
1624 	// UNDO_{INS,DEL} ORed with UNDO_USE_SPOS: commit the undo queue
1625 
1626 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1627 	// This undo queuing functionality groups multiple character typing or backspaces
1628 	// into a single large undo object. This greatly reduces calls to malloc() for
1629 	// single-character operations while typing and has the side benefit of letting
1630 	// an undo operation remove chunks of text rather than a single character.
1631 	switch (u_type) {
1632 	case UNDO_EMPTY:	// Just in case this ever happens...
1633 		return;
1634 	case UNDO_DEL_QUEUED:
1635 		if (length != 1)
1636 			return;	// Only queue single characters
1637 		switch (undo_queue_state) {
1638 		case UNDO_EMPTY:
1639 			undo_queue_state = UNDO_DEL;
1640 		case UNDO_DEL:
1641 			undo_queue_spos = src;
1642 			undo_q++;
1643 			undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1644 			// If queue is full, dump it into an object
1645 			if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1646 				undo_queue_commit();
1647 			return;
1648 		case UNDO_INS:
1649 			// Switch from storing inserted text to deleted text
1650 			undo_queue_commit();
1651 			undo_push(src, length, UNDO_DEL_QUEUED);
1652 			return;
1653 		}
1654 		break;
1655 	case UNDO_INS_QUEUED:
1656 		if (length < 1)
1657 			return;
1658 		switch (undo_queue_state) {
1659 		case UNDO_EMPTY:
1660 			undo_queue_state = UNDO_INS;
1661 			undo_queue_spos = src;
1662 		case UNDO_INS:
1663 			while (length--) {
1664 				undo_q++;	// Don't need to save any data for insertions
1665 				if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1666 					undo_queue_commit();
1667 			}
1668 			return;
1669 		case UNDO_DEL:
1670 			// Switch from storing deleted text to inserted text
1671 			undo_queue_commit();
1672 			undo_push(src, length, UNDO_INS_QUEUED);
1673 			return;
1674 		}
1675 		break;
1676 	}
1677 	u_type &= ~UNDO_USE_SPOS;
1678 # endif
1679 
1680 	// Allocate a new undo object
1681 	if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1682 		// For UNDO_DEL objects, save deleted text
1683 		if ((text + length) == end)
1684 			length--;
1685 		// If this deletion empties text[], strip the newline. When the buffer becomes
1686 		// zero-length, a newline is added back, which requires this to compensate.
1687 		undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1688 		memcpy(undo_entry->undo_text, src, length);
1689 	} else {
1690 		undo_entry = xzalloc(sizeof(*undo_entry));
1691 	}
1692 	undo_entry->length = length;
1693 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1694 	if (use_spos) {
1695 		undo_entry->start = undo_queue_spos - text;	// use start position from queue
1696 	} else {
1697 		undo_entry->start = src - text;	// use offset from start of text buffer
1698 	}
1699 # else
1700 	undo_entry->start = src - text;
1701 # endif
1702 	undo_entry->u_type = u_type;
1703 
1704 	// Push it on undo stack
1705 	undo_entry->prev = undo_stack_tail;
1706 	undo_stack_tail = undo_entry;
1707 	modified_count++;
1708 }
1709 
undo_push_insert(char * p,int len,int undo)1710 static void undo_push_insert(char *p, int len, int undo)
1711 {
1712 	switch (undo) {
1713 	case ALLOW_UNDO:
1714 		undo_push(p, len, UNDO_INS);
1715 		break;
1716 	case ALLOW_UNDO_CHAIN:
1717 		undo_push(p, len, UNDO_INS_CHAIN);
1718 		break;
1719 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1720 	case ALLOW_UNDO_QUEUED:
1721 		undo_push(p, len, UNDO_INS_QUEUED);
1722 		break;
1723 # endif
1724 	}
1725 }
1726 
1727 // Undo the last operation
undo_pop(void)1728 static void undo_pop(void)
1729 {
1730 	int repeat;
1731 	char *u_start, *u_end;
1732 	struct undo_object *undo_entry;
1733 
1734 	// Commit pending undo queue before popping (should be unnecessary)
1735 	undo_queue_commit();
1736 
1737 	undo_entry = undo_stack_tail;
1738 	// Check for an empty undo stack
1739 	if (!undo_entry) {
1740 		status_line("Already at oldest change");
1741 		return;
1742 	}
1743 
1744 	switch (undo_entry->u_type) {
1745 	case UNDO_DEL:
1746 	case UNDO_DEL_CHAIN:
1747 		// make hole and put in text that was deleted; deallocate text
1748 		u_start = text + undo_entry->start;
1749 		text_hole_make(u_start, undo_entry->length);
1750 		memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1751 # if ENABLE_FEATURE_VI_VERBOSE_STATUS
1752 		status_line("Undo [%d] %s %d chars at position %d",
1753 			modified_count, "restored",
1754 			undo_entry->length, undo_entry->start
1755 		);
1756 # endif
1757 		break;
1758 	case UNDO_INS:
1759 	case UNDO_INS_CHAIN:
1760 		// delete what was inserted
1761 		u_start = undo_entry->start + text;
1762 		u_end = u_start - 1 + undo_entry->length;
1763 		text_hole_delete(u_start, u_end, NO_UNDO);
1764 # if ENABLE_FEATURE_VI_VERBOSE_STATUS
1765 		status_line("Undo [%d] %s %d chars at position %d",
1766 			modified_count, "deleted",
1767 			undo_entry->length, undo_entry->start
1768 		);
1769 # endif
1770 		break;
1771 	}
1772 	repeat = 0;
1773 	switch (undo_entry->u_type) {
1774 	// If this is the end of a chain, lower modification count and refresh display
1775 	case UNDO_DEL:
1776 	case UNDO_INS:
1777 		dot = (text + undo_entry->start);
1778 		refresh(FALSE);
1779 		break;
1780 	case UNDO_DEL_CHAIN:
1781 	case UNDO_INS_CHAIN:
1782 		repeat = 1;
1783 		break;
1784 	}
1785 	// Deallocate the undo object we just processed
1786 	undo_stack_tail = undo_entry->prev;
1787 	free(undo_entry);
1788 	modified_count--;
1789 	// For chained operations, continue popping all the way down the chain.
1790 	if (repeat) {
1791 		undo_pop();	// Follow the undo chain if one exists
1792 	}
1793 }
1794 
1795 #else
1796 # define flush_undo_data()   ((void)0)
1797 # define undo_queue_commit() ((void)0)
1798 #endif /* ENABLE_FEATURE_VI_UNDO */
1799 
1800 //----- Dot Movement Routines ----------------------------------
dot_left(void)1801 static void dot_left(void)
1802 {
1803 	undo_queue_commit();
1804 	if (dot > text && dot[-1] != '\n')
1805 		dot--;
1806 }
1807 
dot_right(void)1808 static void dot_right(void)
1809 {
1810 	undo_queue_commit();
1811 	if (dot < end - 1 && *dot != '\n')
1812 		dot++;
1813 }
1814 
dot_begin(void)1815 static void dot_begin(void)
1816 {
1817 	undo_queue_commit();
1818 	dot = begin_line(dot);	// return pointer to first char cur line
1819 }
1820 
dot_end(void)1821 static void dot_end(void)
1822 {
1823 	undo_queue_commit();
1824 	dot = end_line(dot);	// return pointer to last char cur line
1825 }
1826 
move_to_col(char * p,int l)1827 static char *move_to_col(char *p, int l)
1828 {
1829 	int co;
1830 
1831 	p = begin_line(p);
1832 	co = 0;
1833 	do {
1834 		if (*p == '\n') //vda || *p == '\0')
1835 			break;
1836 		co = next_column(*p, co);
1837 	} while (co <= l && p++ < end);
1838 	return p;
1839 }
1840 
dot_next(void)1841 static void dot_next(void)
1842 {
1843 	undo_queue_commit();
1844 	dot = next_line(dot);
1845 }
1846 
dot_prev(void)1847 static void dot_prev(void)
1848 {
1849 	undo_queue_commit();
1850 	dot = prev_line(dot);
1851 }
1852 
dot_skip_over_ws(void)1853 static void dot_skip_over_ws(void)
1854 {
1855 	// skip WS
1856 	while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1857 		dot++;
1858 }
1859 
dot_to_char(int cmd)1860 static void dot_to_char(int cmd)
1861 {
1862 	char *q = dot;
1863 	int dir = islower(cmd) ? FORWARD : BACK;
1864 
1865 	if (last_search_char == 0)
1866 		return;
1867 
1868 	do {
1869 		do {
1870 			q += dir;
1871 			if ((dir == FORWARD ? q > end - 1 : q < text) || *q == '\n') {
1872 				indicate_error();
1873 				return;
1874 			}
1875 		} while (*q != last_search_char);
1876 	} while (--cmdcnt > 0);
1877 
1878 	dot = q;
1879 
1880 	// place cursor before/after char as required
1881 	if (cmd == 't')
1882 		dot_left();
1883 	else if (cmd == 'T')
1884 		dot_right();
1885 }
1886 
dot_scroll(int cnt,int dir)1887 static void dot_scroll(int cnt, int dir)
1888 {
1889 	char *q;
1890 
1891 	undo_queue_commit();
1892 	for (; cnt > 0; cnt--) {
1893 		if (dir < 0) {
1894 			// scroll Backwards
1895 			// ctrl-Y scroll up one line
1896 			screenbegin = prev_line(screenbegin);
1897 		} else {
1898 			// scroll Forwards
1899 			// ctrl-E scroll down one line
1900 			screenbegin = next_line(screenbegin);
1901 		}
1902 	}
1903 	// make sure "dot" stays on the screen so we dont scroll off
1904 	if (dot < screenbegin)
1905 		dot = screenbegin;
1906 	q = end_screen();	// find new bottom line
1907 	if (dot > q)
1908 		dot = begin_line(q);	// is dot is below bottom line?
1909 	dot_skip_over_ws();
1910 }
1911 
bound_dot(char * p)1912 static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
1913 {
1914 	if (p >= end && end > text) {
1915 		p = end - 1;
1916 		indicate_error();
1917 	}
1918 	if (p < text) {
1919 		p = text;
1920 		indicate_error();
1921 	}
1922 	return p;
1923 }
1924 
1925 #if ENABLE_FEATURE_VI_DOT_CMD
start_new_cmd_q(char c)1926 static void start_new_cmd_q(char c)
1927 {
1928 	// get buffer for new cmd
1929 	dotcnt = cmdcnt ?: 1;
1930 	last_modifying_cmd[0] = c;
1931 	lmc_len = 1;
1932 	adding2q = 1;
1933 }
end_cmd_q(void)1934 static void end_cmd_q(void)
1935 {
1936 # if ENABLE_FEATURE_VI_YANKMARK
1937 	YDreg = 26;			// go back to default Yank/Delete reg
1938 # endif
1939 	adding2q = 0;
1940 }
1941 #else
1942 # define end_cmd_q() ((void)0)
1943 #endif /* FEATURE_VI_DOT_CMD */
1944 
1945 // copy text into register, then delete text.
1946 //
1947 #if !ENABLE_FEATURE_VI_UNDO
1948 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1949 #endif
yank_delete(char * start,char * stop,int buftype,int yf,int undo)1950 static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo)
1951 {
1952 	char *p;
1953 
1954 	// make sure start <= stop
1955 	if (start > stop) {
1956 		// they are backwards, reverse them
1957 		p = start;
1958 		start = stop;
1959 		stop = p;
1960 	}
1961 	if (buftype == PARTIAL && *start == '\n')
1962 		return start;
1963 	p = start;
1964 #if ENABLE_FEATURE_VI_YANKMARK
1965 	text_yank(start, stop, YDreg, buftype);
1966 #endif
1967 	if (yf == YANKDEL) {
1968 		p = text_hole_delete(start, stop, undo);
1969 	}					// delete lines
1970 	return p;
1971 }
1972 
1973 // might reallocate text[]!
file_insert(const char * fn,char * p,int initial)1974 static int file_insert(const char *fn, char *p, int initial)
1975 {
1976 	int cnt = -1;
1977 	int fd, size;
1978 	struct stat statbuf;
1979 
1980 	if (p < text)
1981 		p = text;
1982 	if (p > end)
1983 		p = end;
1984 
1985 	fd = open(fn, O_RDONLY);
1986 	if (fd < 0) {
1987 		if (!initial)
1988 			status_line_bold_errno(fn);
1989 		return cnt;
1990 	}
1991 
1992 	// Validate file
1993 	if (fstat(fd, &statbuf) < 0) {
1994 		status_line_bold_errno(fn);
1995 		goto fi;
1996 	}
1997 	if (!S_ISREG(statbuf.st_mode)) {
1998 		status_line_bold("'%s' is not a regular file", fn);
1999 		goto fi;
2000 	}
2001 	size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2002 	p += text_hole_make(p, size);
2003 	cnt = full_read(fd, p, size);
2004 	if (cnt < 0) {
2005 		status_line_bold_errno(fn);
2006 		p = text_hole_delete(p, p + size - 1, NO_UNDO);	// un-do buffer insert
2007 	} else if (cnt < size) {
2008 		// There was a partial read, shrink unused space
2009 		p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2010 		status_line_bold("can't read '%s'", fn);
2011 	}
2012 # if ENABLE_FEATURE_VI_UNDO
2013 	else {
2014 		undo_push_insert(p, size, ALLOW_UNDO);
2015 	}
2016 # endif
2017  fi:
2018 	close(fd);
2019 
2020 #if ENABLE_FEATURE_VI_READONLY
2021 	if (initial
2022 	 && ((access(fn, W_OK) < 0) ||
2023 		// root will always have access()
2024 		// so we check fileperms too
2025 		!(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2026 	    )
2027 	) {
2028 		SET_READONLY_FILE(readonly_mode);
2029 	}
2030 #endif
2031 	return cnt;
2032 }
2033 
2034 // find matching char of pair  ()  []  {}
2035 // will crash if c is not one of these
find_pair(char * p,const char c)2036 static char *find_pair(char *p, const char c)
2037 {
2038 	const char *braces = "()[]{}";
2039 	char match;
2040 	int dir, level;
2041 
2042 	dir = strchr(braces, c) - braces;
2043 	dir ^= 1;
2044 	match = braces[dir];
2045 	dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
2046 
2047 	// look for match, count levels of pairs  (( ))
2048 	level = 1;
2049 	for (;;) {
2050 		p += dir;
2051 		if (p < text || p >= end)
2052 			return NULL;
2053 		if (*p == c)
2054 			level++;	// increase pair levels
2055 		if (*p == match) {
2056 			level--;	// reduce pair level
2057 			if (level == 0)
2058 				return p; // found matching pair
2059 		}
2060 	}
2061 }
2062 
2063 #if ENABLE_FEATURE_VI_SETOPTS
2064 // show the matching char of a pair,  ()  []  {}
showmatching(char * p)2065 static void showmatching(char *p)
2066 {
2067 	char *q, *save_dot;
2068 
2069 	// we found half of a pair
2070 	q = find_pair(p, *p);	// get loc of matching char
2071 	if (q == NULL) {
2072 		indicate_error();	// no matching char
2073 	} else {
2074 		// "q" now points to matching pair
2075 		save_dot = dot;	// remember where we are
2076 		dot = q;		// go to new loc
2077 		refresh(FALSE);	// let the user see it
2078 		mysleep(40);	// give user some time
2079 		dot = save_dot;	// go back to old loc
2080 		refresh(FALSE);
2081 	}
2082 }
2083 #endif /* FEATURE_VI_SETOPTS */
2084 
2085 // might reallocate text[]! use p += stupid_insert(p, ...),
2086 // and be careful to not use pointers into potentially freed text[]!
stupid_insert(char * p,char c)2087 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2088 {
2089 	uintptr_t bias;
2090 	bias = text_hole_make(p, 1);
2091 	p += bias;
2092 	*p = c;
2093 	return bias;
2094 }
2095 
2096 // find number of characters in indent, p must be at beginning of line
indent_len(char * p)2097 static size_t indent_len(char *p)
2098 {
2099 	char *r = p;
2100 
2101 	while (r < (end - 1) && isblank(*r))
2102 		r++;
2103 	return r - p;
2104 }
2105 
2106 #if !ENABLE_FEATURE_VI_UNDO
2107 #define char_insert(a,b,c) char_insert(a,b)
2108 #endif
char_insert(char * p,char c,int undo)2109 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2110 {
2111 #if ENABLE_FEATURE_VI_SETOPTS
2112 	size_t len;
2113 	int col, ntab, nspc;
2114 #endif
2115 	char *bol = begin_line(p);
2116 
2117 	if (c == 22) {		// Is this an ctrl-V?
2118 		p += stupid_insert(p, '^');	// use ^ to indicate literal next
2119 		refresh(FALSE);	// show the ^
2120 		c = get_one_char();
2121 		*p = c;
2122 #if ENABLE_FEATURE_VI_UNDO
2123 		undo_push_insert(p, 1, undo);
2124 #else
2125 		modified_count++;
2126 #endif
2127 		p++;
2128 	} else if (c == 27) {	// Is this an ESC?
2129 		cmd_mode = 0;
2130 		undo_queue_commit();
2131 		cmdcnt = 0;
2132 		end_cmd_q();	// stop adding to q
2133 		last_status_cksum = 0;	// force status update
2134 		if ((dot > text) && (p[-1] != '\n')) {
2135 			p--;
2136 		}
2137 #if ENABLE_FEATURE_VI_SETOPTS
2138 		if (autoindent) {
2139 			len = indent_len(bol);
2140 			if (len && get_column(bol + len) == indentcol && bol[len] == '\n') {
2141 				// remove autoindent from otherwise empty line
2142 				text_hole_delete(bol, bol + len - 1, undo);
2143 				p = bol;
2144 			}
2145 		}
2146 #endif
2147 	} else if (c == 4) {	// ctrl-D reduces indentation
2148 		char *r = bol + indent_len(bol);
2149 		int prev = prev_tabstop(get_column(r));
2150 		while (r > bol && get_column(r) > prev) {
2151 			if (p > bol)
2152 				p--;
2153 			r--;
2154 			r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED);
2155 		}
2156 
2157 #if ENABLE_FEATURE_VI_SETOPTS
2158 		if (autoindent && indentcol && r == end_line(p)) {
2159 			// record changed size of autoindent
2160 			indentcol = get_column(p);
2161 			return p;
2162 		}
2163 #endif
2164 #if ENABLE_FEATURE_VI_SETOPTS
2165 	} else if (c == '\t' && expandtab) {	// expand tab
2166 		col = get_column(p);
2167 		col = next_tabstop(col) - col + 1;
2168 		while (col--) {
2169 # if ENABLE_FEATURE_VI_UNDO
2170 			undo_push_insert(p, 1, undo);
2171 # else
2172 			modified_count++;
2173 # endif
2174 			p += 1 + stupid_insert(p, ' ');
2175 		}
2176 #endif
2177 	} else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
2178 		if (p > text) {
2179 			p--;
2180 			p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED);	// shrink buffer 1 char
2181 		}
2182 	} else {
2183 		// insert a char into text[]
2184 		if (c == 13)
2185 			c = '\n';	// translate \r to \n
2186 #if ENABLE_FEATURE_VI_UNDO
2187 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2188 		if (c == '\n')
2189 			undo_queue_commit();
2190 # endif
2191 		undo_push_insert(p, 1, undo);
2192 #else
2193 		modified_count++;
2194 #endif
2195 		p += 1 + stupid_insert(p, c);	// insert the char
2196 #if ENABLE_FEATURE_VI_SETOPTS
2197 		if (showmatch && strchr(")]}", c) != NULL) {
2198 			showmatching(p - 1);
2199 		}
2200 		if (autoindent && c == '\n') {	// auto indent the new line
2201 			// use indent of current/previous line
2202 			bol = indentcol < 0 ? p : prev_line(p);
2203 			len = indent_len(bol);
2204 			col = get_column(bol + len);
2205 
2206 			if (len && col == indentcol) {
2207 				// previous line was empty except for autoindent
2208 				// move the indent to the current line
2209 				memmove(bol + 1, bol, len);
2210 				*bol = '\n';
2211 				return p;
2212 			}
2213 
2214 			if (indentcol < 0)
2215 				p--;	// open above, indent before newly inserted NL
2216 
2217 			if (len) {
2218 				indentcol = col;
2219 				if (expandtab) {
2220 					ntab = 0;
2221 					nspc = col;
2222 				} else {
2223 					ntab = col / tabstop;
2224 					nspc = col % tabstop;
2225 				}
2226 				p += text_hole_make(p, ntab + nspc);
2227 # if ENABLE_FEATURE_VI_UNDO
2228 				undo_push_insert(p, ntab + nspc, undo);
2229 # endif
2230 				memset(p, '\t', ntab);
2231 				p += ntab;
2232 				memset(p, ' ', nspc);
2233 				return p + nspc;
2234 			}
2235 		}
2236 #endif
2237 	}
2238 #if ENABLE_FEATURE_VI_SETOPTS
2239 	indentcol = 0;
2240 #endif
2241 	return p;
2242 }
2243 
2244 #if ENABLE_FEATURE_VI_COLON_EXPAND
init_filename(char * fn)2245 static void init_filename(char *fn)
2246 {
2247 	char *copy = xstrdup(fn);
2248 
2249 	if (current_filename == NULL) {
2250 		current_filename = copy;
2251 	} else {
2252 		free(alt_filename);
2253 		alt_filename = copy;
2254 	}
2255 }
2256 #else
2257 # define init_filename(f) ((void)(0))
2258 #endif
2259 
update_filename(char * fn)2260 static void update_filename(char *fn)
2261 {
2262 #if ENABLE_FEATURE_VI_COLON_EXPAND
2263 	if (fn == NULL)
2264 		return;
2265 
2266 	if (current_filename == NULL || strcmp(fn, current_filename) != 0) {
2267 		free(alt_filename);
2268 		alt_filename = current_filename;
2269 		current_filename = xstrdup(fn);
2270 	}
2271 #else
2272 	if (fn != current_filename) {
2273 		free(current_filename);
2274 		current_filename = xstrdup(fn);
2275 	}
2276 #endif
2277 }
2278 
2279 // read text from file or create an empty buf
2280 // will also update current_filename
init_text_buffer(char * fn)2281 static int init_text_buffer(char *fn)
2282 {
2283 	int rc;
2284 
2285 	// allocate/reallocate text buffer
2286 	free(text);
2287 	text_size = 10240;
2288 	screenbegin = dot = end = text = xzalloc(text_size);
2289 
2290 	update_filename(fn);
2291 	rc = file_insert(fn, text, 1);
2292 	if (rc < 0) {
2293 		// file doesnt exist. Start empty buf with dummy line
2294 		char_insert(text, '\n', NO_UNDO);
2295 	}
2296 
2297 	flush_undo_data();
2298 	modified_count = 0;
2299 	last_modified_count = -1;
2300 #if ENABLE_FEATURE_VI_YANKMARK
2301 	// init the marks
2302 	memset(mark, 0, sizeof(mark));
2303 #endif
2304 	return rc;
2305 }
2306 
2307 #if ENABLE_FEATURE_VI_YANKMARK \
2308  || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2309  || ENABLE_FEATURE_VI_CRASHME
2310 // might reallocate text[]! use p += string_insert(p, ...),
2311 // and be careful to not use pointers into potentially freed text[]!
2312 # if !ENABLE_FEATURE_VI_UNDO
2313 #  define string_insert(a,b,c) string_insert(a,b)
2314 # endif
string_insert(char * p,const char * s,int undo)2315 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2316 {
2317 	uintptr_t bias;
2318 	int i;
2319 
2320 	i = strlen(s);
2321 #if ENABLE_FEATURE_VI_UNDO
2322 	undo_push_insert(p, i, undo);
2323 #endif
2324 	bias = text_hole_make(p, i);
2325 	p += bias;
2326 	memcpy(p, s, i);
2327 	return bias;
2328 }
2329 #endif
2330 
file_write(char * fn,char * first,char * last)2331 static int file_write(char *fn, char *first, char *last)
2332 {
2333 	int fd, cnt, charcnt;
2334 
2335 	if (fn == 0) {
2336 		status_line_bold("No current filename");
2337 		return -2;
2338 	}
2339 	// By popular request we do not open file with O_TRUNC,
2340 	// but instead ftruncate() it _after_ successful write.
2341 	// Might reduce amount of data lost on power fail etc.
2342 	fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2343 	if (fd < 0)
2344 		return -1;
2345 	cnt = last - first + 1;
2346 	charcnt = full_write(fd, first, cnt);
2347 	ftruncate(fd, charcnt);
2348 	if (charcnt == cnt) {
2349 		// good write
2350 		//modified_count = FALSE;
2351 	} else {
2352 		charcnt = 0;
2353 	}
2354 	close(fd);
2355 	return charcnt;
2356 }
2357 
2358 #if ENABLE_FEATURE_VI_SEARCH
2359 # if ENABLE_FEATURE_VI_REGEX_SEARCH
2360 // search for pattern starting at p
char_search(char * p,const char * pat,int dir_and_range)2361 static char *char_search(char *p, const char *pat, int dir_and_range)
2362 {
2363 	struct re_pattern_buffer preg;
2364 	const char *err;
2365 	char *q;
2366 	int i, size, range, start;
2367 
2368 	re_syntax_options = RE_SYNTAX_POSIX_BASIC & (~RE_DOT_NEWLINE);
2369 	if (ignorecase)
2370 		re_syntax_options |= RE_ICASE;
2371 
2372 	memset(&preg, 0, sizeof(preg));
2373 	err = re_compile_pattern(pat, strlen(pat), &preg);
2374 	preg.not_bol = p != text;
2375 	preg.not_eol = p != end - 1;
2376 	if (err != NULL) {
2377 		status_line_bold("bad search pattern '%s': %s", pat, err);
2378 		return p;
2379 	}
2380 
2381 	range = (dir_and_range & 1);
2382 	q = end - 1; // if FULL
2383 	if (range == LIMITED)
2384 		q = next_line(p);
2385 	if (dir_and_range < 0) { // BACK?
2386 		q = text;
2387 		if (range == LIMITED)
2388 			q = prev_line(p);
2389 	}
2390 
2391 	// RANGE could be negative if we are searching backwards
2392 	range = q - p;
2393 	if (range < 0) {
2394 		size = -range;
2395 		start = size;
2396 	} else {
2397 		size = range;
2398 		start = 0;
2399 	}
2400 	q = p - start;
2401 	if (q < text)
2402 		q = text;
2403 	// search for the compiled pattern, preg, in p[]
2404 	// range < 0, start == size: search backward
2405 	// range > 0, start == 0: search forward
2406 	// re_search() < 0: not found or error
2407 	// re_search() >= 0: index of found pattern
2408 	//           struct pattern   char     int   int    int    struct reg
2409 	// re_search(*pattern_buffer, *string, size, start, range, *regs)
2410 	i = re_search(&preg, q, size, start, range, /*struct re_registers*:*/ NULL);
2411 	regfree(&preg);
2412 	return i < 0 ? NULL : q + i;
2413 }
2414 # else
2415 #  if ENABLE_FEATURE_VI_SETOPTS
mycmp(const char * s1,const char * s2,int len)2416 static int mycmp(const char *s1, const char *s2, int len)
2417 {
2418 	if (ignorecase) {
2419 		return strncasecmp(s1, s2, len);
2420 	}
2421 	return strncmp(s1, s2, len);
2422 }
2423 #  else
2424 #   define mycmp strncmp
2425 #  endif
char_search(char * p,const char * pat,int dir_and_range)2426 static char *char_search(char *p, const char *pat, int dir_and_range)
2427 {
2428 	char *start, *stop;
2429 	int len;
2430 	int range;
2431 
2432 	len = strlen(pat);
2433 	range = (dir_and_range & 1);
2434 	if (dir_and_range > 0) { //FORWARD?
2435 		stop = end - 1;	// assume range is p..end-1
2436 		if (range == LIMITED)
2437 			stop = next_line(p);	// range is to next line
2438 		for (start = p; start < stop; start++) {
2439 			if (mycmp(start, pat, len) == 0) {
2440 				return start;
2441 			}
2442 		}
2443 	} else { //BACK
2444 		stop = text;	// assume range is text..p
2445 		if (range == LIMITED)
2446 			stop = prev_line(p);	// range is to prev line
2447 		for (start = p - len; start >= stop; start--) {
2448 			if (mycmp(start, pat, len) == 0) {
2449 				return start;
2450 			}
2451 		}
2452 	}
2453 	// pattern not found
2454 	return NULL;
2455 }
2456 # endif
2457 #endif /* FEATURE_VI_SEARCH */
2458 
2459 //----- The Colon commands -------------------------------------
2460 #if ENABLE_FEATURE_VI_COLON
2461 // Evaluate colon address expression.  Returns a pointer to the
2462 // next character or NULL on error.  If 'result' contains a valid
2463 // address 'valid' is TRUE.
get_one_address(char * p,int * result,int * valid)2464 static char *get_one_address(char *p, int *result, int *valid)
2465 {
2466 	int num, sign, addr, got_addr;
2467 # if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH
2468 	char *q, c;
2469 # endif
2470 	IF_FEATURE_VI_SEARCH(int dir;)
2471 
2472 	got_addr = FALSE;
2473 	addr = count_lines(text, dot);	// default to current line
2474 	sign = 0;
2475 	for (;;) {
2476 		if (isblank(*p)) {
2477 			if (got_addr) {
2478 				addr += sign;
2479 				sign = 0;
2480 			}
2481 			p++;
2482 		} else if (!got_addr && *p == '.') {	// the current line
2483 			p++;
2484 			//addr = count_lines(text, dot);
2485 			got_addr = TRUE;
2486 		} else if (!got_addr && *p == '$') {	// the last line in file
2487 			p++;
2488 			addr = count_lines(text, end - 1);
2489 			got_addr = TRUE;
2490 		}
2491 # if ENABLE_FEATURE_VI_YANKMARK
2492 		else if (!got_addr && *p == '\'') {	// is this a mark addr
2493 			p++;
2494 			c = tolower(*p);
2495 			p++;
2496 			q = NULL;
2497 			if (c >= 'a' && c <= 'z') {
2498 				// we have a mark
2499 				c = c - 'a';
2500 				q = mark[(unsigned char) c];
2501 			}
2502 			if (q == NULL) {	// is mark valid
2503 				status_line_bold("Mark not set");
2504 				return NULL;
2505 			}
2506 			addr = count_lines(text, q);
2507 			got_addr = TRUE;
2508 		}
2509 # endif
2510 # if ENABLE_FEATURE_VI_SEARCH
2511 		else if (!got_addr && (*p == '/' || *p == '?')) {	// a search pattern
2512 			c = *p;
2513 			q = strchrnul(p + 1, c);
2514 			if (p + 1 != q) {
2515 				// save copy of new pattern
2516 				free(last_search_pattern);
2517 				last_search_pattern = xstrndup(p, q - p);
2518 			}
2519 			p = q;
2520 			if (*p == c)
2521 				p++;
2522 			if (c == '/') {
2523 				q = next_line(dot);
2524 				dir = (FORWARD << 1) | FULL;
2525 			} else {
2526 				q = begin_line(dot);
2527 				dir = ((unsigned)BACK << 1) | FULL;
2528 			}
2529 			q = char_search(q, last_search_pattern + 1, dir);
2530 			if (q == NULL) {
2531 				// no match, continue from other end of file
2532 				q = char_search(dir > 0 ? text : end - 1,
2533 								last_search_pattern + 1, dir);
2534 				if (q == NULL) {
2535 					status_line_bold("Pattern not found");
2536 					return NULL;
2537 				}
2538 			}
2539 			addr = count_lines(text, q);
2540 			got_addr = TRUE;
2541 		}
2542 # endif
2543 		else if (isdigit(*p)) {
2544 			num = 0;
2545 			while (isdigit(*p))
2546 				num = num * 10 + *p++ -'0';
2547 			if (!got_addr) {	// specific line number
2548 				addr = num;
2549 				got_addr = TRUE;
2550 			} else {	// offset from current addr
2551 				addr += sign >= 0 ? num : -num;
2552 			}
2553 			sign = 0;
2554 		} else if (*p == '-' || *p == '+') {
2555 			if (!got_addr) {	// default address is dot
2556 				//addr = count_lines(text, dot);
2557 				got_addr = TRUE;
2558 			} else {
2559 				addr += sign;
2560 			}
2561 			sign = *p++ == '-' ? -1 : 1;
2562 		} else {
2563 			addr += sign;	// consume unused trailing sign
2564 			break;
2565 		}
2566 	}
2567 	*result = addr;
2568 	*valid = got_addr;
2569 	return p;
2570 }
2571 
2572 # define GET_ADDRESS   0
2573 # define GET_SEPARATOR 1
2574 
2575 // Read line addresses for a colon command.  The user can enter as
2576 // many as they like but only the last two will be used.
get_address(char * p,int * b,int * e,unsigned int * got)2577 static char *get_address(char *p, int *b, int *e, unsigned int *got)
2578 {
2579 	int state = GET_ADDRESS;
2580 	int valid;
2581 	int addr;
2582 	char *save_dot = dot;
2583 
2584 	//----- get the address' i.e., 1,3   'a,'b  -----
2585 	for (;;) {
2586 		if (isblank(*p)) {
2587 			p++;
2588 		} else if (state == GET_ADDRESS && *p == '%') {	// alias for 1,$
2589 			p++;
2590 			*b = 1;
2591 			*e = count_lines(text, end-1);
2592 			*got = 3;
2593 			state = GET_SEPARATOR;
2594 		} else if (state == GET_ADDRESS) {
2595 			valid = FALSE;
2596 			p = get_one_address(p, &addr, &valid);
2597 			// Quit on error or if the address is invalid and isn't of
2598 			// the form ',$' or '1,' (in which case it defaults to dot).
2599 			if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1))
2600 				break;
2601 			*b = *e;
2602 			*e = addr;
2603 			*got = (*got << 1) | 1;
2604 			state = GET_SEPARATOR;
2605 		} else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) {
2606 			if (*p == ';')
2607 				dot = find_line(*e);
2608 			p++;
2609 			state = GET_ADDRESS;
2610 		} else {
2611 			break;
2612 		}
2613 	}
2614 	dot = save_dot;
2615 	return p;
2616 }
2617 
2618 # if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
setops(char * args,int flg_no)2619 static void setops(char *args, int flg_no)
2620 {
2621 	char *eq;
2622 	int index;
2623 
2624 	eq = strchr(args, '=');
2625 	if (eq) *eq = '\0';
2626 	index = index_in_strings(OPTS_STR, args + flg_no);
2627 	if (eq) *eq = '=';
2628 	if (index < 0) {
2629  bad:
2630 		status_line_bold("bad option: %s", args);
2631 		return;
2632 	}
2633 
2634 	index = 1 << (index >> 1); // convert to VI_bit
2635 
2636 	if (index & VI_TABSTOP) {
2637 		int t;
2638 		if (!eq || flg_no) // no "=NNN" or it is "notabstop"?
2639 			goto bad;
2640 		t = bb_strtou(eq + 1, NULL, 10);
2641 		if (t <= 0 || t > MAX_TABSTOP)
2642 			goto bad;
2643 		tabstop = t;
2644 		return;
2645 	}
2646 	if (eq)	goto bad; // boolean option has "="?
2647 	if (flg_no) {
2648 		vi_setops &= ~index;
2649 	} else {
2650 		vi_setops |= index;
2651 	}
2652 }
2653 # endif
2654 
2655 # if ENABLE_FEATURE_VI_COLON_EXPAND
expand_args(char * args)2656 static char *expand_args(char *args)
2657 {
2658 	char *s, *t;
2659 	const char *replace;
2660 
2661 	args = xstrdup(args);
2662 	for (s = args; *s; s++) {
2663 		if (*s == '%') {
2664 			replace = current_filename;
2665 		} else if (*s == '#') {
2666 			replace = alt_filename;
2667 		} else {
2668 			if (*s == '\\' && s[1] != '\0') {
2669 				for (t = s++; *t; t++)
2670 					*t = t[1];
2671 			}
2672 			continue;
2673 		}
2674 
2675 		if (replace == NULL) {
2676 			free(args);
2677 			status_line_bold("No previous filename");
2678 			return NULL;
2679 		}
2680 
2681 		*s = '\0';
2682 		t = xasprintf("%s%s%s", args, replace, s+1);
2683 		s = t + (s - args) + strlen(replace);
2684 		free(args);
2685 		args = t;
2686 	}
2687 	return args;
2688 }
2689 # else
2690 #  define expand_args(a) (a)
2691 # endif
2692 #endif /* FEATURE_VI_COLON */
2693 
2694 #if ENABLE_FEATURE_VI_REGEX_SEARCH
2695 # define MAX_SUBPATTERN 10	// subpatterns \0 .. \9
2696 
2697 // Like strchr() but skipping backslash-escaped characters
strchr_backslash(const char * s,int c)2698 static char *strchr_backslash(const char *s, int c)
2699 {
2700 	while (*s) {
2701 		if (*s == c)
2702 			return (char *)s;
2703 		if (*s == '\\')
2704 			if (*++s == '\0')
2705 				break;
2706 		s++;
2707 	}
2708 	return NULL;
2709 }
2710 
2711 // If the return value is not NULL the caller should free R
regex_search(char * q,regex_t * preg,const char * Rorig,size_t * len_F,size_t * len_R,char ** R)2712 static char *regex_search(char *q, regex_t *preg, const char *Rorig,
2713 				size_t *len_F, size_t *len_R, char **R)
2714 {
2715 	regmatch_t regmatch[MAX_SUBPATTERN], *cur_match;
2716 	char *found = NULL;
2717 	const char *t;
2718 	char *r;
2719 
2720 	regmatch[0].rm_so = 0;
2721 	regmatch[0].rm_eo = end_line(q) - q;
2722 	if (regexec(preg, q, MAX_SUBPATTERN, regmatch, REG_STARTEND) != 0)
2723 		return found;
2724 
2725 	found = q + regmatch[0].rm_so;
2726 	*len_F = regmatch[0].rm_eo - regmatch[0].rm_so;
2727 	*R = NULL;
2728 
2729  fill_result:
2730 	// first pass calculates len_R, second fills R
2731 	*len_R = 0;
2732 	for (t = Rorig, r = *R; *t; t++) {
2733 		size_t len = 1;	// default is to copy one char from replace pattern
2734 		const char *from = t;
2735 		if (*t == '\\') {
2736 			from = ++t;	// skip backslash
2737 			if (*t >= '0' && *t < '0' + MAX_SUBPATTERN) {
2738 				cur_match = regmatch + (*t - '0');
2739 				if (cur_match->rm_so >= 0) {
2740 					len = cur_match->rm_eo - cur_match->rm_so;
2741 					from = q + cur_match->rm_so;
2742 				}
2743 			}
2744 		}
2745 		*len_R += len;
2746 		if (*R) {
2747 			memcpy(r, from, len);
2748 			r += len;
2749 			/* *r = '\0'; - xzalloc did it */
2750 		}
2751 	}
2752 	if (*R == NULL) {
2753 		*R = xzalloc(*len_R + 1);
2754 		goto fill_result;
2755 	}
2756 
2757 	return found;
2758 }
2759 #else /* !ENABLE_FEATURE_VI_REGEX_SEARCH */
2760 # define strchr_backslash(s, c) strchr(s, c)
2761 #endif /* ENABLE_FEATURE_VI_REGEX_SEARCH */
2762 
2763 // buf must be no longer than MAX_INPUT_LEN!
colon(char * buf)2764 static void colon(char *buf)
2765 {
2766 #if !ENABLE_FEATURE_VI_COLON
2767 	// Simple ":cmd" handler with minimal set of commands
2768 	char *p = buf;
2769 	int cnt;
2770 
2771 	if (*p == ':')
2772 		p++;
2773 	cnt = strlen(p);
2774 	if (cnt == 0)
2775 		return;
2776 	if (strncmp(p, "quit", cnt) == 0
2777 	 || strcmp(p, "q!") == 0
2778 	) {
2779 		if (modified_count && p[1] != '!') {
2780 			status_line_bold("No write since last change (:%s! overrides)", p);
2781 		} else {
2782 			editing = 0;
2783 		}
2784 		return;
2785 	}
2786 	if (strncmp(p, "write", cnt) == 0
2787 	 || strcmp(p, "wq") == 0
2788 	 || strcmp(p, "wn") == 0
2789 	 || (p[0] == 'x' && !p[1])
2790 	) {
2791 		if (modified_count != 0 || p[0] != 'x') {
2792 			cnt = file_write(current_filename, text, end - 1);
2793 		}
2794 		if (cnt < 0) {
2795 			if (cnt == -1)
2796 				status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2797 		} else {
2798 			modified_count = 0;
2799 			last_modified_count = -1;
2800 			status_line("'%s' %uL, %uC",
2801 				current_filename,
2802 				count_lines(text, end - 1), cnt
2803 			);
2804 			if (p[0] == 'x'
2805 			 || p[1] == 'q' || p[1] == 'n'
2806 			) {
2807 				editing = 0;
2808 			}
2809 		}
2810 		return;
2811 	}
2812 	if (strncmp(p, "file", cnt) == 0) {
2813 		last_status_cksum = 0;	// force status update
2814 		return;
2815 	}
2816 	if (sscanf(p, "%d", &cnt) > 0) {
2817 		dot = find_line(cnt);
2818 		dot_skip_over_ws();
2819 		return;
2820 	}
2821 	not_implemented(p);
2822 #else
2823 
2824 // check how many addresses we got
2825 # define GOT_ADDRESS (got & 1)
2826 # define GOT_RANGE ((got & 3) == 3)
2827 
2828 	char c, *buf1, *q, *r;
2829 	char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL;
2830 	int i, l, li, b, e;
2831 	unsigned int got;
2832 	int useforce;
2833 
2834 	// :3154	// if (-e line 3154) goto it  else stay put
2835 	// :4,33w! foo	// write a portion of buffer to file "foo"
2836 	// :w		// write all of buffer to current file
2837 	// :q		// quit
2838 	// :q!		// quit- dont care about modified file
2839 	// :'a,'z!sort -u   // filter block through sort
2840 	// :'f		// goto mark "f"
2841 	// :'fl		// list literal the mark "f" line
2842 	// :.r bar	// read file "bar" into buffer before dot
2843 	// :/123/,/abc/d    // delete lines from "123" line to "abc" line
2844 	// :/xyz/	// goto the "xyz" line
2845 	// :s/find/replace/ // substitute pattern "find" with "replace"
2846 	// :!<cmd>	// run <cmd> then return
2847 	//
2848 
2849 	while (*buf == ':')
2850 		buf++;			// move past leading colons
2851 	while (isblank(*buf))
2852 		buf++;			// move past leading blanks
2853 	if (!buf[0] || buf[0] == '"')
2854 		goto ret;		// ignore empty lines or those starting with '"'
2855 
2856 	li = i = 0;
2857 	b = e = -1;
2858 	got = 0;
2859 	li = count_lines(text, end - 1);
2860 	fn = current_filename;
2861 
2862 	// look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
2863 	buf = get_address(buf, &b, &e, &got);
2864 	if (buf == NULL) {
2865 		goto ret;
2866 	}
2867 
2868 	// get the COMMAND into cmd[]
2869 	strcpy(cmd, buf);
2870 	buf1 = cmd;
2871 	while (!isspace(*buf1) && *buf1 != '\0') {
2872 		buf1++;
2873 	}
2874 	cmdend = buf1;
2875 	// get any ARGuments
2876 	while (isblank(*buf1))
2877 		buf1++;
2878 	args = buf1;
2879 	*cmdend = '\0';
2880 	useforce = FALSE;
2881 	if (cmdend > cmd && cmdend[-1] == '!') {
2882 		useforce = TRUE;
2883 		cmdend[-1] = '\0';   // get rid of !
2884 	}
2885 	// assume the command will want a range, certain commands
2886 	// (read, substitute) need to adjust these assumptions
2887 	if (!GOT_ADDRESS) {
2888 		q = text;			// no addr, use 1,$ for the range
2889 		r = end - 1;
2890 	} else {
2891 		// at least one addr was given, get its details
2892 		if (e < 0 || e > li) {
2893 			status_line_bold("Invalid range");
2894 			goto ret;
2895 		}
2896 		q = r = find_line(e);
2897 		if (!GOT_RANGE) {
2898 			// if there is only one addr, then it's the line
2899 			// number of the single line the user wants.
2900 			// Reset the end pointer to the end of that line.
2901 			r = end_line(q);
2902 			li = 1;
2903 		} else {
2904 			// we were given two addrs.  change the
2905 			// start pointer to the addr given by user.
2906 			if (b < 0 || b > li || b > e) {
2907 				status_line_bold("Invalid range");
2908 				goto ret;
2909 			}
2910 			q = find_line(b);	// what line is #b
2911 			r = end_line(r);
2912 			li = e - b + 1;
2913 		}
2914 	}
2915 	// ------------ now look for the command ------------
2916 	i = strlen(cmd);
2917 	if (i == 0) {		// :123CR goto line #123
2918 		if (e >= 0) {
2919 			dot = find_line(e);	// what line is #e
2920 			dot_skip_over_ws();
2921 		}
2922 	}
2923 # if ENABLE_FEATURE_ALLOW_EXEC
2924 	else if (cmd[0] == '!') {	// run a cmd
2925 		int retcode;
2926 		// :!ls   run the <cmd>
2927 		exp = expand_args(buf + 1);
2928 		if (exp == NULL)
2929 			goto ret;
2930 		go_bottom_and_clear_to_eol();
2931 		cookmode();
2932 		retcode = system(exp);	// run the cmd
2933 		if (retcode)
2934 			printf("\nshell returned %i\n\n", retcode);
2935 		rawmode();
2936 		Hit_Return();			// let user see results
2937 	}
2938 # endif
2939 	else if (cmd[0] == '=' && !cmd[1]) {	// where is the address
2940 		if (!GOT_ADDRESS) {	// no addr given- use defaults
2941 			e = count_lines(text, dot);
2942 		}
2943 		status_line("%d", e);
2944 	} else if (strncmp(cmd, "delete", i) == 0) {	// delete lines
2945 		if (!GOT_ADDRESS) {	// no addr given- use defaults
2946 			q = begin_line(dot);	// assume .,. for the range
2947 			r = end_line(dot);
2948 		}
2949 		dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO);	// save, then delete lines
2950 		dot_skip_over_ws();
2951 	} else if (strncmp(cmd, "edit", i) == 0) {	// Edit a file
2952 		int size;
2953 
2954 		// don't edit, if the current file has been modified
2955 		if (modified_count && !useforce) {
2956 			status_line_bold("No write since last change (:%s! overrides)", cmd);
2957 			goto ret;
2958 		}
2959 		if (args[0]) {
2960 			// the user supplied a file name
2961 			fn = exp = expand_args(args);
2962 			if (exp == NULL)
2963 				goto ret;
2964 		} else if (current_filename == NULL) {
2965 			// no user file name, no current name- punt
2966 			status_line_bold("No current filename");
2967 			goto ret;
2968 		}
2969 
2970 		size = init_text_buffer(fn);
2971 
2972 # if ENABLE_FEATURE_VI_YANKMARK
2973 		if (Ureg >= 0 && Ureg < 28) {
2974 			free(reg[Ureg]);	//   free orig line reg- for 'U'
2975 			reg[Ureg] = NULL;
2976 		}
2977 		/*if (YDreg < 28) - always true*/ {
2978 			free(reg[YDreg]);	//   free default yank/delete register
2979 			reg[YDreg] = NULL;
2980 		}
2981 # endif
2982 		// how many lines in text[]?
2983 		li = count_lines(text, end - 1);
2984 		status_line("'%s'%s"
2985 			IF_FEATURE_VI_READONLY("%s")
2986 			" %uL, %uC",
2987 			fn,
2988 			(size < 0 ? " [New file]" : ""),
2989 			IF_FEATURE_VI_READONLY(
2990 				((readonly_mode) ? " [Readonly]" : ""),
2991 			)
2992 			li, (int)(end - text)
2993 		);
2994 	} else if (strncmp(cmd, "file", i) == 0) {	// what File is this
2995 		if (e >= 0) {
2996 			status_line_bold("No address allowed on this command");
2997 			goto ret;
2998 		}
2999 		if (args[0]) {
3000 			// user wants a new filename
3001 			exp = expand_args(args);
3002 			if (exp == NULL)
3003 				goto ret;
3004 			update_filename(exp);
3005 		} else {
3006 			// user wants file status info
3007 			last_status_cksum = 0;	// force status update
3008 		}
3009 	} else if (strncmp(cmd, "features", i) == 0) {	// what features are available
3010 		// print out values of all features
3011 		go_bottom_and_clear_to_eol();
3012 		cookmode();
3013 		show_help();
3014 		rawmode();
3015 		Hit_Return();
3016 	} else if (strncmp(cmd, "list", i) == 0) {	// literal print line
3017 		if (!GOT_ADDRESS) {	// no addr given- use defaults
3018 			q = begin_line(dot);	// assume .,. for the range
3019 			r = end_line(dot);
3020 		}
3021 		go_bottom_and_clear_to_eol();
3022 		puts("\r");
3023 		for (; q <= r; q++) {
3024 			int c_is_no_print;
3025 
3026 			c = *q;
3027 			c_is_no_print = (c & 0x80) && !Isprint(c);
3028 			if (c_is_no_print) {
3029 				c = '.';
3030 				standout_start();
3031 			}
3032 			if (c == '\n') {
3033 				write1("$\r");
3034 			} else if (c < ' ' || c == 127) {
3035 				bb_putchar('^');
3036 				if (c == 127)
3037 					c = '?';
3038 				else
3039 					c += '@';
3040 			}
3041 			bb_putchar(c);
3042 			if (c_is_no_print)
3043 				standout_end();
3044 		}
3045 		Hit_Return();
3046 	} else if (strncmp(cmd, "quit", i) == 0 // quit
3047 	        || strncmp(cmd, "next", i) == 0 // edit next file
3048 	        || strncmp(cmd, "prev", i) == 0 // edit previous file
3049 	) {
3050 		int n;
3051 		if (useforce) {
3052 			if (*cmd == 'q') {
3053 				// force end of argv list
3054 				optind = cmdline_filecnt;
3055 			}
3056 			editing = 0;
3057 			goto ret;
3058 		}
3059 		// don't exit if the file been modified
3060 		if (modified_count) {
3061 			status_line_bold("No write since last change (:%s! overrides)", cmd);
3062 			goto ret;
3063 		}
3064 		// are there other file to edit
3065 		n = cmdline_filecnt - optind - 1;
3066 		if (*cmd == 'q' && n > 0) {
3067 			status_line_bold("%u more file(s) to edit", n);
3068 			goto ret;
3069 		}
3070 		if (*cmd == 'n' && n <= 0) {
3071 			status_line_bold("No more files to edit");
3072 			goto ret;
3073 		}
3074 		if (*cmd == 'p') {
3075 			// are there previous files to edit
3076 			if (optind < 1) {
3077 				status_line_bold("No previous files to edit");
3078 				goto ret;
3079 			}
3080 			optind -= 2;
3081 		}
3082 		editing = 0;
3083 	} else if (strncmp(cmd, "read", i) == 0) {	// read file into text[]
3084 		int size, num;
3085 
3086 		if (args[0]) {
3087 			// the user supplied a file name
3088 			fn = exp = expand_args(args);
3089 			if (exp == NULL)
3090 				goto ret;
3091 			init_filename(fn);
3092 		} else if (current_filename == NULL) {
3093 			// no user file name, no current name- punt
3094 			status_line_bold("No current filename");
3095 			goto ret;
3096 		}
3097 		if (e == 0) {	// user said ":0r foo"
3098 			q = text;
3099 		} else {	// read after given line or current line if none given
3100 			q = next_line(GOT_ADDRESS ? find_line(e) : dot);
3101 			// read after last line
3102 			if (q == end-1)
3103 				++q;
3104 		}
3105 		num = count_lines(text, q);
3106 		if (q == end)
3107 			num++;
3108 		{ // dance around potentially-reallocated text[]
3109 			uintptr_t ofs = q - text;
3110 			size = file_insert(fn, q, 0);
3111 			q = text + ofs;
3112 		}
3113 		if (size < 0)
3114 			goto ret;	// nothing was inserted
3115 		// how many lines in text[]?
3116 		li = count_lines(q, q + size - 1);
3117 		status_line("'%s'"
3118 			IF_FEATURE_VI_READONLY("%s")
3119 			" %uL, %uC",
3120 			fn,
3121 			IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
3122 			li, size
3123 		);
3124 		dot = find_line(num);
3125 	} else if (strncmp(cmd, "rewind", i) == 0) {	// rewind cmd line args
3126 		if (modified_count && !useforce) {
3127 			status_line_bold("No write since last change (:%s! overrides)", cmd);
3128 		} else {
3129 			// reset the filenames to edit
3130 			optind = -1; // start from 0th file
3131 			editing = 0;
3132 		}
3133 # if ENABLE_FEATURE_VI_SET
3134 	} else if (strncmp(cmd, "set", i) == 0) {	// set or clear features
3135 #  if ENABLE_FEATURE_VI_SETOPTS
3136 		char *argp, *argn, oldch;
3137 #  endif
3138 		// only blank is regarded as args delimiter. What about tab '\t'?
3139 		if (!args[0] || strcmp(args, "all") == 0) {
3140 			// print out values of all options
3141 #  if ENABLE_FEATURE_VI_SETOPTS
3142 			status_line_bold(
3143 				"%sautoindent "
3144 				"%sexpandtab "
3145 				"%sflash "
3146 				"%signorecase "
3147 				"%sshowmatch "
3148 				"tabstop=%u",
3149 				autoindent ? "" : "no",
3150 				expandtab ? "" : "no",
3151 				err_method ? "" : "no",
3152 				ignorecase ? "" : "no",
3153 				showmatch ? "" : "no",
3154 				tabstop
3155 			);
3156 #  endif
3157 			goto ret;
3158 		}
3159 #  if ENABLE_FEATURE_VI_SETOPTS
3160 		argp = args;
3161 		while (*argp) {
3162 			i = 0;
3163 			if (argp[0] == 'n' && argp[1] == 'o') // "noXXX"
3164 				i = 2;
3165 			argn = skip_non_whitespace(argp);
3166 			oldch = *argn;
3167 			*argn = '\0';
3168 			setops(argp, i);
3169 			*argn = oldch;
3170 			argp = skip_whitespace(argn);
3171 		}
3172 #  endif /* FEATURE_VI_SETOPTS */
3173 # endif /* FEATURE_VI_SET */
3174 
3175 # if ENABLE_FEATURE_VI_SEARCH
3176 	} else if (cmd[0] == 's') {	// substitute a pattern with a replacement pattern
3177 		char *F, *R, *flags;
3178 		size_t len_F, len_R;
3179 		int gflag = 0;		// global replace flag
3180 		int subs = 0;	// number of substitutions
3181 #  if ENABLE_FEATURE_VI_VERBOSE_STATUS
3182 		int last_line = 0, lines = 0;
3183 #  endif
3184 #  if ENABLE_FEATURE_VI_REGEX_SEARCH
3185 		regex_t preg;
3186 		int cflags;
3187 		char *Rorig;
3188 #   if ENABLE_FEATURE_VI_UNDO
3189 		int undo = 0;
3190 #   endif
3191 #  endif
3192 
3193 		// F points to the "find" pattern
3194 		// R points to the "replace" pattern
3195 		// replace the cmd line delimiters "/" with NULs
3196 		c = buf[1];	// what is the delimiter
3197 		F = buf + 2;	// start of "find"
3198 		R = strchr_backslash(F, c);	// middle delimiter
3199 		if (!R)
3200 			goto colon_s_fail;
3201 		len_F = R - F;
3202 		*R++ = '\0';	// terminate "find"
3203 		flags = strchr_backslash(R, c);
3204 		if (flags) {
3205 			*flags++ = '\0';	// terminate "replace"
3206 			gflag = *flags;
3207 		}
3208 
3209 		if (len_F) {	// save "find" as last search pattern
3210 			free(last_search_pattern);
3211 			last_search_pattern = xstrdup(F - 1);
3212 			last_search_pattern[0] = '/';
3213 		} else if (last_search_pattern[1] == '\0') {
3214 			status_line_bold("No previous search");
3215 			goto ret;
3216 		} else {
3217 			F = last_search_pattern + 1;
3218 			len_F = strlen(F);
3219 		}
3220 
3221 		if (!GOT_ADDRESS) {	// no addr given
3222 			q = begin_line(dot);      // start with cur line
3223 			r = end_line(dot);
3224 			b = e = count_lines(text, q); // cur line number
3225 		} else if (!GOT_RANGE) {	// one addr given
3226 			b = e;
3227 		}
3228 
3229 #  if ENABLE_FEATURE_VI_REGEX_SEARCH
3230 		Rorig = R;
3231 		cflags = 0;
3232 		if (ignorecase)
3233 			cflags = REG_ICASE;
3234 		memset(&preg, 0, sizeof(preg));
3235 		if (regcomp(&preg, F, cflags) != 0) {
3236 			status_line(":s bad search pattern");
3237 			goto regex_search_end;
3238 		}
3239 #  else
3240 		len_R = strlen(R);
3241 #  endif
3242 
3243 		for (i = b; i <= e; i++) {	// so, :20,23 s \0 find \0 replace \0
3244 			char *ls = q;		// orig line start
3245 			char *found;
3246  vc4:
3247 #  if ENABLE_FEATURE_VI_REGEX_SEARCH
3248 			found = regex_search(q, &preg, Rorig, &len_F, &len_R, &R);
3249 #  else
3250 			found = char_search(q, F, (FORWARD << 1) | LIMITED);	// search cur line only for "find"
3251 #  endif
3252 			if (found) {
3253 				uintptr_t bias;
3254 				// we found the "find" pattern - delete it
3255 				// For undo support, the first item should not be chained
3256 				// This needs to be handled differently depending on
3257 				// whether or not regex support is enabled.
3258 #  if ENABLE_FEATURE_VI_REGEX_SEARCH
3259 #   define TEST_LEN_F len_F	// len_F may be zero
3260 #   define TEST_UNDO1 undo++
3261 #   define TEST_UNDO2 undo++
3262 #  else
3263 #   define TEST_LEN_F 1		// len_F is never zero
3264 #   define TEST_UNDO1 subs
3265 #   define TEST_UNDO2 1
3266 #  endif
3267 				if (TEST_LEN_F)	// match can be empty, no delete needed
3268 					text_hole_delete(found, found + len_F - 1,
3269 								TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
3270 				if (len_R != 0) {	// insert the "replace" pattern, if required
3271 					bias = string_insert(found, R,
3272 								TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
3273 					found += bias;
3274 					ls += bias;
3275 					//q += bias; - recalculated anyway
3276 				}
3277 #  if ENABLE_FEATURE_VI_REGEX_SEARCH
3278 				free(R);
3279 #  endif
3280 				if (TEST_LEN_F || len_R != 0) {
3281 					dot = ls;
3282 					subs++;
3283 #  if ENABLE_FEATURE_VI_VERBOSE_STATUS
3284 					if (last_line != i) {
3285 						last_line = i;
3286 						++lines;
3287 					}
3288 #  endif
3289 				}
3290 				// check for "global"  :s/foo/bar/g
3291 				if (gflag == 'g') {
3292 					if ((found + len_R) < end_line(ls)) {
3293 						q = found + len_R;
3294 						goto vc4;	// don't let q move past cur line
3295 					}
3296 				}
3297 			}
3298 			q = next_line(ls);
3299 		}
3300 		if (subs == 0) {
3301 			status_line_bold("No match");
3302 		} else {
3303 			dot_skip_over_ws();
3304 #  if ENABLE_FEATURE_VI_VERBOSE_STATUS
3305 			if (subs > 1)
3306 				status_line("%d substitutions on %d lines", subs, lines);
3307 #  endif
3308 		}
3309 #  if ENABLE_FEATURE_VI_REGEX_SEARCH
3310  regex_search_end:
3311 		regfree(&preg);
3312 #  endif
3313 # endif /* FEATURE_VI_SEARCH */
3314 	} else if (strncmp(cmd, "version", i) == 0) {  // show software version
3315 		status_line(BB_VER);
3316 	} else if (strncmp(cmd, "write", i) == 0  // write text to file
3317 	        || strcmp(cmd, "wq") == 0
3318 	        || strcmp(cmd, "wn") == 0
3319 	        || (cmd[0] == 'x' && !cmd[1])
3320 	) {
3321 		int size;
3322 		//int forced = FALSE;
3323 
3324 		// is there a file name to write to?
3325 		if (args[0]) {
3326 			struct stat statbuf;
3327 
3328 			exp = expand_args(args);
3329 			if (exp == NULL)
3330 				goto ret;
3331 			if (!useforce && (fn == NULL || strcmp(fn, exp) != 0) &&
3332 					stat(exp, &statbuf) == 0) {
3333 				status_line_bold("File exists (:w! overrides)");
3334 				goto ret;
3335 			}
3336 			fn = exp;
3337 			init_filename(fn);
3338 		}
3339 # if ENABLE_FEATURE_VI_READONLY
3340 		else if (readonly_mode && !useforce && fn) {
3341 			status_line_bold("'%s' is read only", fn);
3342 			goto ret;
3343 		}
3344 # endif
3345 		//if (useforce) {
3346 			// if "fn" is not write-able, chmod u+w
3347 			// sprintf(syscmd, "chmod u+w %s", fn);
3348 			// system(syscmd);
3349 			// forced = TRUE;
3350 		//}
3351 		if (modified_count != 0 || cmd[0] != 'x') {
3352 			size = r - q + 1;
3353 			l = file_write(fn, q, r);
3354 		} else {
3355 			size = 0;
3356 			l = 0;
3357 		}
3358 		//if (useforce && forced) {
3359 			// chmod u-w
3360 			// sprintf(syscmd, "chmod u-w %s", fn);
3361 			// system(syscmd);
3362 			// forced = FALSE;
3363 		//}
3364 		if (l < 0) {
3365 			if (l == -1)
3366 				status_line_bold_errno(fn);
3367 		} else {
3368 			// how many lines written
3369 			li = count_lines(q, q + l - 1);
3370 			status_line("'%s' %uL, %uC", fn, li, l);
3371 			if (l == size) {
3372 				if (q == text && q + l == end) {
3373 					modified_count = 0;
3374 					last_modified_count = -1;
3375 				}
3376 				if (cmd[1] == 'n') {
3377 					editing = 0;
3378 				} else if (cmd[0] == 'x' || cmd[1] == 'q') {
3379 					// are there other files to edit?
3380 					int n = cmdline_filecnt - optind - 1;
3381 					if (n > 0) {
3382 						if (useforce) {
3383 							// force end of argv list
3384 							optind = cmdline_filecnt;
3385 						} else {
3386 							status_line_bold("%u more file(s) to edit", n);
3387 							goto ret;
3388 						}
3389 					}
3390 					editing = 0;
3391 				}
3392 			}
3393 		}
3394 # if ENABLE_FEATURE_VI_YANKMARK
3395 	} else if (strncmp(cmd, "yank", i) == 0) {	// yank lines
3396 		if (!GOT_ADDRESS) {	// no addr given- use defaults
3397 			q = begin_line(dot);	// assume .,. for the range
3398 			r = end_line(dot);
3399 		}
3400 		text_yank(q, r, YDreg, WHOLE);
3401 		li = count_lines(q, r);
3402 		status_line("Yank %d lines (%d chars) into [%c]",
3403 				li, strlen(reg[YDreg]), what_reg());
3404 # endif
3405 	} else {
3406 		// cmd unknown
3407 		not_implemented(cmd);
3408 	}
3409  ret:
3410 # if ENABLE_FEATURE_VI_COLON_EXPAND
3411 	free(exp);
3412 # endif
3413 	dot = bound_dot(dot);	// make sure "dot" is valid
3414 	return;
3415 # if ENABLE_FEATURE_VI_SEARCH
3416  colon_s_fail:
3417 	status_line(":s expression missing delimiters");
3418 # endif
3419 #endif /* FEATURE_VI_COLON */
3420 }
3421 
3422 //----- Char Routines --------------------------------------------
3423 // Chars that are part of a word-
3424 //    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
3425 // Chars that are Not part of a word (stoppers)
3426 //    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
3427 // Chars that are WhiteSpace
3428 //    TAB NEWLINE VT FF RETURN SPACE
3429 // DO NOT COUNT NEWLINE AS WHITESPACE
3430 
st_test(char * p,int type,int dir,char * tested)3431 static int st_test(char *p, int type, int dir, char *tested)
3432 {
3433 	char c, c0, ci;
3434 	int test, inc;
3435 
3436 	inc = dir;
3437 	c = c0 = p[0];
3438 	ci = p[inc];
3439 	test = 0;
3440 
3441 	if (type == S_BEFORE_WS) {
3442 		c = ci;
3443 		test = (!isspace(c) || c == '\n');
3444 	}
3445 	if (type == S_TO_WS) {
3446 		c = c0;
3447 		test = (!isspace(c) || c == '\n');
3448 	}
3449 	if (type == S_OVER_WS) {
3450 		c = c0;
3451 		test = isspace(c);
3452 	}
3453 	if (type == S_END_PUNCT) {
3454 		c = ci;
3455 		test = ispunct(c);
3456 	}
3457 	if (type == S_END_ALNUM) {
3458 		c = ci;
3459 		test = (isalnum(c) || c == '_');
3460 	}
3461 	*tested = c;
3462 	return test;
3463 }
3464 
skip_thing(char * p,int linecnt,int dir,int type)3465 static char *skip_thing(char *p, int linecnt, int dir, int type)
3466 {
3467 	char c;
3468 
3469 	while (st_test(p, type, dir, &c)) {
3470 		// make sure we limit search to correct number of lines
3471 		if (c == '\n' && --linecnt < 1)
3472 			break;
3473 		if (dir >= 0 && p >= end - 1)
3474 			break;
3475 		if (dir < 0 && p <= text)
3476 			break;
3477 		p += dir;		// move to next char
3478 	}
3479 	return p;
3480 }
3481 
3482 #if ENABLE_FEATURE_VI_USE_SIGNALS
winch_handler(int sig UNUSED_PARAM)3483 static void winch_handler(int sig UNUSED_PARAM)
3484 {
3485 	int save_errno = errno;
3486 	// FIXME: do it in main loop!!!
3487 	signal(SIGWINCH, winch_handler);
3488 	query_screen_dimensions();
3489 	new_screen(rows, columns);	// get memory for virtual screen
3490 	redraw(TRUE);		// re-draw the screen
3491 	errno = save_errno;
3492 }
tstp_handler(int sig UNUSED_PARAM)3493 static void tstp_handler(int sig UNUSED_PARAM)
3494 {
3495 	int save_errno = errno;
3496 
3497 	// ioctl inside cookmode() was seen to generate SIGTTOU,
3498 	// stopping us too early. Prevent that:
3499 	signal(SIGTTOU, SIG_IGN);
3500 
3501 	go_bottom_and_clear_to_eol();
3502 	cookmode(); // terminal to "cooked"
3503 
3504 	// stop now
3505 	//signal(SIGTSTP, SIG_DFL);
3506 	//raise(SIGTSTP);
3507 	raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
3508 	//signal(SIGTSTP, tstp_handler);
3509 
3510 	// we have been "continued" with SIGCONT, restore screen and termios
3511 	rawmode(); // terminal to "raw"
3512 	last_status_cksum = 0; // force status update
3513 	redraw(TRUE); // re-draw the screen
3514 
3515 	errno = save_errno;
3516 }
int_handler(int sig)3517 static void int_handler(int sig)
3518 {
3519 	signal(SIGINT, int_handler);
3520 	siglongjmp(restart, sig);
3521 }
3522 #endif /* FEATURE_VI_USE_SIGNALS */
3523 
3524 static void do_cmd(int c);
3525 
at_eof(const char * s)3526 static int at_eof(const char *s)
3527 {
3528 	// does 's' point to end of file, even with no terminating newline?
3529 	return ((s == end - 2 && s[1] == '\n') || s == end - 1);
3530 }
3531 
find_range(char ** start,char ** stop,int cmd)3532 static int find_range(char **start, char **stop, int cmd)
3533 {
3534 	char *p, *q, *t;
3535 	int buftype = -1;
3536 	int c;
3537 
3538 	p = q = dot;
3539 
3540 #if ENABLE_FEATURE_VI_YANKMARK
3541 	if (cmd == 'Y') {
3542 		c = 'y';
3543 	} else
3544 #endif
3545 	{
3546 		c = get_motion_char();
3547 	}
3548 
3549 #if ENABLE_FEATURE_VI_YANKMARK
3550 	if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) {
3551 #else
3552 	if (cmd == c && strchr("cd><", c)) {
3553 #endif
3554 		// these cmds operate on whole lines
3555 		buftype = WHOLE;
3556 		if (--cmdcnt > 0) {
3557 			do_cmd('j');
3558 			if (cmd_error)
3559 				buftype = -1;
3560 		}
3561 	} else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
3562 		// Most operate on char positions within a line.  Of those that
3563 		// don't '%' needs no special treatment, search commands are
3564 		// marked as MULTI and  "{}" are handled below.
3565 		buftype = strchr("nN/?", c) ? MULTI : PARTIAL;
3566 		do_cmd(c);		// execute movement cmd
3567 		if (p == dot)	// no movement is an error
3568 			buftype = -1;
3569 	} else if (strchr("wW", c)) {
3570 		buftype = MULTI;
3571 		do_cmd(c);		// execute movement cmd
3572 		// step back one char, but not if we're at end of file,
3573 		// or if we are at EOF and search was for 'w' and we're at
3574 		// the start of a 'W' word.
3575 		if (dot > p && (!at_eof(dot) || (c == 'w' && ispunct(*dot))))
3576 			dot--;
3577 		t = dot;
3578 		// don't include trailing WS as part of word
3579 		while (dot > p && isspace(*dot)) {
3580 			if (*dot-- == '\n')
3581 				t = dot;
3582 		}
3583 		// for non-change operations WS after NL is not part of word
3584 		if (cmd != 'c' && dot != t && *dot != '\n')
3585 			dot = t;
3586 	} else if (strchr("GHL+-gjk'\r\n", c)) {
3587 		// these operate on whole lines
3588 		buftype = WHOLE;
3589 		do_cmd(c);		// execute movement cmd
3590 		if (cmd_error)
3591 			buftype = -1;
3592 	} else if (c == ' ' || c == 'l') {
3593 		// forward motion by character
3594 		int tmpcnt = (cmdcnt ?: 1);
3595 		buftype = PARTIAL;
3596 		do_cmd(c);		// execute movement cmd
3597 		// exclude last char unless range isn't what we expected
3598 		// this indicates we've hit EOL
3599 		if (tmpcnt == dot - p)
3600 			dot--;
3601 	}
3602 
3603 	if (buftype == -1) {
3604 		if (c != 27)
3605 			indicate_error();
3606 		return buftype;
3607 	}
3608 
3609 	q = dot;
3610 	if (q < p) {
3611 		t = q;
3612 		q = p;
3613 		p = t;
3614 	}
3615 
3616 	// movements which don't include end of range
3617 	if (q > p) {
3618 		if (strchr("^0bBFThnN/?|\b\177", c)) {
3619 			q--;
3620 		} else if (strchr("{}", c)) {
3621 			buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ?
3622 							WHOLE : MULTI;
3623 			if (!at_eof(q)) {
3624 				q--;
3625 				if (q > p && p != begin_line(p))
3626 					q--;
3627 			}
3628 		}
3629 	}
3630 
3631 	*start = p;
3632 	*stop = q;
3633 	return buftype;
3634 }
3635 
3636 //---------------------------------------------------------------------
3637 //----- the Ascii Chart -----------------------------------------------
3638 //  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
3639 //  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
3640 //  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
3641 //  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
3642 //  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
3643 //  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
3644 //  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
3645 //  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
3646 //  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
3647 //  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
3648 //  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
3649 //  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
3650 //  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
3651 //  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
3652 //  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
3653 //  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
3654 //---------------------------------------------------------------------
3655 
3656 //----- Execute a Vi Command -----------------------------------
3657 static void do_cmd(int c)
3658 {
3659 	char *p, *q, *save_dot;
3660 	char buf[12];
3661 	int dir;
3662 	int cnt, i, j;
3663 	int c1;
3664 #if ENABLE_FEATURE_VI_YANKMARK
3665 	char *orig_dot = dot;
3666 #endif
3667 #if ENABLE_FEATURE_VI_UNDO
3668 	int allow_undo = ALLOW_UNDO;
3669 	int undo_del = UNDO_DEL;
3670 #endif
3671 
3672 //	c1 = c; // quiet the compiler
3673 //	cnt = yf = 0; // quiet the compiler
3674 //	p = q = save_dot = buf; // quiet the compiler
3675 	memset(buf, '\0', sizeof(buf));
3676 	keep_index = FALSE;
3677 	cmd_error = FALSE;
3678 
3679 	show_status_line();
3680 
3681 	// if this is a cursor key, skip these checks
3682 	switch (c) {
3683 		case KEYCODE_UP:
3684 		case KEYCODE_DOWN:
3685 		case KEYCODE_LEFT:
3686 		case KEYCODE_RIGHT:
3687 		case KEYCODE_HOME:
3688 		case KEYCODE_END:
3689 		case KEYCODE_PAGEUP:
3690 		case KEYCODE_PAGEDOWN:
3691 		case KEYCODE_DELETE:
3692 			goto key_cmd_mode;
3693 	}
3694 
3695 	if (cmd_mode == 2) {
3696 		//  flip-flop Insert/Replace mode
3697 		if (c == KEYCODE_INSERT)
3698 			goto dc_i;
3699 		// we are 'R'eplacing the current *dot with new char
3700 		if (*dot == '\n') {
3701 			// don't Replace past E-o-l
3702 			cmd_mode = 1;	// convert to insert
3703 			undo_queue_commit();
3704 		} else {
3705 			if (1 <= c || Isprint(c)) {
3706 				if (c != 27)
3707 					dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);	// delete char
3708 				dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);	// insert new char
3709 			}
3710 			goto dc1;
3711 		}
3712 	}
3713 	if (cmd_mode == 1) {
3714 		// hitting "Insert" twice means "R" replace mode
3715 		if (c == KEYCODE_INSERT) goto dc5;
3716 		// insert the char c at "dot"
3717 		if (1 <= c || Isprint(c)) {
3718 			dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3719 		}
3720 		goto dc1;
3721 	}
3722 
3723  key_cmd_mode:
3724 	switch (c) {
3725 		//case 0x01:	// soh
3726 		//case 0x09:	// ht
3727 		//case 0x0b:	// vt
3728 		//case 0x0e:	// so
3729 		//case 0x0f:	// si
3730 		//case 0x10:	// dle
3731 		//case 0x11:	// dc1
3732 		//case 0x13:	// dc3
3733 #if ENABLE_FEATURE_VI_CRASHME
3734 	case 0x14:			// dc4  ctrl-T
3735 		crashme = (crashme == 0) ? 1 : 0;
3736 		break;
3737 #endif
3738 		//case 0x16:	// syn
3739 		//case 0x17:	// etb
3740 		//case 0x18:	// can
3741 		//case 0x1c:	// fs
3742 		//case 0x1d:	// gs
3743 		//case 0x1e:	// rs
3744 		//case 0x1f:	// us
3745 		//case '!':	// !-
3746 		//case '#':	// #-
3747 		//case '&':	// &-
3748 		//case '(':	// (-
3749 		//case ')':	// )-
3750 		//case '*':	// *-
3751 		//case '=':	// =-
3752 		//case '@':	// @-
3753 		//case 'K':	// K-
3754 		//case 'Q':	// Q-
3755 		//case 'S':	// S-
3756 		//case 'V':	// V-
3757 		//case '[':	// [-
3758 		//case '\\':	// \-
3759 		//case ']':	// ]-
3760 		//case '_':	// _-
3761 		//case '`':	// `-
3762 		//case 'v':	// v-
3763 	default:			// unrecognized command
3764 		buf[0] = c;
3765 		buf[1] = '\0';
3766 		not_implemented(buf);
3767 		end_cmd_q();	// stop adding to q
3768 	case 0x00:			// nul- ignore
3769 		break;
3770 	case 2:			// ctrl-B  scroll up   full screen
3771 	case KEYCODE_PAGEUP:	// Cursor Key Page Up
3772 		dot_scroll(rows - 2, -1);
3773 		break;
3774 	case 4:			// ctrl-D  scroll down half screen
3775 		dot_scroll((rows - 2) / 2, 1);
3776 		break;
3777 	case 5:			// ctrl-E  scroll down one line
3778 		dot_scroll(1, 1);
3779 		break;
3780 	case 6:			// ctrl-F  scroll down full screen
3781 	case KEYCODE_PAGEDOWN:	// Cursor Key Page Down
3782 		dot_scroll(rows - 2, 1);
3783 		break;
3784 	case 7:			// ctrl-G  show current status
3785 		last_status_cksum = 0;	// force status update
3786 		break;
3787 	case 'h':			// h- move left
3788 	case KEYCODE_LEFT:	// cursor key Left
3789 	case 8:		// ctrl-H- move left    (This may be ERASE char)
3790 	case 0x7f:	// DEL- move left   (This may be ERASE char)
3791 		do {
3792 			dot_left();
3793 		} while (--cmdcnt > 0);
3794 		break;
3795 	case 10:			// Newline ^J
3796 	case 'j':			// j- goto next line, same col
3797 	case KEYCODE_DOWN:	// cursor key Down
3798 	case 13:			// Carriage Return ^M
3799 	case '+':			// +- goto next line
3800 		q = dot;
3801 		do {
3802 			p = next_line(q);
3803 			if (p == end_line(q)) {
3804 				indicate_error();
3805 				goto dc1;
3806 			}
3807 			q = p;
3808 		} while (--cmdcnt > 0);
3809 		dot = q;
3810 		if (c == 13 || c == '+') {
3811 			dot_skip_over_ws();
3812 		} else {
3813 			// try to stay in saved column
3814 			dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
3815 			keep_index = TRUE;
3816 		}
3817 		break;
3818 	case 12:			// ctrl-L  force redraw whole screen
3819 	case 18:			// ctrl-R  force redraw
3820 		redraw(TRUE);	// this will redraw the entire display
3821 		break;
3822 	case 21:			// ctrl-U  scroll up half screen
3823 		dot_scroll((rows - 2) / 2, -1);
3824 		break;
3825 	case 25:			// ctrl-Y  scroll up one line
3826 		dot_scroll(1, -1);
3827 		break;
3828 	case 27:			// esc
3829 		if (cmd_mode == 0)
3830 			indicate_error();
3831 		cmd_mode = 0;	// stop inserting
3832 		undo_queue_commit();
3833 		end_cmd_q();
3834 		last_status_cksum = 0;	// force status update
3835 		break;
3836 	case ' ':			// move right
3837 	case 'l':			// move right
3838 	case KEYCODE_RIGHT:	// Cursor Key Right
3839 		do {
3840 			dot_right();
3841 		} while (--cmdcnt > 0);
3842 		break;
3843 #if ENABLE_FEATURE_VI_YANKMARK
3844 	case '"':			// "- name a register to use for Delete/Yank
3845 		c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3846 		if ((unsigned)c1 <= 25) { // a-z?
3847 			YDreg = c1;
3848 		} else {
3849 			indicate_error();
3850 		}
3851 		break;
3852 	case '\'':			// '- goto a specific mark
3853 		c1 = (get_one_char() | 0x20);
3854 		if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3855 			c1 = (c1 - 'a');
3856 			// get the b-o-l
3857 			q = mark[c1];
3858 			if (text <= q && q < end) {
3859 				dot = q;
3860 				dot_begin();	// go to B-o-l
3861 				dot_skip_over_ws();
3862 			} else {
3863 				indicate_error();
3864 			}
3865 		} else if (c1 == '\'') {	// goto previous context
3866 			dot = swap_context(dot);	// swap current and previous context
3867 			dot_begin();	// go to B-o-l
3868 			dot_skip_over_ws();
3869 #if ENABLE_FEATURE_VI_YANKMARK
3870 			orig_dot = dot;	// this doesn't update stored contexts
3871 #endif
3872 		} else {
3873 			indicate_error();
3874 		}
3875 		break;
3876 	case 'm':			// m- Mark a line
3877 		// this is really stupid.  If there are any inserts or deletes
3878 		// between text[0] and dot then this mark will not point to the
3879 		// correct location! It could be off by many lines!
3880 		// Well..., at least its quick and dirty.
3881 		c1 = (get_one_char() | 0x20) - 'a';
3882 		if ((unsigned)c1 <= 25) { // a-z?
3883 			// remember the line
3884 			mark[c1] = dot;
3885 		} else {
3886 			indicate_error();
3887 		}
3888 		break;
3889 	case 'P':			// P- Put register before
3890 	case 'p':			// p- put register after
3891 		p = reg[YDreg];
3892 		if (p == NULL) {
3893 			status_line_bold("Nothing in register %c", what_reg());
3894 			break;
3895 		}
3896 		cnt = 0;
3897 		i = cmdcnt ?: 1;
3898 		// are we putting whole lines or strings
3899 		if (regtype[YDreg] == WHOLE) {
3900 			if (c == 'P') {
3901 				dot_begin();	// putting lines- Put above
3902 			}
3903 			else /* if ( c == 'p') */ {
3904 				// are we putting after very last line?
3905 				if (end_line(dot) == (end - 1)) {
3906 					dot = end;	// force dot to end of text[]
3907 				} else {
3908 					dot_next();	// next line, then put before
3909 				}
3910 			}
3911 		} else {
3912 			if (c == 'p')
3913 				dot_right();	// move to right, can move to NL
3914 			// how far to move cursor if register doesn't have a NL
3915 			if (strchr(p, '\n') == NULL)
3916 				cnt = i * strlen(p) - 1;
3917 		}
3918 		do {
3919 			// dot is adjusted if text[] is reallocated so we don't have to
3920 			string_insert(dot, p, allow_undo);	// insert the string
3921 # if ENABLE_FEATURE_VI_UNDO
3922 			allow_undo = ALLOW_UNDO_CHAIN;
3923 # endif
3924 		} while (--cmdcnt > 0);
3925 		dot += cnt;
3926 		dot_skip_over_ws();
3927 # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
3928 		yank_status("Put", p, i);
3929 # endif
3930 		end_cmd_q();	// stop adding to q
3931 		break;
3932 	case 'U':			// U- Undo; replace current line with original version
3933 		if (reg[Ureg] != NULL) {
3934 			p = begin_line(dot);
3935 			q = end_line(dot);
3936 			p = text_hole_delete(p, q, ALLOW_UNDO);	// delete cur line
3937 			p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN);	// insert orig line
3938 			dot = p;
3939 			dot_skip_over_ws();
3940 # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
3941 			yank_status("Undo", reg[Ureg], 1);
3942 # endif
3943 		}
3944 		break;
3945 #endif /* FEATURE_VI_YANKMARK */
3946 #if ENABLE_FEATURE_VI_UNDO
3947 	case 'u':	// u- undo last operation
3948 		undo_pop();
3949 		break;
3950 #endif
3951 	case '$':			// $- goto end of line
3952 	case KEYCODE_END:		// Cursor Key End
3953 		for (;;) {
3954 			dot = end_line(dot);
3955 			if (--cmdcnt <= 0)
3956 				break;
3957 			dot_next();
3958 		}
3959 		cindex = C_END;
3960 		keep_index = TRUE;
3961 		break;
3962 	case '%':			// %- find matching char of pair () [] {}
3963 		for (q = dot; q < end && *q != '\n'; q++) {
3964 			if (strchr("()[]{}", *q) != NULL) {
3965 				// we found half of a pair
3966 				p = find_pair(q, *q);
3967 				if (p == NULL) {
3968 					indicate_error();
3969 				} else {
3970 					dot = p;
3971 				}
3972 				break;
3973 			}
3974 		}
3975 		if (*q == '\n')
3976 			indicate_error();
3977 		break;
3978 	case 'f':			// f- forward to a user specified char
3979 	case 'F':			// F- backward to a user specified char
3980 	case 't':			// t- move to char prior to next x
3981 	case 'T':			// T- move to char after previous x
3982 		last_search_char = get_one_char();	// get the search char
3983 		last_search_cmd = c;
3984 		// fall through
3985 	case ';':			// ;- look at rest of line for last search char
3986 	case ',':           // ,- repeat latest search in opposite direction
3987 		dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20);
3988 		break;
3989 #if ENABLE_FEATURE_VI_DOT_CMD
3990 	case '.':			// .- repeat the last modifying command
3991 		// Stuff the last_modifying_cmd back into stdin
3992 		// and let it be re-executed.
3993 		if (lmc_len != 0) {
3994 			if (cmdcnt)	// update saved count if current count is non-zero
3995 				dotcnt = cmdcnt;
3996 			last_modifying_cmd[lmc_len] = '\0';
3997 			ioq = ioq_start = xasprintf("%u%s", dotcnt, last_modifying_cmd);
3998 		}
3999 		break;
4000 #endif
4001 #if ENABLE_FEATURE_VI_SEARCH
4002 	case 'N':			// N- backward search for last pattern
4003 		dir = last_search_pattern[0] == '/' ? BACK : FORWARD;
4004 		goto dc4;		// now search for pattern
4005 		break;
4006 	case '?':			// ?- backward search for a pattern
4007 	case '/':			// /- forward search for a pattern
4008 		buf[0] = c;
4009 		buf[1] = '\0';
4010 		q = get_input_line(buf);	// get input line- use "status line"
4011 		if (!q[0])	// user changed mind and erased the "/"-  do nothing
4012 			break;
4013 		if (!q[1]) {	// if no pat re-use old pat
4014 			if (last_search_pattern[0])
4015 				last_search_pattern[0] = c;
4016 		} else {	// strlen(q) > 1: new pat- save it and find
4017 			free(last_search_pattern);
4018 			last_search_pattern = xstrdup(q);
4019 		}
4020 		// fall through
4021 	case 'n':			// n- repeat search for last pattern
4022 		// search rest of text[] starting at next char
4023 		// if search fails "dot" is unchanged
4024 		dir = last_search_pattern[0] == '/' ? FORWARD : BACK;
4025  dc4:
4026 		if (last_search_pattern[1] == '\0') {
4027 			status_line_bold("No previous search");
4028 			break;
4029 		}
4030 		do {
4031 			q = char_search(dot + dir, last_search_pattern + 1,
4032 						(dir << 1) | FULL);
4033 			if (q != NULL) {
4034 				dot = q;	// good search, update "dot"
4035 			} else {
4036 				// no pattern found between "dot" and top/bottom of file
4037 				// continue from other end of file
4038 				const char *msg;
4039 				q = char_search(dir == FORWARD ? text : end - 1,
4040 						last_search_pattern + 1, (dir << 1) | FULL);
4041 				if (q != NULL) {	// found something
4042 					dot = q;	// found new pattern- goto it
4043 					msg = "search hit %s, continuing at %s";
4044 				} else {	// pattern is nowhere in file
4045 					cmdcnt = 0;	// force exit from loop
4046 					msg = "Pattern not found";
4047 				}
4048 				if (dir == FORWARD)
4049 					status_line_bold(msg, "BOTTOM", "TOP");
4050 				else
4051 					status_line_bold(msg, "TOP", "BOTTOM");
4052 			}
4053 		} while (--cmdcnt > 0);
4054 		break;
4055 	case '{':			// {- move backward paragraph
4056 	case '}':			// }- move forward paragraph
4057 		dir = c == '}' ? FORWARD : BACK;
4058 		do {
4059 			int skip = TRUE; // initially skip consecutive empty lines
4060 			while (dir == FORWARD ? dot < end - 1 : dot > text) {
4061 				if (*dot == '\n' && dot[dir] == '\n') {
4062 					if (!skip) {
4063 						if (dir == FORWARD)
4064 							++dot;	// move to next blank line
4065 						goto dc2;
4066 					}
4067 				}
4068 				else {
4069 					skip = FALSE;
4070 				}
4071 				dot += dir;
4072 			}
4073 			goto dc6; // end of file
4074  dc2:		continue;
4075 		} while (--cmdcnt > 0);
4076 		break;
4077 #endif /* FEATURE_VI_SEARCH */
4078 	case '0':			// 0- goto beginning of line
4079 	case '1':			// 1-
4080 	case '2':			// 2-
4081 	case '3':			// 3-
4082 	case '4':			// 4-
4083 	case '5':			// 5-
4084 	case '6':			// 6-
4085 	case '7':			// 7-
4086 	case '8':			// 8-
4087 	case '9':			// 9-
4088 		if (c == '0' && cmdcnt < 1) {
4089 			dot_begin();	// this was a standalone zero
4090 		} else {
4091 			cmdcnt = cmdcnt * 10 + (c - '0');	// this 0 is part of a number
4092 		}
4093 		break;
4094 	case ':':			// :- the colon mode commands
4095 		p = get_input_line(":");	// get input line- use "status line"
4096 		colon(p);		// execute the command
4097 		break;
4098 	case '<':			// <- Left  shift something
4099 	case '>':			// >- Right shift something
4100 		cnt = count_lines(text, dot);	// remember what line we are on
4101 		if (find_range(&p, &q, c) == -1)
4102 			goto dc6;
4103 		i = count_lines(p, q);	// # of lines we are shifting
4104 		for (p = begin_line(p); i > 0; i--, p = next_line(p)) {
4105 			if (c == '<') {
4106 				// shift left- remove tab or tabstop spaces
4107 				if (*p == '\t') {
4108 					// shrink buffer 1 char
4109 					text_hole_delete(p, p, allow_undo);
4110 				} else if (*p == ' ') {
4111 					// we should be calculating columns, not just SPACE
4112 					for (j = 0; *p == ' ' && j < tabstop; j++) {
4113 						text_hole_delete(p, p, allow_undo);
4114 #if ENABLE_FEATURE_VI_UNDO
4115 						allow_undo = ALLOW_UNDO_CHAIN;
4116 #endif
4117 					}
4118 				}
4119 			} else if (/* c == '>' && */ p != end_line(p)) {
4120 				// shift right -- add tab or tabstop spaces on non-empty lines
4121 				char_insert(p, '\t', allow_undo);
4122 			}
4123 #if ENABLE_FEATURE_VI_UNDO
4124 			allow_undo = ALLOW_UNDO_CHAIN;
4125 #endif
4126 		}
4127 		dot = find_line(cnt);	// what line were we on
4128 		dot_skip_over_ws();
4129 		end_cmd_q();	// stop adding to q
4130 		break;
4131 	case 'A':			// A- append at e-o-l
4132 		dot_end();		// go to e-o-l
4133 		//**** fall through to ... 'a'
4134 	case 'a':			// a- append after current char
4135 		if (*dot != '\n')
4136 			dot++;
4137 		goto dc_i;
4138 		break;
4139 	case 'B':			// B- back a blank-delimited Word
4140 	case 'E':			// E- end of a blank-delimited word
4141 	case 'W':			// W- forward a blank-delimited word
4142 		dir = FORWARD;
4143 		if (c == 'B')
4144 			dir = BACK;
4145 		do {
4146 			if (c == 'W' || isspace(dot[dir])) {
4147 				dot = skip_thing(dot, 1, dir, S_TO_WS);
4148 				dot = skip_thing(dot, 2, dir, S_OVER_WS);
4149 			}
4150 			if (c != 'W')
4151 				dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
4152 		} while (--cmdcnt > 0);
4153 		break;
4154 	case 'C':			// C- Change to e-o-l
4155 	case 'D':			// D- delete to e-o-l
4156 		save_dot = dot;
4157 		dot = dollar_line(dot);	// move to before NL
4158 		// copy text into a register and delete
4159 		dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);	// delete to e-o-l
4160 		if (c == 'C')
4161 			goto dc_i;	// start inserting
4162 #if ENABLE_FEATURE_VI_DOT_CMD
4163 		if (c == 'D')
4164 			end_cmd_q();	// stop adding to q
4165 #endif
4166 		break;
4167 	case 'g': // 'gg' goto a line number (vim) (default: very first line)
4168 		c1 = get_one_char();
4169 		if (c1 != 'g') {
4170 			buf[0] = 'g';
4171 			// c1 < 0 if the key was special. Try "g<up-arrow>"
4172 			// TODO: if Unicode?
4173 			buf[1] = (c1 >= 0 ? c1 : '*');
4174 			buf[2] = '\0';
4175 			not_implemented(buf);
4176 			cmd_error = TRUE;
4177 			break;
4178 		}
4179 		if (cmdcnt == 0)
4180 			cmdcnt = 1;
4181 		// fall through
4182 	case 'G':		// G- goto to a line number (default= E-O-F)
4183 		dot = end - 1;				// assume E-O-F
4184 		if (cmdcnt > 0) {
4185 			dot = find_line(cmdcnt);	// what line is #cmdcnt
4186 		}
4187 		dot_begin();
4188 		dot_skip_over_ws();
4189 		break;
4190 	case 'H':			// H- goto top line on screen
4191 		dot = screenbegin;
4192 		if (cmdcnt > (rows - 1)) {
4193 			cmdcnt = (rows - 1);
4194 		}
4195 		while (--cmdcnt > 0) {
4196 			dot_next();
4197 		}
4198 		dot_begin();
4199 		dot_skip_over_ws();
4200 		break;
4201 	case 'I':			// I- insert before first non-blank
4202 		dot_begin();	// 0
4203 		dot_skip_over_ws();
4204 		//**** fall through to ... 'i'
4205 	case 'i':			// i- insert before current char
4206 	case KEYCODE_INSERT:	// Cursor Key Insert
4207  dc_i:
4208 		cmd_mode = 1;	// start inserting
4209 		undo_queue_commit();	// commit queue when cmd_mode changes
4210 		break;
4211 	case 'J':			// J- join current and next lines together
4212 		do {
4213 			dot_end();		// move to NL
4214 			if (dot < end - 1) {	// make sure not last char in text[]
4215 #if ENABLE_FEATURE_VI_UNDO
4216 				undo_push(dot, 1, UNDO_DEL);
4217 				*dot++ = ' ';	// replace NL with space
4218 				undo_push((dot - 1), 1, UNDO_INS_CHAIN);
4219 #else
4220 				*dot++ = ' ';
4221 				modified_count++;
4222 #endif
4223 				while (isblank(*dot)) {	// delete leading WS
4224 					text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
4225 				}
4226 			}
4227 		} while (--cmdcnt > 0);
4228 		end_cmd_q();	// stop adding to q
4229 		break;
4230 	case 'L':			// L- goto bottom line on screen
4231 		dot = end_screen();
4232 		if (cmdcnt > (rows - 1)) {
4233 			cmdcnt = (rows - 1);
4234 		}
4235 		while (--cmdcnt > 0) {
4236 			dot_prev();
4237 		}
4238 		dot_begin();
4239 		dot_skip_over_ws();
4240 		break;
4241 	case 'M':			// M- goto middle line on screen
4242 		dot = screenbegin;
4243 		for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4244 			dot = next_line(dot);
4245 		dot_skip_over_ws();
4246 		break;
4247 	case 'O':			// O- open an empty line above
4248 		dot_begin();
4249 #if ENABLE_FEATURE_VI_SETOPTS
4250 		indentcol = -1;
4251 #endif
4252 		goto dc3;
4253 	case 'o':			// o- open an empty line below
4254 		dot_end();
4255  dc3:
4256 		dot = char_insert(dot, '\n', ALLOW_UNDO);
4257 		if (c == 'O' && !autoindent) {
4258 			// done in char_insert() for 'O'+autoindent
4259 			dot_prev();
4260 		}
4261 		goto dc_i;
4262 		break;
4263 	case 'R':			// R- continuous Replace char
4264  dc5:
4265 		cmd_mode = 2;
4266 		undo_queue_commit();
4267 		break;
4268 	case KEYCODE_DELETE:
4269 		if (dot < end - 1)
4270 			dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
4271 		break;
4272 	case 'X':			// X- delete char before dot
4273 	case 'x':			// x- delete the current char
4274 	case 's':			// s- substitute the current char
4275 		dir = 0;
4276 		if (c == 'X')
4277 			dir = -1;
4278 		do {
4279 			if (dot[dir] != '\n') {
4280 				if (c == 'X')
4281 					dot--;	// delete prev char
4282 				dot = yank_delete(dot, dot, PARTIAL, YANKDEL, allow_undo);	// delete char
4283 #if ENABLE_FEATURE_VI_UNDO
4284 				allow_undo = ALLOW_UNDO_CHAIN;
4285 #endif
4286 			}
4287 		} while (--cmdcnt > 0);
4288 		end_cmd_q();	// stop adding to q
4289 		if (c == 's')
4290 			goto dc_i;	// start inserting
4291 		break;
4292 	case 'Z':			// Z- if modified, {write}; exit
4293 		// ZZ means to save file (if necessary), then exit
4294 		c1 = get_one_char();
4295 		if (c1 != 'Z') {
4296 			indicate_error();
4297 			break;
4298 		}
4299 		if (modified_count) {
4300 			if (ENABLE_FEATURE_VI_READONLY && readonly_mode && current_filename) {
4301 				status_line_bold("'%s' is read only", current_filename);
4302 				break;
4303 			}
4304 			cnt = file_write(current_filename, text, end - 1);
4305 			if (cnt < 0) {
4306 				if (cnt == -1)
4307 					status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
4308 			} else if (cnt == (end - 1 - text + 1)) {
4309 				editing = 0;
4310 			}
4311 		} else {
4312 			editing = 0;
4313 		}
4314 		// are there other files to edit?
4315 		j = cmdline_filecnt - optind - 1;
4316 		if (editing == 0 && j > 0) {
4317 			editing = 1;
4318 			modified_count = 0;
4319 			last_modified_count = -1;
4320 			status_line_bold("%u more file(s) to edit", j);
4321 		}
4322 		break;
4323 	case '^':			// ^- move to first non-blank on line
4324 		dot_begin();
4325 		dot_skip_over_ws();
4326 		break;
4327 	case 'b':			// b- back a word
4328 	case 'e':			// e- end of word
4329 		dir = FORWARD;
4330 		if (c == 'b')
4331 			dir = BACK;
4332 		do {
4333 			if ((dot + dir) < text || (dot + dir) > end - 1)
4334 				break;
4335 			dot += dir;
4336 			if (isspace(*dot)) {
4337 				dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4338 			}
4339 			if (isalnum(*dot) || *dot == '_') {
4340 				dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4341 			} else if (ispunct(*dot)) {
4342 				dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4343 			}
4344 		} while (--cmdcnt > 0);
4345 		break;
4346 	case 'c':			// c- change something
4347 	case 'd':			// d- delete something
4348 #if ENABLE_FEATURE_VI_YANKMARK
4349 	case 'y':			// y- yank   something
4350 	case 'Y':			// Y- Yank a line
4351 #endif
4352 	{
4353 		int yf = YANKDEL;	// assume either "c" or "d"
4354 		int buftype;
4355 #if ENABLE_FEATURE_VI_YANKMARK
4356 # if ENABLE_FEATURE_VI_VERBOSE_STATUS
4357 		char *savereg = reg[YDreg];
4358 # endif
4359 		if (c == 'y' || c == 'Y')
4360 			yf = YANKONLY;
4361 #endif
4362 		// determine range, and whether it spans lines
4363 		buftype = find_range(&p, &q, c);
4364 		if (buftype == -1)	// invalid range
4365 			goto dc6;
4366 		if (buftype == WHOLE) {
4367 			save_dot = p;	// final cursor position is start of range
4368 			p = begin_line(p);
4369 			q = end_line(q);
4370 		}
4371 		dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO);	// delete word
4372 		if (buftype == WHOLE) {
4373 			if (c == 'c') {
4374 				dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4375 				// on the last line of file don't move to prev line
4376 				if (dot != (end-1)) {
4377 					dot_prev();
4378 				}
4379 			} else if (c == 'd') {
4380 				dot_begin();
4381 				dot_skip_over_ws();
4382 			} else {
4383 				dot = save_dot;
4384 			}
4385 		}
4386 		// if CHANGING, not deleting, start inserting after the delete
4387 		if (c == 'c') {
4388 			goto dc_i;	// start inserting
4389 		}
4390 #if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
4391 		// only update status if a yank has actually happened
4392 		if (reg[YDreg] != savereg)
4393 			yank_status(c == 'd' ? "Delete" : "Yank", reg[YDreg], 1);
4394 #endif
4395  dc6:
4396 		end_cmd_q();	// stop adding to q
4397 		break;
4398 	}
4399 	case 'k':			// k- goto prev line, same col
4400 	case KEYCODE_UP:		// cursor key Up
4401 	case '-':			// -- goto prev line
4402 		q = dot;
4403 		do {
4404 			p = prev_line(q);
4405 			if (p == begin_line(q)) {
4406 				indicate_error();
4407 				goto dc1;
4408 			}
4409 			q = p;
4410 		} while (--cmdcnt > 0);
4411 		dot = q;
4412 		if (c == '-') {
4413 			dot_skip_over_ws();
4414 		} else {
4415 			// try to stay in saved column
4416 			dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
4417 			keep_index = TRUE;
4418 		}
4419 		break;
4420 	case 'r':			// r- replace the current char with user input
4421 		c1 = get_one_char();	// get the replacement char
4422 		if (c1 != 27) {
4423 			if (end_line(dot) - dot < (cmdcnt ?: 1)) {
4424 				indicate_error();
4425 				goto dc6;
4426 			}
4427 			do {
4428 				dot = text_hole_delete(dot, dot, allow_undo);
4429 #if ENABLE_FEATURE_VI_UNDO
4430 				allow_undo = ALLOW_UNDO_CHAIN;
4431 #endif
4432 				dot = char_insert(dot, c1, allow_undo);
4433 			} while (--cmdcnt > 0);
4434 			dot_left();
4435 		}
4436 		end_cmd_q();	// stop adding to q
4437 		break;
4438 	case 'w':			// w- forward a word
4439 		do {
4440 			if (isalnum(*dot) || *dot == '_') {	// we are on ALNUM
4441 				dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4442 			} else if (ispunct(*dot)) {	// we are on PUNCT
4443 				dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4444 			}
4445 			if (dot < end - 1)
4446 				dot++;		// move over word
4447 			if (isspace(*dot)) {
4448 				dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4449 			}
4450 		} while (--cmdcnt > 0);
4451 		break;
4452 	case 'z':			// z-
4453 		c1 = get_one_char();	// get the replacement char
4454 		cnt = 0;
4455 		if (c1 == '.')
4456 			cnt = (rows - 2) / 2;	// put dot at center
4457 		if (c1 == '-')
4458 			cnt = rows - 2;	// put dot at bottom
4459 		screenbegin = begin_line(dot);	// start dot at top
4460 		dot_scroll(cnt, -1);
4461 		break;
4462 	case '|':			// |- move to column "cmdcnt"
4463 		dot = move_to_col(dot, cmdcnt - 1);	// try to move to column
4464 		break;
4465 	case '~':			// ~- flip the case of letters   a-z -> A-Z
4466 		do {
4467 #if ENABLE_FEATURE_VI_UNDO
4468 			if (isalpha(*dot)) {
4469 				undo_push(dot, 1, undo_del);
4470 				*dot = islower(*dot) ? toupper(*dot) : tolower(*dot);
4471 				undo_push(dot, 1, UNDO_INS_CHAIN);
4472 				undo_del = UNDO_DEL_CHAIN;
4473 			}
4474 #else
4475 			if (islower(*dot)) {
4476 				*dot = toupper(*dot);
4477 				modified_count++;
4478 			} else if (isupper(*dot)) {
4479 				*dot = tolower(*dot);
4480 				modified_count++;
4481 			}
4482 #endif
4483 			dot_right();
4484 		} while (--cmdcnt > 0);
4485 		end_cmd_q();	// stop adding to q
4486 		break;
4487 		//----- The Cursor and Function Keys -----------------------------
4488 	case KEYCODE_HOME:	// Cursor Key Home
4489 		dot_begin();
4490 		break;
4491 		// The Fn keys could point to do_macro which could translate them
4492 #if 0
4493 	case KEYCODE_FUN1:	// Function Key F1
4494 	case KEYCODE_FUN2:	// Function Key F2
4495 	case KEYCODE_FUN3:	// Function Key F3
4496 	case KEYCODE_FUN4:	// Function Key F4
4497 	case KEYCODE_FUN5:	// Function Key F5
4498 	case KEYCODE_FUN6:	// Function Key F6
4499 	case KEYCODE_FUN7:	// Function Key F7
4500 	case KEYCODE_FUN8:	// Function Key F8
4501 	case KEYCODE_FUN9:	// Function Key F9
4502 	case KEYCODE_FUN10:	// Function Key F10
4503 	case KEYCODE_FUN11:	// Function Key F11
4504 	case KEYCODE_FUN12:	// Function Key F12
4505 		break;
4506 #endif
4507 	}
4508 
4509  dc1:
4510 	// if text[] just became empty, add back an empty line
4511 	if (end == text) {
4512 		char_insert(text, '\n', NO_UNDO);	// start empty buf with dummy line
4513 		dot = text;
4514 	}
4515 	// it is OK for dot to exactly equal to end, otherwise check dot validity
4516 	if (dot != end) {
4517 		dot = bound_dot(dot);	// make sure "dot" is valid
4518 	}
4519 #if ENABLE_FEATURE_VI_YANKMARK
4520 	if (dot != orig_dot)
4521 		check_context(c);	// update the current context
4522 #endif
4523 
4524 	if (!isdigit(c))
4525 		cmdcnt = 0;		// cmd was not a number, reset cmdcnt
4526 	cnt = dot - begin_line(dot);
4527 	// Try to stay off of the Newline
4528 	if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4529 		dot--;
4530 }
4531 
4532 // NB!  the CRASHME code is unmaintained, and doesn't currently build
4533 #if ENABLE_FEATURE_VI_CRASHME
4534 static int totalcmds = 0;
4535 static int Mp = 85;             // Movement command Probability
4536 static int Np = 90;             // Non-movement command Probability
4537 static int Dp = 96;             // Delete command Probability
4538 static int Ip = 97;             // Insert command Probability
4539 static int Yp = 98;             // Yank command Probability
4540 static int Pp = 99;             // Put command Probability
4541 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4542 static const char chars[20] = "\t012345 abcdABCD-=.$";
4543 static const char *const words[20] = {
4544 	"this", "is", "a", "test",
4545 	"broadcast", "the", "emergency", "of",
4546 	"system", "quick", "brown", "fox",
4547 	"jumped", "over", "lazy", "dogs",
4548 	"back", "January", "Febuary", "March"
4549 };
4550 static const char *const lines[20] = {
4551 	"You should have received a copy of the GNU General Public License\n",
4552 	"char c, cm, *cmd, *cmd1;\n",
4553 	"generate a command by percentages\n",
4554 	"Numbers may be typed as a prefix to some commands.\n",
4555 	"Quit, discarding changes!\n",
4556 	"Forced write, if permission originally not valid.\n",
4557 	"In general, any ex or ed command (such as substitute or delete).\n",
4558 	"I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4559 	"Please get w/ me and I will go over it with you.\n",
4560 	"The following is a list of scheduled, committed changes.\n",
4561 	"1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4562 	"Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4563 	"Any question about transactions please contact Sterling Huxley.\n",
4564 	"I will try to get back to you by Friday, December 31.\n",
4565 	"This Change will be implemented on Friday.\n",
4566 	"Let me know if you have problems accessing this;\n",
4567 	"Sterling Huxley recently added you to the access list.\n",
4568 	"Would you like to go to lunch?\n",
4569 	"The last command will be automatically run.\n",
4570 	"This is too much english for a computer geek.\n",
4571 };
4572 static char *multilines[20] = {
4573 	"You should have received a copy of the GNU General Public License\n",
4574 	"char c, cm, *cmd, *cmd1;\n",
4575 	"generate a command by percentages\n",
4576 	"Numbers may be typed as a prefix to some commands.\n",
4577 	"Quit, discarding changes!\n",
4578 	"Forced write, if permission originally not valid.\n",
4579 	"In general, any ex or ed command (such as substitute or delete).\n",
4580 	"I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4581 	"Please get w/ me and I will go over it with you.\n",
4582 	"The following is a list of scheduled, committed changes.\n",
4583 	"1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4584 	"Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4585 	"Any question about transactions please contact Sterling Huxley.\n",
4586 	"I will try to get back to you by Friday, December 31.\n",
4587 	"This Change will be implemented on Friday.\n",
4588 	"Let me know if you have problems accessing this;\n",
4589 	"Sterling Huxley recently added you to the access list.\n",
4590 	"Would you like to go to lunch?\n",
4591 	"The last command will be automatically run.\n",
4592 	"This is too much english for a computer geek.\n",
4593 };
4594 
4595 // create a random command to execute
4596 static void crash_dummy()
4597 {
4598 	static int sleeptime;   // how long to pause between commands
4599 	char c, cm, *cmd, *cmd1;
4600 	int i, cnt, thing, rbi, startrbi, percent;
4601 
4602 	// "dot" movement commands
4603 	cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4604 
4605 	// is there already a command running?
4606 	if (readbuffer[0] > 0)
4607 		goto cd1;
4608  cd0:
4609 	readbuffer[0] = 'X';
4610 	startrbi = rbi = 1;
4611 	sleeptime = 0;          // how long to pause between commands
4612 	memset(readbuffer, '\0', sizeof(readbuffer));
4613 	// generate a command by percentages
4614 	percent = (int) lrand48() % 100;        // get a number from 0-99
4615 	if (percent < Mp) {     //  Movement commands
4616 		// available commands
4617 		cmd = cmd1;
4618 		M++;
4619 	} else if (percent < Np) {      //  non-movement commands
4620 		cmd = "mz<>\'\"";       // available commands
4621 		N++;
4622 	} else if (percent < Dp) {      //  Delete commands
4623 		cmd = "dx";             // available commands
4624 		D++;
4625 	} else if (percent < Ip) {      //  Inset commands
4626 		cmd = "iIaAsrJ";        // available commands
4627 		I++;
4628 	} else if (percent < Yp) {      //  Yank commands
4629 		cmd = "yY";             // available commands
4630 		Y++;
4631 	} else if (percent < Pp) {      //  Put commands
4632 		cmd = "pP";             // available commands
4633 		P++;
4634 	} else {
4635 		// We do not know how to handle this command, try again
4636 		U++;
4637 		goto cd0;
4638 	}
4639 	// randomly pick one of the available cmds from "cmd[]"
4640 	i = (int) lrand48() % strlen(cmd);
4641 	cm = cmd[i];
4642 	if (strchr(":\024", cm))
4643 		goto cd0;               // dont allow colon or ctrl-T commands
4644 	readbuffer[rbi++] = cm; // put cmd into input buffer
4645 
4646 	// now we have the command-
4647 	// there are 1, 2, and multi char commands
4648 	// find out which and generate the rest of command as necessary
4649 	if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
4650 		cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4651 		if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
4652 			cmd1 = "abcdefghijklmnopqrstuvwxyz";
4653 		}
4654 		thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4655 		c = cmd1[thing];
4656 		readbuffer[rbi++] = c;  // add movement to input buffer
4657 	}
4658 	if (strchr("iIaAsc", cm)) {     // multi-char commands
4659 		if (cm == 'c') {
4660 			// change some thing
4661 			thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4662 			c = cmd1[thing];
4663 			readbuffer[rbi++] = c;  // add movement to input buffer
4664 		}
4665 		thing = (int) lrand48() % 4;    // what thing to insert
4666 		cnt = (int) lrand48() % 10;     // how many to insert
4667 		for (i = 0; i < cnt; i++) {
4668 			if (thing == 0) {       // insert chars
4669 				readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4670 			} else if (thing == 1) {        // insert words
4671 				strcat(readbuffer, words[(int) lrand48() % 20]);
4672 				strcat(readbuffer, " ");
4673 				sleeptime = 0;  // how fast to type
4674 			} else if (thing == 2) {        // insert lines
4675 				strcat(readbuffer, lines[(int) lrand48() % 20]);
4676 				sleeptime = 0;  // how fast to type
4677 			} else {        // insert multi-lines
4678 				strcat(readbuffer, multilines[(int) lrand48() % 20]);
4679 				sleeptime = 0;  // how fast to type
4680 			}
4681 		}
4682 		strcat(readbuffer, ESC);
4683 	}
4684 	readbuffer[0] = strlen(readbuffer + 1);
4685  cd1:
4686 	totalcmds++;
4687 	if (sleeptime > 0)
4688 		mysleep(sleeptime);      // sleep 1/100 sec
4689 }
4690 
4691 // test to see if there are any errors
4692 static void crash_test()
4693 {
4694 	static time_t oldtim;
4695 
4696 	time_t tim;
4697 	char d[2], msg[80];
4698 
4699 	msg[0] = '\0';
4700 	if (end < text) {
4701 		strcat(msg, "end<text ");
4702 	}
4703 	if (end > textend) {
4704 		strcat(msg, "end>textend ");
4705 	}
4706 	if (dot < text) {
4707 		strcat(msg, "dot<text ");
4708 	}
4709 	if (dot > end) {
4710 		strcat(msg, "dot>end ");
4711 	}
4712 	if (screenbegin < text) {
4713 		strcat(msg, "screenbegin<text ");
4714 	}
4715 	if (screenbegin > end - 1) {
4716 		strcat(msg, "screenbegin>end-1 ");
4717 	}
4718 
4719 	if (msg[0]) {
4720 		printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4721 			totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4722 		fflush_all();
4723 		while (safe_read(STDIN_FILENO, d, 1) > 0) {
4724 			if (d[0] == '\n' || d[0] == '\r')
4725 				break;
4726 		}
4727 	}
4728 	tim = time(NULL);
4729 	if (tim >= (oldtim + 3)) {
4730 		sprintf(status_buffer,
4731 				"Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4732 				totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4733 		oldtim = tim;
4734 	}
4735 }
4736 #endif
4737 
4738 #if ENABLE_FEATURE_VI_COLON
4739 static void run_cmds(char *p)
4740 {
4741 	while (p) {
4742 		char *q = p;
4743 		p = strchr(q, '\n');
4744 		if (p)
4745 			while (*p == '\n')
4746 				*p++ = '\0';
4747 		if (strlen(q) < MAX_INPUT_LEN)
4748 			colon(q);
4749 	}
4750 }
4751 #endif
4752 
4753 static void edit_file(char *fn)
4754 {
4755 #if ENABLE_FEATURE_VI_YANKMARK
4756 #define cur_line edit_file__cur_line
4757 #endif
4758 	int c;
4759 #if ENABLE_FEATURE_VI_USE_SIGNALS
4760 	int sig;
4761 #endif
4762 
4763 	editing = 1;	// 0 = exit, 1 = one file, 2 = multiple files
4764 	rawmode();
4765 	rows = 24;
4766 	columns = 80;
4767 	IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4768 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4769 	if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4770 		uint64_t k;
4771 		write1(ESC"[999;999H" ESC"[6n");
4772 		fflush_all();
4773 		k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4774 		if ((int32_t)k == KEYCODE_CURSOR_POS) {
4775 			uint32_t rc = (k >> 32);
4776 			columns = (rc & 0x7fff);
4777 			if (columns > MAX_SCR_COLS)
4778 				columns = MAX_SCR_COLS;
4779 			rows = ((rc >> 16) & 0x7fff);
4780 			if (rows > MAX_SCR_ROWS)
4781 				rows = MAX_SCR_ROWS;
4782 		}
4783 	}
4784 #endif
4785 	new_screen(rows, columns);	// get memory for virtual screen
4786 	init_text_buffer(fn);
4787 
4788 #if ENABLE_FEATURE_VI_YANKMARK
4789 	YDreg = 26;			// default Yank/Delete reg
4790 //	Ureg = 27; - const		// hold orig line for "U" cmd
4791 	mark[26] = mark[27] = text;	// init "previous context"
4792 #endif
4793 
4794 #if ENABLE_FEATURE_VI_CRASHME
4795 	last_input_char = '\0';
4796 #endif
4797 	crow = 0;
4798 	ccol = 0;
4799 
4800 #if ENABLE_FEATURE_VI_USE_SIGNALS
4801 	signal(SIGWINCH, winch_handler);
4802 	signal(SIGTSTP, tstp_handler);
4803 	sig = sigsetjmp(restart, 1);
4804 	if (sig != 0) {
4805 		screenbegin = dot = text;
4806 	}
4807 	// int_handler() can jump to "restart",
4808 	// must install handler *after* initializing "restart"
4809 	signal(SIGINT, int_handler);
4810 #endif
4811 
4812 	cmd_mode = 0;		// 0=command  1=insert  2='R'eplace
4813 	cmdcnt = 0;
4814 	offset = 0;			// no horizontal offset
4815 	c = '\0';
4816 #if ENABLE_FEATURE_VI_DOT_CMD
4817 	free(ioq_start);
4818 	ioq_start = NULL;
4819 	adding2q = 0;
4820 #endif
4821 
4822 #if ENABLE_FEATURE_VI_COLON
4823 	while (initial_cmds)
4824 		run_cmds((char *)llist_pop(&initial_cmds));
4825 #endif
4826 	redraw(FALSE);			// dont force every col re-draw
4827 	//------This is the main Vi cmd handling loop -----------------------
4828 	while (editing > 0) {
4829 #if ENABLE_FEATURE_VI_CRASHME
4830 		if (crashme > 0) {
4831 			if ((end - text) > 1) {
4832 				crash_dummy();	// generate a random command
4833 			} else {
4834 				crashme = 0;
4835 				string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n", NO_UNDO);
4836 				dot = text;
4837 				refresh(FALSE);
4838 			}
4839 		}
4840 #endif
4841 		c = get_one_char();	// get a cmd from user
4842 #if ENABLE_FEATURE_VI_CRASHME
4843 		last_input_char = c;
4844 #endif
4845 #if ENABLE_FEATURE_VI_YANKMARK
4846 		// save a copy of the current line- for the 'U" command
4847 		if (begin_line(dot) != cur_line) {
4848 			cur_line = begin_line(dot);
4849 			text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL);
4850 		}
4851 #endif
4852 #if ENABLE_FEATURE_VI_DOT_CMD
4853 		// If c is a command that changes text[],
4854 		// (re)start remembering the input for the "." command.
4855 		if (!adding2q
4856 		 && ioq_start == NULL
4857 		 && cmd_mode == 0 // command mode
4858 		 && c > '\0' // exclude NUL and non-ASCII chars
4859 		 && c < 0x7f // (Unicode and such)
4860 		 && strchr(modifying_cmds, c)
4861 		) {
4862 			start_new_cmd_q(c);
4863 		}
4864 #endif
4865 		do_cmd(c);		// execute the user command
4866 
4867 		// poll to see if there is input already waiting. if we are
4868 		// not able to display output fast enough to keep up, skip
4869 		// the display update until we catch up with input.
4870 		if (!readbuffer[0] && mysleep(0) == 0) {
4871 			// no input pending - so update output
4872 			refresh(FALSE);
4873 			show_status_line();
4874 		}
4875 #if ENABLE_FEATURE_VI_CRASHME
4876 		if (crashme > 0)
4877 			crash_test();	// test editor variables
4878 #endif
4879 	}
4880 	//-------------------------------------------------------------------
4881 
4882 	go_bottom_and_clear_to_eol();
4883 	cookmode();
4884 #undef cur_line
4885 }
4886 
4887 #define VI_OPTSTR \
4888 	IF_FEATURE_VI_CRASHME("C") \
4889 	IF_FEATURE_VI_COLON("c:*") \
4890 	"Hh" \
4891 	IF_FEATURE_VI_READONLY("R")
4892 
4893 enum {
4894 	IF_FEATURE_VI_CRASHME(OPTBIT_C,)
4895 	IF_FEATURE_VI_COLON(OPTBIT_c,)
4896 	OPTBIT_H,
4897 	OPTBIT_h,
4898 	IF_FEATURE_VI_READONLY(OPTBIT_R,)
4899 	OPT_C = IF_FEATURE_VI_CRASHME(	(1 << OPTBIT_C)) + 0,
4900 	OPT_c = IF_FEATURE_VI_COLON(	(1 << OPTBIT_c)) + 0,
4901 	OPT_H = 1 << OPTBIT_H,
4902 	OPT_h = 1 << OPTBIT_h,
4903 	OPT_R = IF_FEATURE_VI_READONLY(	(1 << OPTBIT_R)) + 0,
4904 };
4905 
4906 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4907 int vi_main(int argc, char **argv)
4908 {
4909 	int opts;
4910 
4911 	INIT_G();
4912 
4913 #if ENABLE_FEATURE_VI_UNDO
4914 	//undo_stack_tail = NULL; - already is
4915 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4916 	undo_queue_state = UNDO_EMPTY;
4917 	//undo_q = 0; - already is
4918 # endif
4919 #endif
4920 
4921 #if ENABLE_FEATURE_VI_CRASHME
4922 	srand((long) getpid());
4923 #endif
4924 #ifdef NO_SUCH_APPLET_YET
4925 	// if we aren't "vi", we are "view"
4926 	if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4927 		SET_READONLY_MODE(readonly_mode);
4928 	}
4929 #endif
4930 
4931 	// 0: all of our options are disabled by default in vim
4932 	//vi_setops = 0;
4933 	opts = getopt32(argv, VI_OPTSTR IF_FEATURE_VI_COLON(, &initial_cmds));
4934 
4935 #if ENABLE_FEATURE_VI_CRASHME
4936 	if (opts & OPT_C)
4937 		crashme = 1;
4938 #endif
4939 	if (opts & OPT_R)
4940 		SET_READONLY_MODE(readonly_mode);
4941 	if (opts & OPT_H)
4942 		show_help();
4943 	if (opts & (OPT_H | OPT_h)) {
4944 		bb_show_usage();
4945 		return 1;
4946 	}
4947 
4948 	argv += optind;
4949 	cmdline_filecnt = argc - optind;
4950 
4951 	//  1-  process EXINIT variable from environment
4952 	//  2-  if EXINIT is unset process $HOME/.exrc file
4953 	//  3-  process command line args
4954 #if ENABLE_FEATURE_VI_COLON
4955 	{
4956 		const char *exinit = getenv("EXINIT");
4957 		char *cmds = NULL;
4958 
4959 		if (exinit) {
4960 			cmds = xstrdup(exinit);
4961 		} else {
4962 			const char *home = getenv("HOME");
4963 
4964 			if (home && *home) {
4965 				char *exrc = concat_path_file(home, ".exrc");
4966 				struct stat st;
4967 
4968 				// .exrc must belong to and only be writable by user
4969 				if (stat(exrc, &st) == 0) {
4970 					if ((st.st_mode & (S_IWGRP|S_IWOTH)) == 0
4971 					 && st.st_uid == getuid()
4972 					) {
4973 						cmds = xmalloc_open_read_close(exrc, NULL);
4974 					} else {
4975 						status_line_bold(".exrc: permission denied");
4976 					}
4977 				}
4978 				free(exrc);
4979 			}
4980 		}
4981 
4982 		if (cmds) {
4983 			init_text_buffer(NULL);
4984 			run_cmds(cmds);
4985 			free(cmds);
4986 		}
4987 	}
4988 #endif
4989 	// "Save cursor, use alternate screen buffer, clear screen"
4990 	write1(ESC"[?1049h");
4991 	// This is the main file handling loop
4992 	optind = 0;
4993 	while (1) {
4994 		edit_file(argv[optind]); // might be NULL on 1st iteration
4995 		// NB: optind can be changed by ":next" and ":rewind" commands
4996 		optind++;
4997 		if (optind >= cmdline_filecnt)
4998 			break;
4999 	}
5000 	// "Use normal screen buffer, restore cursor"
5001 	write1(ESC"[?1049l");
5002 
5003 	return 0;
5004 }
5005