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