source: MondoRescue/branches/3.3/mindi-busybox/editors/vi.c@ 3621

Last change on this file since 3621 was 3621, checked in by Bruno Cornec, 7 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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