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