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