1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * how about mode lines: vi: set sw=8 ts=8:
17 * if mark[] values were line numbers rather than pointers
18 * it would be easier to change the mark when add/delete lines
19 * More intelligence in refresh()
20 * ":r !cmd" and "!cmd" to filter text through an external command
21 * A true "undo" facility
22 * An "ex" line oriented mode- maybe using "cmdedit"
30 #include <sys/ioctl.h>
38 #define vi_Version BB_VER " " BB_BT
40 #ifdef CONFIG_LOCALE_SUPPORT
41 #define Isprint(c) isprint((c))
43 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
46 #define MAX_SCR_COLS BUFSIZ
48 // Misc. non-Ascii keys that report an escape sequence
49 #define VI_K_UP 128 // cursor key Up
50 #define VI_K_DOWN 129 // cursor key Down
51 #define VI_K_RIGHT 130 // Cursor Key Right
52 #define VI_K_LEFT 131 // cursor key Left
53 #define VI_K_HOME 132 // Cursor Key Home
54 #define VI_K_END 133 // Cursor Key End
55 #define VI_K_INSERT 134 // Cursor Key Insert
56 #define VI_K_PAGEUP 135 // Cursor Key Page Up
57 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
58 #define VI_K_FUN1 137 // Function Key F1
59 #define VI_K_FUN2 138 // Function Key F2
60 #define VI_K_FUN3 139 // Function Key F3
61 #define VI_K_FUN4 140 // Function Key F4
62 #define VI_K_FUN5 141 // Function Key F5
63 #define VI_K_FUN6 142 // Function Key F6
64 #define VI_K_FUN7 143 // Function Key F7
65 #define VI_K_FUN8 144 // Function Key F8
66 #define VI_K_FUN9 145 // Function Key F9
67 #define VI_K_FUN10 146 // Function Key F10
68 #define VI_K_FUN11 147 // Function Key F11
69 #define VI_K_FUN12 148 // Function Key F12
71 /* vt102 typical ESC sequence */
72 /* terminal standout start/normal ESC sequence */
73 static const char SOs[] = "\033[7m";
74 static const char SOn[] = "\033[0m";
75 /* terminal bell sequence */
76 static const char bell[] = "\007";
77 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
78 static const char Ceol[] = "\033[0K";
79 static const char Ceos [] = "\033[0J";
80 /* Cursor motion arbitrary destination ESC sequence */
81 static const char CMrc[] = "\033[%d;%dH";
82 /* Cursor motion up and down ESC sequence */
83 static const char CMup[] = "\033[A";
84 static const char CMdown[] = "\n";
90 FORWARD = 1, // code depends on "1" for array index
91 BACK = -1, // code depends on "-1" for array index
92 LIMITED = 0, // how much of text[] in char_search
93 FULL = 1, // how much of text[] in char_search
95 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
96 S_TO_WS = 2, // used in skip_thing() for moving "dot"
97 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
98 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
99 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
102 typedef unsigned char Byte;
104 static int vi_setops;
105 #define VI_AUTOINDENT 1
106 #define VI_SHOWMATCH 2
107 #define VI_IGNORECASE 4
108 #define VI_ERR_METHOD 8
109 #define autoindent (vi_setops & VI_AUTOINDENT)
110 #define showmatch (vi_setops & VI_SHOWMATCH )
111 #define ignorecase (vi_setops & VI_IGNORECASE)
112 /* indicate error with beep or flash */
113 #define err_method (vi_setops & VI_ERR_METHOD)
116 static int editing; // >0 while we are editing a file
117 static int cmd_mode; // 0=command 1=insert 2=replace
118 static int file_modified; // buffer contents changed
119 static int last_file_modified = -1;
120 static int fn_start; // index of first cmd line file name
121 static int save_argc; // how many file names on cmd line
122 static int cmdcnt; // repetition count
123 static fd_set rfds; // use select() for small sleeps
124 static struct timeval tv; // use select() for small sleeps
125 static int rows, columns; // the terminal screen is this size
126 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
127 static Byte *status_buffer; // mesages to the user
128 #define STATUS_BUFFER_LEN 200
129 static int have_status_msg; // is default edit status needed?
130 static int last_status_cksum; // hash of current status line
131 static Byte *cfn; // previous, current, and next file name
132 static Byte *text, *end, *textend; // pointers to the user data in memory
133 static Byte *screen; // pointer to the virtual screen buffer
134 static int screensize; // and its size
135 static Byte *screenbegin; // index into text[], of top line on the screen
136 static Byte *dot; // where all the action takes place
138 static struct termios term_orig, term_vi; // remember what the cooked mode was
139 static Byte erase_char; // the users erase character
140 static Byte last_input_char; // last char read from user
141 static Byte last_forward_char; // last char searched for with 'f'
143 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
144 static int last_row; // where the cursor was last moved to
145 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
146 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
147 static jmp_buf restart; // catch_sig()
148 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
149 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
152 #ifdef CONFIG_FEATURE_VI_DOT_CMD
153 static int adding2q; // are we currently adding user input to q
154 static Byte *last_modifying_cmd; // last modifying cmd for "."
155 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
156 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
157 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
158 static Byte *modifying_cmds; // cmds that modify text[]
159 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
160 #ifdef CONFIG_FEATURE_VI_READONLY
161 static int vi_readonly, readonly;
162 #endif /* CONFIG_FEATURE_VI_READONLY */
163 #ifdef CONFIG_FEATURE_VI_YANKMARK
164 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
165 static int YDreg, Ureg; // default delete register and orig line for "U"
166 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
167 static Byte *context_start, *context_end;
168 #endif /* CONFIG_FEATURE_VI_YANKMARK */
169 #ifdef CONFIG_FEATURE_VI_SEARCH
170 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
171 #endif /* CONFIG_FEATURE_VI_SEARCH */
174 static void edit_file(Byte *); // edit one file
175 static void do_cmd(Byte); // execute a command
176 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
177 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
178 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
179 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
180 static Byte *next_line(Byte *); // return pointer to next line B-o-l
181 static Byte *end_screen(void); // get pointer to last char on screen
182 static int count_lines(Byte *, Byte *); // count line from start to stop
183 static Byte *find_line(int); // find begining of line #li
184 static Byte *move_to_col(Byte *, int); // move "p" to column l
185 static int isblnk(Byte); // is the char a blank or tab
186 static void dot_left(void); // move dot left- dont leave line
187 static void dot_right(void); // move dot right- dont leave line
188 static void dot_begin(void); // move dot to B-o-l
189 static void dot_end(void); // move dot to E-o-l
190 static void dot_next(void); // move dot to next line B-o-l
191 static void dot_prev(void); // move dot to prev line B-o-l
192 static void dot_scroll(int, int); // move the screen up or down
193 static void dot_skip_over_ws(void); // move dot pat WS
194 static void dot_delete(void); // delete the char at 'dot'
195 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
196 static Byte *new_screen(int, int); // malloc virtual screen memory
197 static Byte *new_text(int); // malloc memory for text[] buffer
198 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
199 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
200 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
201 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
202 static Byte *skip_thing(Byte *, int, int, int); // skip some object
203 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
204 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
205 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
206 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
207 static void show_help(void); // display some help info
208 static void rawmode(void); // set "raw" mode on tty
209 static void cookmode(void); // return to "cooked" mode on tty
210 static int mysleep(int); // sleep for 'h' 1/100 seconds
211 static Byte readit(void); // read (maybe cursor) key from stdin
212 static Byte get_one_char(void); // read 1 char from stdin
213 static int file_size(const Byte *); // what is the byte size of "fn"
214 static int file_insert(Byte *, Byte *, int);
215 static int file_write(Byte *, Byte *, Byte *);
216 static void place_cursor(int, int, int);
217 static void screen_erase(void);
218 static void clear_to_eol(void);
219 static void clear_to_eos(void);
220 static void standout_start(void); // send "start reverse video" sequence
221 static void standout_end(void); // send "end reverse video" sequence
222 static void flash(int); // flash the terminal screen
223 static void show_status_line(void); // put a message on the bottom line
224 static void psb(const char *, ...); // Print Status Buf
225 static void psbs(const char *, ...); // Print Status Buf in standout mode
226 static void ni(Byte *); // display messages
227 static int format_edit_status(void); // format file status on status line
228 static void redraw(int); // force a full screen refresh
229 static void format_line(Byte*, Byte*, int);
230 static void refresh(int); // update the terminal from screen[]
232 static void Indicate_Error(void); // use flash or beep to indicate error
233 #define indicate_error(c) Indicate_Error()
234 static void Hit_Return(void);
236 #ifdef CONFIG_FEATURE_VI_SEARCH
237 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
238 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
239 #endif /* CONFIG_FEATURE_VI_SEARCH */
240 #ifdef CONFIG_FEATURE_VI_COLON
241 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
242 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
243 static void colon(Byte *); // execute the "colon" mode cmds
244 #endif /* CONFIG_FEATURE_VI_COLON */
245 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
246 static void winch_sig(int); // catch window size changes
247 static void suspend_sig(int); // catch ctrl-Z
248 static void catch_sig(int); // catch ctrl-C and alarm time-outs
249 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
250 #ifdef CONFIG_FEATURE_VI_DOT_CMD
251 static void start_new_cmd_q(Byte); // new queue for command
252 static void end_cmd_q(void); // stop saving input chars
253 #else /* CONFIG_FEATURE_VI_DOT_CMD */
255 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
256 #ifdef CONFIG_FEATURE_VI_SETOPTS
257 static void showmatching(Byte *); // show the matching pair () [] {}
258 #endif /* CONFIG_FEATURE_VI_SETOPTS */
259 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
260 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
261 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
262 #ifdef CONFIG_FEATURE_VI_YANKMARK
263 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
264 static Byte what_reg(void); // what is letter of current YDreg
265 static void check_context(Byte); // remember context for '' command
266 #endif /* CONFIG_FEATURE_VI_YANKMARK */
267 #ifdef CONFIG_FEATURE_VI_CRASHME
268 static void crash_dummy();
269 static void crash_test();
270 static int crashme = 0;
271 #endif /* CONFIG_FEATURE_VI_CRASHME */
274 static void write1(const char *out)
279 int vi_main(int argc, char **argv)
282 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
284 #ifdef CONFIG_FEATURE_VI_YANKMARK
286 #endif /* CONFIG_FEATURE_VI_YANKMARK */
287 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
290 #ifdef CONFIG_FEATURE_VI_CRASHME
291 (void) srand((long) my_pid);
292 #endif /* CONFIG_FEATURE_VI_CRASHME */
294 status_buffer = (Byte *)STATUS_BUFFER;
295 last_status_cksum = 0;
297 #ifdef CONFIG_FEATURE_VI_READONLY
298 vi_readonly = readonly = FALSE;
299 if (strncmp(argv[0], "view", 4) == 0) {
303 #endif /* CONFIG_FEATURE_VI_READONLY */
304 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
305 #ifdef CONFIG_FEATURE_VI_YANKMARK
306 for (i = 0; i < 28; i++) {
308 } // init the yank regs
309 #endif /* CONFIG_FEATURE_VI_YANKMARK */
310 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
311 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
312 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
314 // 1- process $HOME/.exrc file
315 // 2- process EXINIT variable from environment
316 // 3- process command line args
317 while ((c = getopt(argc, argv, "hCR")) != -1) {
319 #ifdef CONFIG_FEATURE_VI_CRASHME
323 #endif /* CONFIG_FEATURE_VI_CRASHME */
324 #ifdef CONFIG_FEATURE_VI_READONLY
325 case 'R': // Read-only flag
329 #endif /* CONFIG_FEATURE_VI_READONLY */
330 //case 'r': // recover flag- ignore- we don't use tmp file
331 //case 'x': // encryption flag- ignore
332 //case 'c': // execute command first
333 //case 'h': // help -- just use default
340 // The argv array can be used by the ":next" and ":rewind" commands
342 fn_start = optind; // remember first file name for :next and :rew
345 //----- This is the main file handling loop --------------
346 if (optind >= argc) {
347 editing = 1; // 0= exit, 1= one file, 2= multiple files
350 for (; optind < argc; optind++) {
351 editing = 1; // 0=exit, 1=one file, 2+ =many files
353 cfn = (Byte *) bb_xstrdup(argv[optind]);
357 //-----------------------------------------------------------
362 static void edit_file(Byte * fn)
367 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
369 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
370 #ifdef CONFIG_FEATURE_VI_YANKMARK
371 static Byte *cur_line;
372 #endif /* CONFIG_FEATURE_VI_YANKMARK */
378 if (ENABLE_FEATURE_VI_WIN_RESIZE)
379 get_terminal_width_height(0, &columns, &rows);
380 new_screen(rows, columns); // get memory for virtual screen
382 cnt = file_size(fn); // file size
383 size = 2 * cnt; // 200% of file size
384 new_text(size); // get a text[] buffer
385 screenbegin = dot = end = text;
387 ch= file_insert(fn, text, cnt);
390 (void) char_insert(text, '\n'); // start empty buf with dummy line
393 last_file_modified = -1;
394 #ifdef CONFIG_FEATURE_VI_YANKMARK
395 YDreg = 26; // default Yank/Delete reg
396 Ureg = 27; // hold orig line for "U" cmd
397 for (cnt = 0; cnt < 28; cnt++) {
400 mark[26] = mark[27] = text; // init "previous context"
401 #endif /* CONFIG_FEATURE_VI_YANKMARK */
403 last_forward_char = last_input_char = '\0';
407 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
409 signal(SIGWINCH, winch_sig);
410 signal(SIGTSTP, suspend_sig);
411 sig = setjmp(restart);
413 screenbegin = dot = text;
415 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
418 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
421 offset = 0; // no horizontal offset
423 #ifdef CONFIG_FEATURE_VI_DOT_CMD
424 free(last_modifying_cmd);
426 ioq = ioq_start = last_modifying_cmd = 0;
428 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
429 redraw(FALSE); // dont force every col re-draw
432 //------This is the main Vi cmd handling loop -----------------------
433 while (editing > 0) {
434 #ifdef CONFIG_FEATURE_VI_CRASHME
436 if ((end - text) > 1) {
437 crash_dummy(); // generate a random command
441 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
445 #endif /* CONFIG_FEATURE_VI_CRASHME */
446 last_input_char = c = get_one_char(); // get a cmd from user
447 #ifdef CONFIG_FEATURE_VI_YANKMARK
448 // save a copy of the current line- for the 'U" command
449 if (begin_line(dot) != cur_line) {
450 cur_line = begin_line(dot);
451 text_yank(begin_line(dot), end_line(dot), Ureg);
453 #endif /* CONFIG_FEATURE_VI_YANKMARK */
454 #ifdef CONFIG_FEATURE_VI_DOT_CMD
455 // These are commands that change text[].
456 // Remember the input for the "." command
457 if (!adding2q && ioq_start == 0
458 && strchr((char *) modifying_cmds, c) != NULL) {
461 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
462 do_cmd(c); // execute the user command
464 // poll to see if there is input already waiting. if we are
465 // not able to display output fast enough to keep up, skip
466 // the display update until we catch up with input.
467 if (mysleep(0) == 0) {
468 // no input pending- so update output
472 #ifdef CONFIG_FEATURE_VI_CRASHME
474 crash_test(); // test editor variables
475 #endif /* CONFIG_FEATURE_VI_CRASHME */
477 //-------------------------------------------------------------------
479 place_cursor(rows, 0, FALSE); // go to bottom of screen
480 clear_to_eol(); // Erase to end of line
484 //----- The Colon commands -------------------------------------
485 #ifdef CONFIG_FEATURE_VI_COLON
486 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
491 #ifdef CONFIG_FEATURE_VI_YANKMARK
493 #endif /* CONFIG_FEATURE_VI_YANKMARK */
494 #ifdef CONFIG_FEATURE_VI_SEARCH
495 Byte *pat, buf[BUFSIZ];
496 #endif /* CONFIG_FEATURE_VI_SEARCH */
498 *addr = -1; // assume no addr
499 if (*p == '.') { // the current line
502 *addr = count_lines(text, q);
503 #ifdef CONFIG_FEATURE_VI_YANKMARK
504 } else if (*p == '\'') { // is this a mark addr
508 if (c >= 'a' && c <= 'z') {
512 if (q != NULL) { // is mark valid
513 *addr = count_lines(text, q); // count lines
516 #endif /* CONFIG_FEATURE_VI_YANKMARK */
517 #ifdef CONFIG_FEATURE_VI_SEARCH
518 } else if (*p == '/') { // a search pattern
526 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
529 q = char_search(dot, pat, FORWARD, FULL);
531 *addr = count_lines(text, q);
534 #endif /* CONFIG_FEATURE_VI_SEARCH */
535 } else if (*p == '$') { // the last line in file
537 q = begin_line(end - 1);
538 *addr = count_lines(text, q);
539 } else if (isdigit(*p)) { // specific line number
540 sscanf((char *) p, "%d%n", addr, &st);
542 } else { // I don't reconise this
543 // unrecognised address- assume -1
549 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
551 //----- get the address' i.e., 1,3 'a,'b -----
552 // get FIRST addr, if present
554 p++; // skip over leading spaces
555 if (*p == '%') { // alias for 1,$
558 *e = count_lines(text, end-1);
561 p = get_one_address(p, b);
564 if (*p == ',') { // is there a address separator
568 // get SECOND addr, if present
569 p = get_one_address(p, e);
573 p++; // skip over trailing spaces
577 #ifdef CONFIG_FEATURE_VI_SETOPTS
578 static void setops(const Byte *args, const char *opname, int flg_no,
579 const char *short_opname, int opt)
581 const char *a = (char *) args + flg_no;
582 int l = strlen(opname) - 1; /* opname have + ' ' */
584 if (strncasecmp(a, opname, l) == 0 ||
585 strncasecmp(a, short_opname, 2) == 0) {
594 static void colon(Byte * buf)
596 Byte c, *orig_buf, *buf1, *q, *r;
597 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
598 int i, l, li, ch, st, b, e;
599 int useforce = FALSE, forced = FALSE;
602 // :3154 // if (-e line 3154) goto it else stay put
603 // :4,33w! foo // write a portion of buffer to file "foo"
604 // :w // write all of buffer to current file
606 // :q! // quit- dont care about modified file
607 // :'a,'z!sort -u // filter block through sort
608 // :'f // goto mark "f"
609 // :'fl // list literal the mark "f" line
610 // :.r bar // read file "bar" into buffer before dot
611 // :/123/,/abc/d // delete lines from "123" line to "abc" line
612 // :/xyz/ // goto the "xyz" line
613 // :s/find/replace/ // substitute pattern "find" with "replace"
614 // :!<cmd> // run <cmd> then return
617 if (strlen((char *) buf) <= 0)
620 buf++; // move past the ':'
622 li = st = ch = i = 0;
624 q = text; // assume 1,$ for the range
626 li = count_lines(text, end - 1);
627 fn = cfn; // default to current file
628 memset(cmd, '\0', BUFSIZ); // clear cmd[]
629 memset(args, '\0', BUFSIZ); // clear args[]
631 // look for optional address(es) :. :1 :1,9 :'q,'a :%
632 buf = get_address(buf, &b, &e);
634 // remember orig command line
637 // get the COMMAND into cmd[]
639 while (*buf != '\0') {
647 strcpy((char *) args, (char *) buf);
648 buf1 = (Byte*)last_char_is((char *)cmd, '!');
651 *buf1 = '\0'; // get rid of !
654 // if there is only one addr, then the addr
655 // is the line number of the single line the
656 // user wants. So, reset the end
657 // pointer to point at end of the "b" line
658 q = find_line(b); // what line is #b
663 // we were given two addrs. change the
664 // end pointer to the addr given by user.
665 r = find_line(e); // what line is #e
669 // ------------ now look for the command ------------
670 i = strlen((char *) cmd);
671 if (i == 0) { // :123CR goto line #123
673 dot = find_line(b); // what line is #b
676 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
677 // :!ls run the <cmd>
678 (void) alarm(0); // wait for input- no alarms
679 place_cursor(rows - 1, 0, FALSE); // go to Status line
680 clear_to_eol(); // clear the line
682 system((char*)(orig_buf+1)); // run the cmd
684 Hit_Return(); // let user see results
685 (void) alarm(3); // done waiting for input
686 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
687 if (b < 0) { // no addr given- use defaults
688 b = e = count_lines(text, dot);
691 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
692 if (b < 0) { // no addr given- use defaults
693 q = begin_line(dot); // assume .,. for the range
696 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
698 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
701 // don't edit, if the current file has been modified
702 if (file_modified && ! useforce) {
703 psbs("No write since last change (:edit! overrides)");
706 if (strlen((char*)args) > 0) {
707 // the user supplied a file name
709 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
710 // no user supplied name- use the current filename
714 // no user file name, no current name- punt
715 psbs("No current filename");
719 // see if file exists- if not, its just a new file request
720 if ((sr=stat((char*)fn, &st_buf)) < 0) {
721 // This is just a request for a new file creation.
722 // The file_insert below will fail but we get
723 // an empty buffer with a file name. Then the "write"
724 // command can do the create.
726 if ((st_buf.st_mode & (S_IFREG)) == 0) {
727 // This is not a regular file
728 psbs("\"%s\" is not a regular file", fn);
731 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
732 // dont have any read permissions
733 psbs("\"%s\" is not readable", fn);
738 // There is a read-able regular file
739 // make this the current file
740 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
741 free(cfn); // free the old name
742 cfn = q; // remember new cfn
745 // delete all the contents of text[]
746 new_text(2 * file_size(fn));
747 screenbegin = dot = end = text;
750 ch = file_insert(fn, text, file_size(fn));
753 // start empty buf with dummy line
754 (void) char_insert(text, '\n');
758 last_file_modified = -1;
759 #ifdef CONFIG_FEATURE_VI_YANKMARK
760 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
761 free(reg[Ureg]); // free orig line reg- for 'U'
764 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
765 free(reg[YDreg]); // free default yank/delete register
768 for (li = 0; li < 28; li++) {
771 #endif /* CONFIG_FEATURE_VI_YANKMARK */
772 // how many lines in text[]?
773 li = count_lines(text, end - 1);
775 #ifdef CONFIG_FEATURE_VI_READONLY
777 #endif /* CONFIG_FEATURE_VI_READONLY */
779 (sr < 0 ? " [New file]" : ""),
780 #ifdef CONFIG_FEATURE_VI_READONLY
781 ((vi_readonly || readonly) ? " [Read only]" : ""),
782 #endif /* CONFIG_FEATURE_VI_READONLY */
784 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
785 if (b != -1 || e != -1) {
786 ni((Byte *) "No address allowed on this command");
789 if (strlen((char *) args) > 0) {
790 // user wants a new filename
792 cfn = (Byte *) bb_xstrdup((char *) args);
794 // user wants file status info
795 last_status_cksum = 0; // force status update
797 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
798 // print out values of all features
799 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
800 clear_to_eol(); // clear the line
805 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
806 if (b < 0) { // no addr given- use defaults
807 q = begin_line(dot); // assume .,. for the range
810 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
811 clear_to_eol(); // clear the line
813 for (; q <= r; q++) {
817 c_is_no_print = c > 127 && !Isprint(c);
824 } else if (c < ' ' || c == 127) {
835 #ifdef CONFIG_FEATURE_VI_SET
837 #endif /* CONFIG_FEATURE_VI_SET */
839 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
840 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
842 // force end of argv list
849 // don't exit if the file been modified
851 psbs("No write since last change (:%s! overrides)",
852 (*cmd == 'q' ? "quit" : "next"));
855 // are there other file to edit
856 if (*cmd == 'q' && optind < save_argc - 1) {
857 psbs("%d more file to edit", (save_argc - optind - 1));
860 if (*cmd == 'n' && optind >= save_argc - 1) {
861 psbs("No more files to edit");
865 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
867 if (strlen((char *) fn) <= 0) {
868 psbs("No filename given");
871 if (b < 0) { // no addr given- use defaults
872 q = begin_line(dot); // assume "dot"
874 // read after current line- unless user said ":0r foo"
877 #ifdef CONFIG_FEATURE_VI_READONLY
878 l= readonly; // remember current files' status
880 ch = file_insert(fn, q, file_size(fn));
881 #ifdef CONFIG_FEATURE_VI_READONLY
885 goto vc1; // nothing was inserted
886 // how many lines in text[]?
887 li = count_lines(q, q + ch - 1);
889 #ifdef CONFIG_FEATURE_VI_READONLY
891 #endif /* CONFIG_FEATURE_VI_READONLY */
893 #ifdef CONFIG_FEATURE_VI_READONLY
894 ((vi_readonly || readonly) ? " [Read only]" : ""),
895 #endif /* CONFIG_FEATURE_VI_READONLY */
898 // if the insert is before "dot" then we need to update
903 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
904 if (file_modified && ! useforce) {
905 psbs("No write since last change (:rewind! overrides)");
907 // reset the filenames to edit
908 optind = fn_start - 1;
911 #ifdef CONFIG_FEATURE_VI_SET
912 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
913 i = 0; // offset into args
914 if (strlen((char *) args) == 0) {
915 // print out values of all options
916 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
917 clear_to_eol(); // clear the line
918 printf("----------------------------------------\r\n");
919 #ifdef CONFIG_FEATURE_VI_SETOPTS
922 printf("autoindent ");
928 printf("ignorecase ");
931 printf("showmatch ");
932 printf("tabstop=%d ", tabstop);
933 #endif /* CONFIG_FEATURE_VI_SETOPTS */
937 if (strncasecmp((char *) args, "no", 2) == 0)
938 i = 2; // ":set noautoindent"
939 #ifdef CONFIG_FEATURE_VI_SETOPTS
940 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
941 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
942 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
943 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
944 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
945 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
946 if (ch > 0 && ch < columns - 1)
949 #endif /* CONFIG_FEATURE_VI_SETOPTS */
950 #endif /* CONFIG_FEATURE_VI_SET */
951 #ifdef CONFIG_FEATURE_VI_SEARCH
952 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
956 // F points to the "find" pattern
957 // R points to the "replace" pattern
958 // replace the cmd line delimiters "/" with NULLs
959 gflag = 0; // global replace flag
960 c = orig_buf[1]; // what is the delimiter
961 F = orig_buf + 2; // start of "find"
962 R = (Byte *) strchr((char *) F, c); // middle delimiter
963 if (!R) goto colon_s_fail;
964 *R++ = '\0'; // terminate "find"
965 buf1 = (Byte *) strchr((char *) R, c);
966 if (!buf1) goto colon_s_fail;
967 *buf1++ = '\0'; // terminate "replace"
968 if (*buf1 == 'g') { // :s/foo/bar/g
970 gflag++; // turn on gflag
973 if (b < 0) { // maybe :s/foo/bar/
974 q = begin_line(dot); // start with cur line
975 b = count_lines(text, q); // cur line number
978 e = b; // maybe :.s/foo/bar/
979 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
980 ls = q; // orig line start
982 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
984 // we found the "find" pattern- delete it
985 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
986 // inset the "replace" patern
987 (void) string_insert(buf1, R); // insert the string
988 // check for "global" :s/foo/bar/g
990 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
991 q = buf1 + strlen((char *) R);
992 goto vc4; // don't let q move past cur line
998 #endif /* CONFIG_FEATURE_VI_SEARCH */
999 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1000 psb("%s", vi_Version);
1001 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
1002 || strncasecmp((char *) cmd, "wq", i) == 0
1003 || strncasecmp((char *) cmd, "wn", i) == 0
1004 || strncasecmp((char *) cmd, "x", i) == 0) {
1005 // is there a file name to write to?
1006 if (strlen((char *) args) > 0) {
1009 #ifdef CONFIG_FEATURE_VI_READONLY
1010 if ((vi_readonly || readonly) && ! useforce) {
1011 psbs("\"%s\" File is read only", fn);
1014 #endif /* CONFIG_FEATURE_VI_READONLY */
1015 // how many lines in text[]?
1016 li = count_lines(q, r);
1018 // see if file exists- if not, its just a new file request
1020 // if "fn" is not write-able, chmod u+w
1021 // sprintf(syscmd, "chmod u+w %s", fn);
1025 l = file_write(fn, q, r);
1026 if (useforce && forced) {
1028 // sprintf(syscmd, "chmod u-w %s", fn);
1034 psbs("Write error: %s", strerror(errno));
1036 psb("\"%s\" %dL, %dC", fn, li, l);
1037 if (q == text && r == end - 1 && l == ch) {
1039 last_file_modified = -1;
1041 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1042 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1047 #ifdef CONFIG_FEATURE_VI_READONLY
1049 #endif /* CONFIG_FEATURE_VI_READONLY */
1050 #ifdef CONFIG_FEATURE_VI_YANKMARK
1051 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1052 if (b < 0) { // no addr given- use defaults
1053 q = begin_line(dot); // assume .,. for the range
1056 text_yank(q, r, YDreg);
1057 li = count_lines(q, r);
1058 psb("Yank %d lines (%d chars) into [%c]",
1059 li, strlen((char *) reg[YDreg]), what_reg());
1060 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1066 dot = bound_dot(dot); // make sure "dot" is valid
1068 #ifdef CONFIG_FEATURE_VI_SEARCH
1070 psb(":s expression missing delimiters");
1074 #endif /* CONFIG_FEATURE_VI_COLON */
1076 static void Hit_Return(void)
1080 standout_start(); // start reverse video
1081 write1("[Hit return to continue]");
1082 standout_end(); // end reverse video
1083 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1085 redraw(TRUE); // force redraw all
1088 //----- Synchronize the cursor to Dot --------------------------
1089 static void sync_cursor(Byte * d, int *row, int *col)
1091 Byte *beg_cur, *end_cur; // begin and end of "d" line
1092 Byte *beg_scr, *end_scr; // begin and end of screen
1096 beg_cur = begin_line(d); // first char of cur line
1097 end_cur = end_line(d); // last char of cur line
1099 beg_scr = end_scr = screenbegin; // first char of screen
1100 end_scr = end_screen(); // last char of screen
1102 if (beg_cur < screenbegin) {
1103 // "d" is before top line on screen
1104 // how many lines do we have to move
1105 cnt = count_lines(beg_cur, screenbegin);
1107 screenbegin = beg_cur;
1108 if (cnt > (rows - 1) / 2) {
1109 // we moved too many lines. put "dot" in middle of screen
1110 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1111 screenbegin = prev_line(screenbegin);
1114 } else if (beg_cur > end_scr) {
1115 // "d" is after bottom line on screen
1116 // how many lines do we have to move
1117 cnt = count_lines(end_scr, beg_cur);
1118 if (cnt > (rows - 1) / 2)
1119 goto sc1; // too many lines
1120 for (ro = 0; ro < cnt - 1; ro++) {
1121 // move screen begin the same amount
1122 screenbegin = next_line(screenbegin);
1123 // now, move the end of screen
1124 end_scr = next_line(end_scr);
1125 end_scr = end_line(end_scr);
1128 // "d" is on screen- find out which row
1130 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1136 // find out what col "d" is on
1138 do { // drive "co" to correct column
1139 if (*tp == '\n' || *tp == '\0')
1143 co += ((tabstop - 1) - (co % tabstop));
1144 } else if (*tp < ' ' || *tp == 127) {
1145 co++; // display as ^X, use 2 columns
1147 } while (tp++ < d && ++co);
1149 // "co" is the column where "dot" is.
1150 // The screen has "columns" columns.
1151 // The currently displayed columns are 0+offset -- columns+ofset
1152 // |-------------------------------------------------------------|
1154 // offset | |------- columns ----------------|
1156 // If "co" is already in this range then we do not have to adjust offset
1157 // but, we do have to subtract the "offset" bias from "co".
1158 // If "co" is outside this range then we have to change "offset".
1159 // If the first char of a line is a tab the cursor will try to stay
1160 // in column 7, but we have to set offset to 0.
1162 if (co < 0 + offset) {
1165 if (co >= columns + offset) {
1166 offset = co - columns + 1;
1168 // if the first char of the line is a tab, and "dot" is sitting on it
1169 // force offset to 0.
1170 if (d == beg_cur && *d == '\t') {
1179 //----- Text Movement Routines ---------------------------------
1180 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1182 while (p > text && p[-1] != '\n')
1183 p--; // go to cur line B-o-l
1187 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1189 while (p < end - 1 && *p != '\n')
1190 p++; // go to cur line E-o-l
1194 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1196 while (p < end - 1 && *p != '\n')
1197 p++; // go to cur line E-o-l
1198 // Try to stay off of the Newline
1199 if (*p == '\n' && (p - begin_line(p)) > 0)
1204 static Byte *prev_line(Byte * p) // return pointer first char prev line
1206 p = begin_line(p); // goto begining of cur line
1207 if (p[-1] == '\n' && p > text)
1208 p--; // step to prev line
1209 p = begin_line(p); // goto begining of prev line
1213 static Byte *next_line(Byte * p) // return pointer first char next line
1216 if (*p == '\n' && p < end - 1)
1217 p++; // step to next line
1221 //----- Text Information Routines ------------------------------
1222 static Byte *end_screen(void)
1227 // find new bottom line
1229 for (cnt = 0; cnt < rows - 2; cnt++)
1235 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1240 if (stop < start) { // start and stop are backwards- reverse them
1246 stop = end_line(stop); // get to end of this line
1247 for (q = start; q <= stop && q <= end - 1; q++) {
1254 static Byte *find_line(int li) // find begining of line #li
1258 for (q = text; li > 1; li--) {
1264 //----- Dot Movement Routines ----------------------------------
1265 static void dot_left(void)
1267 if (dot > text && dot[-1] != '\n')
1271 static void dot_right(void)
1273 if (dot < end - 1 && *dot != '\n')
1277 static void dot_begin(void)
1279 dot = begin_line(dot); // return pointer to first char cur line
1282 static void dot_end(void)
1284 dot = end_line(dot); // return pointer to last char cur line
1287 static Byte *move_to_col(Byte * p, int l)
1294 if (*p == '\n' || *p == '\0')
1298 co += ((tabstop - 1) - (co % tabstop));
1299 } else if (*p < ' ' || *p == 127) {
1300 co++; // display as ^X, use 2 columns
1302 } while (++co <= l && p++ < end);
1306 static void dot_next(void)
1308 dot = next_line(dot);
1311 static void dot_prev(void)
1313 dot = prev_line(dot);
1316 static void dot_scroll(int cnt, int dir)
1320 for (; cnt > 0; cnt--) {
1323 // ctrl-Y scroll up one line
1324 screenbegin = prev_line(screenbegin);
1327 // ctrl-E scroll down one line
1328 screenbegin = next_line(screenbegin);
1331 // make sure "dot" stays on the screen so we dont scroll off
1332 if (dot < screenbegin)
1334 q = end_screen(); // find new bottom line
1336 dot = begin_line(q); // is dot is below bottom line?
1340 static void dot_skip_over_ws(void)
1343 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1347 static void dot_delete(void) // delete the char at 'dot'
1349 (void) text_hole_delete(dot, dot);
1352 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1354 if (p >= end && end > text) {
1356 indicate_error('1');
1360 indicate_error('2');
1365 //----- Helper Utility Routines --------------------------------
1367 //----------------------------------------------------------------
1368 //----- Char Routines --------------------------------------------
1369 /* Chars that are part of a word-
1370 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1371 * Chars that are Not part of a word (stoppers)
1372 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1373 * Chars that are WhiteSpace
1374 * TAB NEWLINE VT FF RETURN SPACE
1375 * DO NOT COUNT NEWLINE AS WHITESPACE
1378 static Byte *new_screen(int ro, int co)
1383 screensize = ro * co + 8;
1384 screen = (Byte *) xmalloc(screensize);
1385 // initialize the new screen. assume this will be a empty file.
1387 // non-existent text[] lines start with a tilde (~).
1388 for (li = 1; li < ro - 1; li++) {
1389 screen[(li * co) + 0] = '~';
1394 static Byte *new_text(int size)
1397 size = 10240; // have a minimum size for new files
1399 text = (Byte *) xmalloc(size + 8);
1400 memset(text, '\0', size); // clear new text[]
1401 //text += 4; // leave some room for "oops"
1402 textend = text + size - 1;
1403 //textend -= 4; // leave some root for "oops"
1407 #ifdef CONFIG_FEATURE_VI_SEARCH
1408 static int mycmp(Byte * s1, Byte * s2, int len)
1412 i = strncmp((char *) s1, (char *) s2, len);
1413 #ifdef CONFIG_FEATURE_VI_SETOPTS
1415 i = strncasecmp((char *) s1, (char *) s2, len);
1417 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1421 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1423 #ifndef REGEX_SEARCH
1427 len = strlen((char *) pat);
1428 if (dir == FORWARD) {
1429 stop = end - 1; // assume range is p - end-1
1430 if (range == LIMITED)
1431 stop = next_line(p); // range is to next line
1432 for (start = p; start < stop; start++) {
1433 if (mycmp(start, pat, len) == 0) {
1437 } else if (dir == BACK) {
1438 stop = text; // assume range is text - p
1439 if (range == LIMITED)
1440 stop = prev_line(p); // range is to prev line
1441 for (start = p - len; start >= stop; start--) {
1442 if (mycmp(start, pat, len) == 0) {
1447 // pattern not found
1449 #else /*REGEX_SEARCH */
1451 struct re_pattern_buffer preg;
1455 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1461 // assume a LIMITED forward search
1469 // count the number of chars to search over, forward or backward
1473 // RANGE could be negative if we are searching backwards
1476 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1478 // The pattern was not compiled
1479 psbs("bad search pattern: \"%s\": %s", pat, q);
1480 i = 0; // return p if pattern not compiled
1490 // search for the compiled pattern, preg, in p[]
1491 // range < 0- search backward
1492 // range > 0- search forward
1494 // re_search() < 0 not found or error
1495 // re_search() > 0 index of found pattern
1496 // struct pattern char int int int struct reg
1497 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1498 i = re_search(&preg, q, size, 0, range, 0);
1501 i = 0; // return NULL if pattern not found
1504 if (dir == FORWARD) {
1510 #endif /*REGEX_SEARCH */
1512 #endif /* CONFIG_FEATURE_VI_SEARCH */
1514 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1516 if (c == 22) { // Is this an ctrl-V?
1517 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1518 p--; // backup onto ^
1519 refresh(FALSE); // show the ^
1523 file_modified++; // has the file been modified
1524 } else if (c == 27) { // Is this an ESC?
1527 end_cmd_q(); // stop adding to q
1528 last_status_cksum = 0; // force status update
1529 if ((p[-1] != '\n') && (dot>text)) {
1532 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1534 if ((p[-1] != '\n') && (dot>text)) {
1536 p = text_hole_delete(p, p); // shrink buffer 1 char
1539 // insert a char into text[]
1540 Byte *sp; // "save p"
1543 c = '\n'; // translate \r to \n
1544 sp = p; // remember addr of insert
1545 p = stupid_insert(p, c); // insert the char
1546 #ifdef CONFIG_FEATURE_VI_SETOPTS
1547 if (showmatch && strchr(")]}", *sp) != NULL) {
1550 if (autoindent && c == '\n') { // auto indent the new line
1553 q = prev_line(p); // use prev line as templet
1554 for (; isblnk(*q); q++) {
1555 p = stupid_insert(p, *q); // insert the char
1558 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1563 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1565 p = text_hole_make(p, 1);
1568 file_modified++; // has the file been modified
1574 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1576 Byte *save_dot, *p, *q;
1582 if (strchr("cdy><", c)) {
1583 // these cmds operate on whole lines
1584 p = q = begin_line(p);
1585 for (cnt = 1; cnt < cmdcnt; cnt++) {
1589 } else if (strchr("^%$0bBeEft", c)) {
1590 // These cmds operate on char positions
1591 do_cmd(c); // execute movement cmd
1593 } else if (strchr("wW", c)) {
1594 do_cmd(c); // execute movement cmd
1595 // if we are at the next word's first char
1596 // step back one char
1597 // but check the possibilities when it is true
1598 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1599 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1600 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1601 dot--; // move back off of next word
1602 if (dot > text && *dot == '\n')
1603 dot--; // stay off NL
1605 } else if (strchr("H-k{", c)) {
1606 // these operate on multi-lines backwards
1607 q = end_line(dot); // find NL
1608 do_cmd(c); // execute movement cmd
1611 } else if (strchr("L+j}\r\n", c)) {
1612 // these operate on multi-lines forwards
1613 p = begin_line(dot);
1614 do_cmd(c); // execute movement cmd
1615 dot_end(); // find NL
1618 c = 27; // error- return an ESC char
1631 static int st_test(Byte * p, int type, int dir, Byte * tested)
1641 if (type == S_BEFORE_WS) {
1643 test = ((!isspace(c)) || c == '\n');
1645 if (type == S_TO_WS) {
1647 test = ((!isspace(c)) || c == '\n');
1649 if (type == S_OVER_WS) {
1651 test = ((isspace(c)));
1653 if (type == S_END_PUNCT) {
1655 test = ((ispunct(c)));
1657 if (type == S_END_ALNUM) {
1659 test = ((isalnum(c)) || c == '_');
1665 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1669 while (st_test(p, type, dir, &c)) {
1670 // make sure we limit search to correct number of lines
1671 if (c == '\n' && --linecnt < 1)
1673 if (dir >= 0 && p >= end - 1)
1675 if (dir < 0 && p <= text)
1677 p += dir; // move to next char
1682 // find matching char of pair () [] {}
1683 static Byte *find_pair(Byte * p, Byte c)
1690 dir = 1; // assume forward
1714 for (q = p + dir; text <= q && q < end; q += dir) {
1715 // look for match, count levels of pairs (( ))
1717 level++; // increase pair levels
1719 level--; // reduce pair level
1721 break; // found matching pair
1724 q = NULL; // indicate no match
1728 #ifdef CONFIG_FEATURE_VI_SETOPTS
1729 // show the matching char of a pair, () [] {}
1730 static void showmatching(Byte * p)
1734 // we found half of a pair
1735 q = find_pair(p, *p); // get loc of matching char
1737 indicate_error('3'); // no matching char
1739 // "q" now points to matching pair
1740 save_dot = dot; // remember where we are
1741 dot = q; // go to new loc
1742 refresh(FALSE); // let the user see it
1743 (void) mysleep(40); // give user some time
1744 dot = save_dot; // go back to old loc
1748 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1750 // open a hole in text[]
1751 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1760 cnt = end - src; // the rest of buffer
1761 if (memmove(dest, src, cnt) != dest) {
1762 psbs("can't create room for new characters");
1764 memset(p, ' ', size); // clear new hole
1765 end = end + size; // adjust the new END
1766 file_modified++; // has the file been modified
1771 // close a hole in text[]
1772 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1777 // move forwards, from beginning
1781 if (q < p) { // they are backward- swap them
1785 hole_size = q - p + 1;
1787 if (src < text || src > end)
1789 if (dest < text || dest >= end)
1792 goto thd_atend; // just delete the end of the buffer
1793 if (memmove(dest, src, cnt) != dest) {
1794 psbs("can't delete the character");
1797 end = end - hole_size; // adjust the new END
1799 dest = end - 1; // make sure dest in below end-1
1801 dest = end = text; // keep pointers valid
1802 file_modified++; // has the file been modified
1807 // copy text into register, then delete text.
1808 // if dist <= 0, do not include, or go past, a NewLine
1810 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1814 // make sure start <= stop
1816 // they are backwards, reverse them
1822 // we can not cross NL boundaries
1826 // dont go past a NewLine
1827 for (; p + 1 <= stop; p++) {
1829 stop = p; // "stop" just before NewLine
1835 #ifdef CONFIG_FEATURE_VI_YANKMARK
1836 text_yank(start, stop, YDreg);
1837 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1838 if (yf == YANKDEL) {
1839 p = text_hole_delete(start, stop);
1844 static void show_help(void)
1846 puts("These features are available:"
1847 #ifdef CONFIG_FEATURE_VI_SEARCH
1848 "\n\tPattern searches with / and ?"
1849 #endif /* CONFIG_FEATURE_VI_SEARCH */
1850 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1851 "\n\tLast command repeat with \'.\'"
1852 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1853 #ifdef CONFIG_FEATURE_VI_YANKMARK
1854 "\n\tLine marking with 'x"
1855 "\n\tNamed buffers with \"x"
1856 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1857 #ifdef CONFIG_FEATURE_VI_READONLY
1858 "\n\tReadonly if vi is called as \"view\""
1859 "\n\tReadonly with -R command line arg"
1860 #endif /* CONFIG_FEATURE_VI_READONLY */
1861 #ifdef CONFIG_FEATURE_VI_SET
1862 "\n\tSome colon mode commands with \':\'"
1863 #endif /* CONFIG_FEATURE_VI_SET */
1864 #ifdef CONFIG_FEATURE_VI_SETOPTS
1865 "\n\tSettable options with \":set\""
1866 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1867 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1868 "\n\tSignal catching- ^C"
1869 "\n\tJob suspend and resume with ^Z"
1870 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1871 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1872 "\n\tAdapt to window re-sizes"
1873 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1877 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1882 strcpy((char *) buf, ""); // init buf
1883 if (strlen((char *) s) <= 0)
1884 s = (Byte *) "(NULL)";
1885 for (; *s > '\0'; s++) {
1889 c_is_no_print = c > 127 && !Isprint(c);
1890 if (c_is_no_print) {
1891 strcat((char *) buf, SOn);
1894 if (c < ' ' || c == 127) {
1895 strcat((char *) buf, "^");
1902 strcat((char *) buf, (char *) b);
1904 strcat((char *) buf, SOs);
1906 strcat((char *) buf, "$");
1911 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1912 static void start_new_cmd_q(Byte c)
1915 free(last_modifying_cmd);
1916 // get buffer for new cmd
1917 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1918 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1919 // if there is a current cmd count put it in the buffer first
1921 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1922 else // just save char c onto queue
1923 last_modifying_cmd[0] = c;
1927 static void end_cmd_q(void)
1929 #ifdef CONFIG_FEATURE_VI_YANKMARK
1930 YDreg = 26; // go back to default Yank/Delete reg
1931 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1935 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1937 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1938 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1942 i = strlen((char *) s);
1943 p = text_hole_make(p, i);
1944 strncpy((char *) p, (char *) s, i);
1945 for (cnt = 0; *s != '\0'; s++) {
1949 #ifdef CONFIG_FEATURE_VI_YANKMARK
1950 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1951 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1954 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1956 #ifdef CONFIG_FEATURE_VI_YANKMARK
1957 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1962 if (q < p) { // they are backwards- reverse them
1969 free(t); // if already a yank register, free it
1970 t = (Byte *) xmalloc(cnt + 1); // get a new register
1971 memset(t, '\0', cnt + 1); // clear new text[]
1972 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1977 static Byte what_reg(void)
1983 c = 'D'; // default to D-reg
1984 if (0 <= YDreg && YDreg <= 25)
1985 c = 'a' + (Byte) YDreg;
1993 static void check_context(Byte cmd)
1995 // A context is defined to be "modifying text"
1996 // Any modifying command establishes a new context.
1998 if (dot < context_start || dot > context_end) {
1999 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2000 // we are trying to modify text[]- make this the current context
2001 mark[27] = mark[26]; // move cur to prev
2002 mark[26] = dot; // move local to cur
2003 context_start = prev_line(prev_line(dot));
2004 context_end = next_line(next_line(dot));
2005 //loiter= start_loiter= now;
2011 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2015 // the current context is in mark[26]
2016 // the previous context is in mark[27]
2017 // only swap context if other context is valid
2018 if (text <= mark[27] && mark[27] <= end - 1) {
2020 mark[27] = mark[26];
2022 p = mark[26]; // where we are going- previous context
2023 context_start = prev_line(prev_line(prev_line(p)));
2024 context_end = next_line(next_line(next_line(p)));
2028 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2030 static int isblnk(Byte c) // is the char a blank or tab
2032 return (c == ' ' || c == '\t');
2035 //----- Set terminal attributes --------------------------------
2036 static void rawmode(void)
2038 tcgetattr(0, &term_orig);
2039 term_vi = term_orig;
2040 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2041 term_vi.c_iflag &= (~IXON & ~ICRNL);
2042 term_vi.c_oflag &= (~ONLCR);
2043 term_vi.c_cc[VMIN] = 1;
2044 term_vi.c_cc[VTIME] = 0;
2045 erase_char = term_vi.c_cc[VERASE];
2046 tcsetattr(0, TCSANOW, &term_vi);
2049 static void cookmode(void)
2052 tcsetattr(0, TCSANOW, &term_orig);
2055 //----- Come here when we get a window resize signal ---------
2056 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2057 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2059 signal(SIGWINCH, winch_sig);
2060 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2061 get_terminal_width_height(0, &columns, &rows);
2062 new_screen(rows, columns); // get memory for virtual screen
2063 redraw(TRUE); // re-draw the screen
2066 //----- Come here when we get a continue signal -------------------
2067 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2069 rawmode(); // terminal to "raw"
2070 last_status_cksum = 0; // force status update
2071 redraw(TRUE); // re-draw the screen
2073 signal(SIGTSTP, suspend_sig);
2074 signal(SIGCONT, SIG_DFL);
2075 kill(my_pid, SIGCONT);
2078 //----- Come here when we get a Suspend signal -------------------
2079 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2081 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2082 clear_to_eol(); // Erase to end of line
2083 cookmode(); // terminal to "cooked"
2085 signal(SIGCONT, cont_sig);
2086 signal(SIGTSTP, SIG_DFL);
2087 kill(my_pid, SIGTSTP);
2090 //----- Come here when we get a signal ---------------------------
2091 static void catch_sig(int sig)
2093 signal(SIGINT, catch_sig);
2095 longjmp(restart, sig);
2097 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2099 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2101 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2106 tv.tv_usec = hund * 10000;
2107 select(1, &rfds, NULL, NULL, &tv);
2108 return (FD_ISSET(0, &rfds));
2111 #define readbuffer bb_common_bufsiz1
2113 static int readed_for_parse;
2115 //----- IO Routines --------------------------------------------
2116 static Byte readit(void) // read (maybe cursor) key from stdin
2125 static const struct esc_cmds esccmds[] = {
2126 {"OA", (Byte) VI_K_UP}, // cursor key Up
2127 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2128 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2129 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2130 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2131 {"OF", (Byte) VI_K_END}, // Cursor Key End
2132 {"[A", (Byte) VI_K_UP}, // cursor key Up
2133 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2134 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2135 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2136 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2137 {"[F", (Byte) VI_K_END}, // Cursor Key End
2138 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2139 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2140 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2141 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2142 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2143 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2144 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2145 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2146 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2147 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2148 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2149 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2150 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2151 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2152 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2153 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2154 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2155 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2156 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2157 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2158 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2161 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2163 (void) alarm(0); // turn alarm OFF while we wait for input
2165 n = readed_for_parse;
2166 // get input from User- are there already input chars in Q?
2169 // the Q is empty, wait for a typed char
2170 n = read(0, readbuffer, BUFSIZ - 1);
2173 goto ri0; // interrupted sys call
2176 if (errno == EFAULT)
2178 if (errno == EINVAL)
2186 if (readbuffer[0] == 27) {
2187 // This is an ESC char. Is this Esc sequence?
2188 // Could be bare Esc key. See if there are any
2189 // more chars to read after the ESC. This would
2190 // be a Function or Cursor Key sequence.
2194 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2196 // keep reading while there are input chars and room in buffer
2197 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2198 // read the rest of the ESC string
2199 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2205 readed_for_parse = n;
2208 if(c == 27 && n > 1) {
2209 // Maybe cursor or function key?
2210 const struct esc_cmds *eindex;
2212 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2213 int cnt = strlen(eindex->seq);
2217 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2219 // is a Cursor key- put derived value back into Q
2221 // for squeeze out the ESC sequence
2225 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2226 /* defined ESC sequence not found, set only one ESC */
2232 // remove key sequence from Q
2233 readed_for_parse -= n;
2234 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2235 (void) alarm(3); // we are done waiting for input, turn alarm ON
2239 //----- IO Routines --------------------------------------------
2240 static Byte get_one_char(void)
2244 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2245 // ! adding2q && ioq == 0 read()
2246 // ! adding2q && ioq != 0 *ioq
2247 // adding2q *last_modifying_cmd= read()
2249 // we are not adding to the q.
2250 // but, we may be reading from a q
2252 // there is no current q, read from STDIN
2253 c = readit(); // get the users input
2255 // there is a queue to get chars from first
2258 // the end of the q, read from STDIN
2260 ioq_start = ioq = 0;
2261 c = readit(); // get the users input
2265 // adding STDIN chars to q
2266 c = readit(); // get the users input
2267 if (last_modifying_cmd != 0) {
2268 int len = strlen((char *) last_modifying_cmd);
2269 if (len + 1 >= BUFSIZ) {
2270 psbs("last_modifying_cmd overrun");
2272 // add new char to q
2273 last_modifying_cmd[len] = c;
2277 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2278 c = readit(); // get the users input
2279 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2280 return (c); // return the char, where ever it came from
2283 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2288 static Byte *obufp = NULL;
2290 strcpy((char *) buf, (char *) prompt);
2291 last_status_cksum = 0; // force status update
2292 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2293 clear_to_eol(); // clear the line
2294 write1((char *) prompt); // write out the :, /, or ? prompt
2296 for (i = strlen((char *) buf); i < BUFSIZ;) {
2297 c = get_one_char(); // read user input
2298 if (c == '\n' || c == '\r' || c == 27)
2299 break; // is this end of input
2300 if (c == erase_char || c == 8 || c == 127) {
2301 // user wants to erase prev char
2302 i--; // backup to prev char
2303 buf[i] = '\0'; // erase the char
2304 buf[i + 1] = '\0'; // null terminate buffer
2305 write1("\b \b"); // erase char on screen
2306 if (i <= 0) { // user backs up before b-o-l, exit
2310 buf[i] = c; // save char in buffer
2311 buf[i + 1] = '\0'; // make sure buffer is null terminated
2312 putchar(c); // echo the char back to user
2318 obufp = (Byte *) bb_xstrdup((char *) buf);
2322 static int file_size(const Byte * fn) // what is the byte size of "fn"
2327 if (fn == 0 || strlen((char *)fn) <= 0)
2330 sr = stat((char *) fn, &st_buf); // see if file exists
2332 cnt = (int) st_buf.st_size;
2337 static int file_insert(Byte * fn, Byte * p, int size)
2342 #ifdef CONFIG_FEATURE_VI_READONLY
2344 #endif /* CONFIG_FEATURE_VI_READONLY */
2345 if (fn == 0 || strlen((char*) fn) <= 0) {
2346 psbs("No filename given");
2350 // OK- this is just a no-op
2355 psbs("Trying to insert a negative number (%d) of characters", size);
2358 if (p < text || p > end) {
2359 psbs("Trying to insert file outside of memory");
2363 // see if we can open the file
2364 #ifdef CONFIG_FEATURE_VI_READONLY
2365 if (vi_readonly) goto fi1; // do not try write-mode
2367 fd = open((char *) fn, O_RDWR); // assume read & write
2369 // could not open for writing- maybe file is read only
2370 #ifdef CONFIG_FEATURE_VI_READONLY
2373 fd = open((char *) fn, O_RDONLY); // try read-only
2375 psbs("\"%s\" %s", fn, "could not open file");
2378 #ifdef CONFIG_FEATURE_VI_READONLY
2379 // got the file- read-only
2381 #endif /* CONFIG_FEATURE_VI_READONLY */
2383 p = text_hole_make(p, size);
2384 cnt = read(fd, p, size);
2388 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2389 psbs("could not read file \"%s\"", fn);
2390 } else if (cnt < size) {
2391 // There was a partial read, shrink unused space text[]
2392 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2393 psbs("could not read all of file \"%s\"", fn);
2401 static int file_write(Byte * fn, Byte * first, Byte * last)
2403 int fd, cnt, charcnt;
2406 psbs("No current filename");
2410 // FIXIT- use the correct umask()
2411 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2414 cnt = last - first + 1;
2415 charcnt = write(fd, first, cnt);
2416 if (charcnt == cnt) {
2418 //file_modified= FALSE; // the file has not been modified
2426 //----- Terminal Drawing ---------------------------------------
2427 // The terminal is made up of 'rows' line of 'columns' columns.
2428 // classically this would be 24 x 80.
2429 // screen coordinates
2435 // 23,0 ... 23,79 status line
2438 //----- Move the cursor to row x col (count from 0, not 1) -------
2439 static void place_cursor(int row, int col, int opti)
2443 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2446 // char cm3[BUFSIZ];
2448 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2450 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2452 if (row < 0) row = 0;
2453 if (row >= rows) row = rows - 1;
2454 if (col < 0) col = 0;
2455 if (col >= columns) col = columns - 1;
2457 //----- 1. Try the standard terminal ESC sequence
2458 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2460 if (! opti) goto pc0;
2462 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2463 //----- find the minimum # of chars to move cursor -------------
2464 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2465 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2467 // move to the correct row
2468 while (row < Rrow) {
2469 // the cursor has to move up
2473 while (row > Rrow) {
2474 // the cursor has to move down
2475 strcat(cm2, CMdown);
2479 // now move to the correct column
2480 strcat(cm2, "\r"); // start at col 0
2481 // just send out orignal source char to get to correct place
2482 screenp = &screen[row * columns]; // start of screen line
2483 strncat(cm2, (char* )screenp, col);
2485 //----- 3. Try some other way of moving cursor
2486 //---------------------------------------------
2488 // pick the shortest cursor motion to send out
2490 if (strlen(cm2) < strlen(cm)) {
2492 } /* else if (strlen(cm3) < strlen(cm)) {
2495 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2497 write1(cm); // move the cursor
2500 //----- Erase from cursor to end of line -----------------------
2501 static void clear_to_eol(void)
2503 write1(Ceol); // Erase from cursor to end of line
2506 //----- Erase from cursor to end of screen -----------------------
2507 static void clear_to_eos(void)
2509 write1(Ceos); // Erase from cursor to end of screen
2512 //----- Start standout mode ------------------------------------
2513 static void standout_start(void) // send "start reverse video" sequence
2515 write1(SOs); // Start reverse video mode
2518 //----- End standout mode --------------------------------------
2519 static void standout_end(void) // send "end reverse video" sequence
2521 write1(SOn); // End reverse video mode
2524 //----- Flash the screen --------------------------------------
2525 static void flash(int h)
2527 standout_start(); // send "start reverse video" sequence
2530 standout_end(); // send "end reverse video" sequence
2534 static void Indicate_Error(void)
2536 #ifdef CONFIG_FEATURE_VI_CRASHME
2538 return; // generate a random command
2539 #endif /* CONFIG_FEATURE_VI_CRASHME */
2541 write1(bell); // send out a bell character
2547 //----- Screen[] Routines --------------------------------------
2548 //----- Erase the Screen[] memory ------------------------------
2549 static void screen_erase(void)
2551 memset(screen, ' ', screensize); // clear new screen
2554 static int bufsum(unsigned char *buf, int count)
2557 unsigned char *e = buf + count;
2563 //----- Draw the status line at bottom of the screen -------------
2564 static void show_status_line(void)
2566 int cnt = 0, cksum = 0;
2568 // either we already have an error or status message, or we
2570 if (!have_status_msg) {
2571 cnt = format_edit_status();
2572 cksum = bufsum(status_buffer, cnt);
2574 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2575 last_status_cksum= cksum; // remember if we have seen this line
2576 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2577 write1((char*)status_buffer);
2579 if (have_status_msg) {
2580 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2582 have_status_msg = 0;
2585 have_status_msg = 0;
2587 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2592 //----- format the status buffer, the bottom line of screen ------
2593 // format status buffer, with STANDOUT mode
2594 static void psbs(const char *format, ...)
2598 va_start(args, format);
2599 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2600 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2601 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2604 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2609 // format status buffer
2610 static void psb(const char *format, ...)
2614 va_start(args, format);
2615 vsprintf((char *) status_buffer, format, args);
2618 have_status_msg = 1;
2623 static void ni(Byte * s) // display messages
2627 print_literal(buf, s);
2628 psbs("\'%s\' is not implemented", buf);
2631 static int format_edit_status(void) // show file status on status line
2633 int cur, percent, ret, trunc_at;
2636 // file_modified is now a counter rather than a flag. this
2637 // helps reduce the amount of line counting we need to do.
2638 // (this will cause a mis-reporting of modified status
2639 // once every MAXINT editing operations.)
2641 // it would be nice to do a similar optimization here -- if
2642 // we haven't done a motion that could have changed which line
2643 // we're on, then we shouldn't have to do this count_lines()
2644 cur = count_lines(text, dot);
2646 // reduce counting -- the total lines can't have
2647 // changed if we haven't done any edits.
2648 if (file_modified != last_file_modified) {
2649 tot = cur + count_lines(dot, end - 1) - 1;
2650 last_file_modified = file_modified;
2653 // current line percent
2654 // ------------- ~~ ----------
2657 percent = (100 * cur) / tot;
2663 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2664 columns : STATUS_BUFFER_LEN-1;
2666 ret = snprintf((char *) status_buffer, trunc_at+1,
2667 #ifdef CONFIG_FEATURE_VI_READONLY
2668 "%c %s%s%s %d/%d %d%%",
2670 "%c %s%s %d/%d %d%%",
2672 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2673 (cfn != 0 ? (char *) cfn : "No file"),
2674 #ifdef CONFIG_FEATURE_VI_READONLY
2675 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2677 (file_modified ? " [modified]" : ""),
2680 if (ret >= 0 && ret < trunc_at)
2681 return ret; /* it all fit */
2683 return trunc_at; /* had to truncate */
2686 //----- Force refresh of all Lines -----------------------------
2687 static void redraw(int full_screen)
2689 place_cursor(0, 0, FALSE); // put cursor in correct place
2690 clear_to_eos(); // tel terminal to erase display
2691 screen_erase(); // erase the internal screen buffer
2692 last_status_cksum = 0; // force status update
2693 refresh(full_screen); // this will redraw the entire display
2697 //----- Format a text[] line into a buffer ---------------------
2698 static void format_line(Byte *dest, Byte *src, int li)
2703 for (co= 0; co < MAX_SCR_COLS; co++) {
2704 c= ' '; // assume blank
2705 if (li > 0 && co == 0) {
2706 c = '~'; // not first line, assume Tilde
2708 // are there chars in text[] and have we gone past the end
2709 if (text < end && src < end) {
2714 if (c > 127 && !Isprint(c)) {
2717 if (c < ' ' || c == 127) {
2721 for (; (co % tabstop) != (tabstop - 1); co++) {
2729 c += '@'; // make it visible
2732 // the co++ is done here so that the column will
2733 // not be overwritten when we blank-out the rest of line
2740 //----- Refresh the changed screen lines -----------------------
2741 // Copy the source line from text[] into the buffer and note
2742 // if the current screenline is different from the new buffer.
2743 // If they differ then that line needs redrawing on the terminal.
2745 static void refresh(int full_screen)
2747 static int old_offset;
2749 Byte buf[MAX_SCR_COLS];
2750 Byte *tp, *sp; // pointer into text[] and screen[]
2751 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2752 int last_li= -2; // last line that changed- for optimizing cursor movement
2753 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2755 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2756 get_terminal_width_height(0, &columns, &rows);
2757 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2758 tp = screenbegin; // index into text[] of top line
2760 // compare text[] to screen[] and mark screen[] lines that need updating
2761 for (li = 0; li < rows - 1; li++) {
2762 int cs, ce; // column start & end
2763 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2764 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2765 // format current text line into buf
2766 format_line(buf, tp, li);
2768 // skip to the end of the current text[] line
2769 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2771 // see if there are any changes between vitual screen and buf
2772 changed = FALSE; // assume no change
2775 sp = &screen[li * columns]; // start of screen line
2777 // force re-draw of every single column from 0 - columns-1
2780 // compare newly formatted buffer with virtual screen
2781 // look forward for first difference between buf and screen
2782 for ( ; cs <= ce; cs++) {
2783 if (buf[cs + offset] != sp[cs]) {
2784 changed = TRUE; // mark for redraw
2789 // look backward for last difference between buf and screen
2790 for ( ; ce >= cs; ce--) {
2791 if (buf[ce + offset] != sp[ce]) {
2792 changed = TRUE; // mark for redraw
2796 // now, cs is index of first diff, and ce is index of last diff
2798 // if horz offset has changed, force a redraw
2799 if (offset != old_offset) {
2804 // make a sanity check of columns indexes
2806 if (ce > columns-1) ce= columns-1;
2807 if (cs > ce) { cs= 0; ce= columns-1; }
2808 // is there a change between vitual screen and buf
2810 // copy changed part of buffer to virtual screen
2811 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2813 // move cursor to column of first change
2814 if (offset != old_offset) {
2815 // opti_cur_move is still too stupid
2816 // to handle offsets correctly
2817 place_cursor(li, cs, FALSE);
2819 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2820 // if this just the next line
2821 // try to optimize cursor movement
2822 // otherwise, use standard ESC sequence
2823 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2825 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2826 place_cursor(li, cs, FALSE); // use standard ESC sequence
2827 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2830 // write line out to terminal
2833 char *out = (char*)sp+cs;
2840 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2842 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2846 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2847 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2850 place_cursor(crow, ccol, FALSE);
2851 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2853 if (offset != old_offset)
2854 old_offset = offset;
2857 //---------------------------------------------------------------------
2858 //----- the Ascii Chart -----------------------------------------------
2860 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2861 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2862 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2863 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2864 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2865 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2866 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2867 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2868 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2869 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2870 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2871 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2872 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2873 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2874 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2875 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2876 //---------------------------------------------------------------------
2878 //----- Execute a Vi Command -----------------------------------
2879 static void do_cmd(Byte c)
2881 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2882 int cnt, i, j, dir, yf;
2884 c1 = c; // quiet the compiler
2885 cnt = yf = dir = 0; // quiet the compiler
2886 p = q = save_dot = msg = buf; // quiet the compiler
2887 memset(buf, '\0', 9); // clear buf
2891 /* if this is a cursor key, skip these checks */
2904 if (cmd_mode == 2) {
2905 // flip-flop Insert/Replace mode
2906 if (c == VI_K_INSERT) goto dc_i;
2907 // we are 'R'eplacing the current *dot with new char
2909 // don't Replace past E-o-l
2910 cmd_mode = 1; // convert to insert
2912 if (1 <= c || Isprint(c)) {
2914 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2915 dot = char_insert(dot, c); // insert new char
2920 if (cmd_mode == 1) {
2921 // hitting "Insert" twice means "R" replace mode
2922 if (c == VI_K_INSERT) goto dc5;
2923 // insert the char c at "dot"
2924 if (1 <= c || Isprint(c)) {
2925 dot = char_insert(dot, c);
2940 #ifdef CONFIG_FEATURE_VI_CRASHME
2941 case 0x14: // dc4 ctrl-T
2942 crashme = (crashme == 0) ? 1 : 0;
2944 #endif /* CONFIG_FEATURE_VI_CRASHME */
2973 //case 'u': // u- FIXME- there is no undo
2975 default: // unrecognised command
2984 end_cmd_q(); // stop adding to q
2985 case 0x00: // nul- ignore
2987 case 2: // ctrl-B scroll up full screen
2988 case VI_K_PAGEUP: // Cursor Key Page Up
2989 dot_scroll(rows - 2, -1);
2991 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2992 case 0x03: // ctrl-C interrupt
2993 longjmp(restart, 1);
2995 case 26: // ctrl-Z suspend
2996 suspend_sig(SIGTSTP);
2998 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2999 case 4: // ctrl-D scroll down half screen
3000 dot_scroll((rows - 2) / 2, 1);
3002 case 5: // ctrl-E scroll down one line
3005 case 6: // ctrl-F scroll down full screen
3006 case VI_K_PAGEDOWN: // Cursor Key Page Down
3007 dot_scroll(rows - 2, 1);
3009 case 7: // ctrl-G show current status
3010 last_status_cksum = 0; // force status update
3012 case 'h': // h- move left
3013 case VI_K_LEFT: // cursor key Left
3014 case 8: // ctrl-H- move left (This may be ERASE char)
3015 case 127: // DEL- move left (This may be ERASE char)
3021 case 10: // Newline ^J
3022 case 'j': // j- goto next line, same col
3023 case VI_K_DOWN: // cursor key Down
3027 dot_next(); // go to next B-o-l
3028 dot = move_to_col(dot, ccol + offset); // try stay in same col
3030 case 12: // ctrl-L force redraw whole screen
3031 case 18: // ctrl-R force redraw
3032 place_cursor(0, 0, FALSE); // put cursor in correct place
3033 clear_to_eos(); // tel terminal to erase display
3035 screen_erase(); // erase the internal screen buffer
3036 last_status_cksum = 0; // force status update
3037 refresh(TRUE); // this will redraw the entire display
3039 case 13: // Carriage Return ^M
3040 case '+': // +- goto next line
3047 case 21: // ctrl-U scroll up half screen
3048 dot_scroll((rows - 2) / 2, -1);
3050 case 25: // ctrl-Y scroll up one line
3056 cmd_mode = 0; // stop insrting
3058 last_status_cksum = 0; // force status update
3060 case ' ': // move right
3061 case 'l': // move right
3062 case VI_K_RIGHT: // Cursor Key Right
3068 #ifdef CONFIG_FEATURE_VI_YANKMARK
3069 case '"': // "- name a register to use for Delete/Yank
3070 c1 = get_one_char();
3078 case '\'': // '- goto a specific mark
3079 c1 = get_one_char();
3085 if (text <= q && q < end) {
3087 dot_begin(); // go to B-o-l
3090 } else if (c1 == '\'') { // goto previous context
3091 dot = swap_context(dot); // swap current and previous context
3092 dot_begin(); // go to B-o-l
3098 case 'm': // m- Mark a line
3099 // this is really stupid. If there are any inserts or deletes
3100 // between text[0] and dot then this mark will not point to the
3101 // correct location! It could be off by many lines!
3102 // Well..., at least its quick and dirty.
3103 c1 = get_one_char();
3107 // remember the line
3108 mark[(int) c1] = dot;
3113 case 'P': // P- Put register before
3114 case 'p': // p- put register after
3117 psbs("Nothing in register %c", what_reg());
3120 // are we putting whole lines or strings
3121 if (strchr((char *) p, '\n') != NULL) {
3123 dot_begin(); // putting lines- Put above
3126 // are we putting after very last line?
3127 if (end_line(dot) == (end - 1)) {
3128 dot = end; // force dot to end of text[]
3130 dot_next(); // next line, then put before
3135 dot_right(); // move to right, can move to NL
3137 dot = string_insert(dot, p); // insert the string
3138 end_cmd_q(); // stop adding to q
3140 case 'U': // U- Undo; replace current line with original version
3141 if (reg[Ureg] != 0) {
3142 p = begin_line(dot);
3144 p = text_hole_delete(p, q); // delete cur line
3145 p = string_insert(p, reg[Ureg]); // insert orig line
3150 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3151 case '$': // $- goto end of line
3152 case VI_K_END: // Cursor Key End
3156 dot = end_line(dot);
3158 case '%': // %- find matching char of pair () [] {}
3159 for (q = dot; q < end && *q != '\n'; q++) {
3160 if (strchr("()[]{}", *q) != NULL) {
3161 // we found half of a pair
3162 p = find_pair(q, *q);
3174 case 'f': // f- forward to a user specified char
3175 last_forward_char = get_one_char(); // get the search char
3177 // dont separate these two commands. 'f' depends on ';'
3179 //**** fall thru to ... ';'
3180 case ';': // ;- look at rest of line for last forward char
3184 if (last_forward_char == 0) break;
3186 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3189 if (*q == last_forward_char)
3192 case '-': // -- goto prev line
3199 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3200 case '.': // .- repeat the last modifying command
3201 // Stuff the last_modifying_cmd back into stdin
3202 // and let it be re-executed.
3203 if (last_modifying_cmd != 0) {
3204 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3207 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3208 #ifdef CONFIG_FEATURE_VI_SEARCH
3209 case '?': // /- search for a pattern
3210 case '/': // /- search for a pattern
3213 q = get_input_line(buf); // get input line- use "status line"
3214 if (strlen((char *) q) == 1)
3215 goto dc3; // if no pat re-use old pat
3216 if (strlen((char *) q) > 1) { // new pat- save it and find
3217 // there is a new pat
3218 free(last_search_pattern);
3219 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3220 goto dc3; // now find the pattern
3222 // user changed mind and erased the "/"- do nothing
3224 case 'N': // N- backward search for last pattern
3228 dir = BACK; // assume BACKWARD search
3230 if (last_search_pattern[0] == '?') {
3234 goto dc4; // now search for pattern
3236 case 'n': // n- repeat search for last pattern
3237 // search rest of text[] starting at next char
3238 // if search fails return orignal "p" not the "p+1" address
3243 if (last_search_pattern == 0) {
3244 msg = (Byte *) "No previous regular expression";
3247 if (last_search_pattern[0] == '/') {
3248 dir = FORWARD; // assume FORWARD search
3251 if (last_search_pattern[0] == '?') {
3256 q = char_search(p, last_search_pattern + 1, dir, FULL);
3258 dot = q; // good search, update "dot"
3262 // no pattern found between "dot" and "end"- continue at top
3267 q = char_search(p, last_search_pattern + 1, dir, FULL);
3268 if (q != NULL) { // found something
3269 dot = q; // found new pattern- goto it
3270 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3272 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3275 msg = (Byte *) "Pattern not found";
3278 if (*msg) psbs("%s", msg);
3280 case '{': // {- move backward paragraph
3281 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3282 if (q != NULL) { // found blank line
3283 dot = next_line(q); // move to next blank line
3286 case '}': // }- move forward paragraph
3287 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3288 if (q != NULL) { // found blank line
3289 dot = next_line(q); // move to next blank line
3292 #endif /* CONFIG_FEATURE_VI_SEARCH */
3293 case '0': // 0- goto begining of line
3303 if (c == '0' && cmdcnt < 1) {
3304 dot_begin(); // this was a standalone zero
3306 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3309 case ':': // :- the colon mode commands
3310 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3311 #ifdef CONFIG_FEATURE_VI_COLON
3312 colon(p); // execute the command
3313 #else /* CONFIG_FEATURE_VI_COLON */
3315 p++; // move past the ':'
3316 cnt = strlen((char *) p);
3319 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3320 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3321 if (file_modified && p[1] != '!') {
3322 psbs("No write since last change (:quit! overrides)");
3326 } else if (strncasecmp((char *) p, "write", cnt) == 0
3327 || strncasecmp((char *) p, "wq", cnt) == 0
3328 || strncasecmp((char *) p, "wn", cnt) == 0
3329 || strncasecmp((char *) p, "x", cnt) == 0) {
3330 cnt = file_write(cfn, text, end - 1);
3333 psbs("Write error: %s", strerror(errno));
3336 last_file_modified = -1;
3337 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3338 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3339 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3343 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3344 last_status_cksum = 0; // force status update
3345 } else if (sscanf((char *) p, "%d", &j) > 0) {
3346 dot = find_line(j); // go to line # j
3348 } else { // unrecognised cmd
3351 #endif /* CONFIG_FEATURE_VI_COLON */
3353 case '<': // <- Left shift something
3354 case '>': // >- Right shift something
3355 cnt = count_lines(text, dot); // remember what line we are on
3356 c1 = get_one_char(); // get the type of thing to delete
3357 find_range(&p, &q, c1);
3358 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3361 i = count_lines(p, q); // # of lines we are shifting
3362 for ( ; i > 0; i--, p = next_line(p)) {
3364 // shift left- remove tab or 8 spaces
3366 // shrink buffer 1 char
3367 (void) text_hole_delete(p, p);
3368 } else if (*p == ' ') {
3369 // we should be calculating columns, not just SPACE
3370 for (j = 0; *p == ' ' && j < tabstop; j++) {
3371 (void) text_hole_delete(p, p);
3374 } else if (c == '>') {
3375 // shift right -- add tab or 8 spaces
3376 (void) char_insert(p, '\t');
3379 dot = find_line(cnt); // what line were we on
3381 end_cmd_q(); // stop adding to q
3383 case 'A': // A- append at e-o-l
3384 dot_end(); // go to e-o-l
3385 //**** fall thru to ... 'a'
3386 case 'a': // a- append after current char
3391 case 'B': // B- back a blank-delimited Word
3392 case 'E': // E- end of a blank-delimited word
3393 case 'W': // W- forward a blank-delimited word
3400 if (c == 'W' || isspace(dot[dir])) {
3401 dot = skip_thing(dot, 1, dir, S_TO_WS);
3402 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3405 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3407 case 'C': // C- Change to e-o-l
3408 case 'D': // D- delete to e-o-l
3410 dot = dollar_line(dot); // move to before NL
3411 // copy text into a register and delete
3412 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3414 goto dc_i; // start inserting
3415 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3417 end_cmd_q(); // stop adding to q
3418 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3420 case 'G': // G- goto to a line number (default= E-O-F)
3421 dot = end - 1; // assume E-O-F
3423 dot = find_line(cmdcnt); // what line is #cmdcnt
3427 case 'H': // H- goto top line on screen
3429 if (cmdcnt > (rows - 1)) {
3430 cmdcnt = (rows - 1);
3437 case 'I': // I- insert before first non-blank
3440 //**** fall thru to ... 'i'
3441 case 'i': // i- insert before current char
3442 case VI_K_INSERT: // Cursor Key Insert
3444 cmd_mode = 1; // start insrting
3446 case 'J': // J- join current and next lines together
3450 dot_end(); // move to NL
3451 if (dot < end - 1) { // make sure not last char in text[]
3452 *dot++ = ' '; // replace NL with space
3454 while (isblnk(*dot)) { // delete leading WS
3458 end_cmd_q(); // stop adding to q
3460 case 'L': // L- goto bottom line on screen
3462 if (cmdcnt > (rows - 1)) {
3463 cmdcnt = (rows - 1);
3471 case 'M': // M- goto middle line on screen
3473 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3474 dot = next_line(dot);
3476 case 'O': // O- open a empty line above
3478 p = begin_line(dot);
3479 if (p[-1] == '\n') {
3481 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3483 dot = char_insert(dot, '\n');
3486 dot = char_insert(dot, '\n'); // i\n ESC
3491 case 'R': // R- continuous Replace char
3495 case 'X': // X- delete char before dot
3496 case 'x': // x- delete the current char
3497 case 's': // s- substitute the current char
3504 if (dot[dir] != '\n') {
3506 dot--; // delete prev char
3507 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3510 goto dc_i; // start insrting
3511 end_cmd_q(); // stop adding to q
3513 case 'Z': // Z- if modified, {write}; exit
3514 // ZZ means to save file (if necessary), then exit
3515 c1 = get_one_char();
3520 if (file_modified) {
3521 #ifdef CONFIG_FEATURE_VI_READONLY
3522 if (vi_readonly || readonly) {
3523 psbs("\"%s\" File is read only", cfn);
3526 #endif /* CONFIG_FEATURE_VI_READONLY */
3527 cnt = file_write(cfn, text, end - 1);
3530 psbs("Write error: %s", strerror(errno));
3531 } else if (cnt == (end - 1 - text + 1)) {
3538 case '^': // ^- move to first non-blank on line
3542 case 'b': // b- back a word
3543 case 'e': // e- end of word
3550 if ((dot + dir) < text || (dot + dir) > end - 1)
3553 if (isspace(*dot)) {
3554 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3556 if (isalnum(*dot) || *dot == '_') {
3557 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3558 } else if (ispunct(*dot)) {
3559 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3562 case 'c': // c- change something
3563 case 'd': // d- delete something
3564 #ifdef CONFIG_FEATURE_VI_YANKMARK
3565 case 'y': // y- yank something
3566 case 'Y': // Y- Yank a line
3567 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3568 yf = YANKDEL; // assume either "c" or "d"
3569 #ifdef CONFIG_FEATURE_VI_YANKMARK
3570 if (c == 'y' || c == 'Y')
3572 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3575 c1 = get_one_char(); // get the type of thing to delete
3576 find_range(&p, &q, c1);
3577 if (c1 == 27) { // ESC- user changed mind and wants out
3578 c = c1 = 27; // Escape- do nothing
3579 } else if (strchr("wW", c1)) {
3581 // don't include trailing WS as part of word
3582 while (isblnk(*q)) {
3583 if (q <= text || q[-1] == '\n')
3588 dot = yank_delete(p, q, 0, yf); // delete word
3589 } else if (strchr("^0bBeEft$", c1)) {
3590 // single line copy text into a register and delete
3591 dot = yank_delete(p, q, 0, yf); // delete word
3592 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3593 // multiple line copy text into a register and delete
3594 dot = yank_delete(p, q, 1, yf); // delete lines
3596 dot = char_insert(dot, '\n');
3597 // on the last line of file don't move to prev line
3598 if (dot != (end-1)) {
3601 } else if (c == 'd') {
3606 // could not recognize object
3607 c = c1 = 27; // error-
3611 // if CHANGING, not deleting, start inserting after the delete
3613 strcpy((char *) buf, "Change");
3614 goto dc_i; // start inserting
3617 strcpy((char *) buf, "Delete");
3619 #ifdef CONFIG_FEATURE_VI_YANKMARK
3620 if (c == 'y' || c == 'Y') {
3621 strcpy((char *) buf, "Yank");
3624 q = p + strlen((char *) p);
3625 for (cnt = 0; p <= q; p++) {
3629 psb("%s %d lines (%d chars) using [%c]",
3630 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3631 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3632 end_cmd_q(); // stop adding to q
3635 case 'k': // k- goto prev line, same col
3636 case VI_K_UP: // cursor key Up
3641 dot = move_to_col(dot, ccol + offset); // try stay in same col
3643 case 'r': // r- replace the current char with user input
3644 c1 = get_one_char(); // get the replacement char
3647 file_modified++; // has the file been modified
3649 end_cmd_q(); // stop adding to q
3651 case 't': // t- move to char prior to next x
3652 last_forward_char = get_one_char();
3654 if (*dot == last_forward_char)
3656 last_forward_char= 0;
3658 case 'w': // w- forward a word
3662 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3663 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3664 } else if (ispunct(*dot)) { // we are on PUNCT
3665 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3668 dot++; // move over word
3669 if (isspace(*dot)) {
3670 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3674 c1 = get_one_char(); // get the replacement char
3677 cnt = (rows - 2) / 2; // put dot at center
3679 cnt = rows - 2; // put dot at bottom
3680 screenbegin = begin_line(dot); // start dot at top
3681 dot_scroll(cnt, -1);
3683 case '|': // |- move to column "cmdcnt"
3684 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3686 case '~': // ~- flip the case of letters a-z -> A-Z
3690 if (islower(*dot)) {
3691 *dot = toupper(*dot);
3692 file_modified++; // has the file been modified
3693 } else if (isupper(*dot)) {
3694 *dot = tolower(*dot);
3695 file_modified++; // has the file been modified
3698 end_cmd_q(); // stop adding to q
3700 //----- The Cursor and Function Keys -----------------------------
3701 case VI_K_HOME: // Cursor Key Home
3704 // The Fn keys could point to do_macro which could translate them
3705 case VI_K_FUN1: // Function Key F1
3706 case VI_K_FUN2: // Function Key F2
3707 case VI_K_FUN3: // Function Key F3
3708 case VI_K_FUN4: // Function Key F4
3709 case VI_K_FUN5: // Function Key F5
3710 case VI_K_FUN6: // Function Key F6
3711 case VI_K_FUN7: // Function Key F7
3712 case VI_K_FUN8: // Function Key F8
3713 case VI_K_FUN9: // Function Key F9
3714 case VI_K_FUN10: // Function Key F10
3715 case VI_K_FUN11: // Function Key F11
3716 case VI_K_FUN12: // Function Key F12
3721 // if text[] just became empty, add back an empty line
3723 (void) char_insert(text, '\n'); // start empty buf with dummy line
3726 // it is OK for dot to exactly equal to end, otherwise check dot validity
3728 dot = bound_dot(dot); // make sure "dot" is valid
3730 #ifdef CONFIG_FEATURE_VI_YANKMARK
3731 check_context(c); // update the current context
3732 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3735 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3736 cnt = dot - begin_line(dot);
3737 // Try to stay off of the Newline
3738 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3742 #ifdef CONFIG_FEATURE_VI_CRASHME
3743 static int totalcmds = 0;
3744 static int Mp = 85; // Movement command Probability
3745 static int Np = 90; // Non-movement command Probability
3746 static int Dp = 96; // Delete command Probability
3747 static int Ip = 97; // Insert command Probability
3748 static int Yp = 98; // Yank command Probability
3749 static int Pp = 99; // Put command Probability
3750 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3751 char chars[20] = "\t012345 abcdABCD-=.$";
3752 char *words[20] = { "this", "is", "a", "test",
3753 "broadcast", "the", "emergency", "of",
3754 "system", "quick", "brown", "fox",
3755 "jumped", "over", "lazy", "dogs",
3756 "back", "January", "Febuary", "March"
3759 "You should have received a copy of the GNU General Public License\n",
3760 "char c, cm, *cmd, *cmd1;\n",
3761 "generate a command by percentages\n",
3762 "Numbers may be typed as a prefix to some commands.\n",
3763 "Quit, discarding changes!\n",
3764 "Forced write, if permission originally not valid.\n",
3765 "In general, any ex or ed command (such as substitute or delete).\n",
3766 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3767 "Please get w/ me and I will go over it with you.\n",
3768 "The following is a list of scheduled, committed changes.\n",
3769 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3770 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3771 "Any question about transactions please contact Sterling Huxley.\n",
3772 "I will try to get back to you by Friday, December 31.\n",
3773 "This Change will be implemented on Friday.\n",
3774 "Let me know if you have problems accessing this;\n",
3775 "Sterling Huxley recently added you to the access list.\n",
3776 "Would you like to go to lunch?\n",
3777 "The last command will be automatically run.\n",
3778 "This is too much english for a computer geek.\n",
3780 char *multilines[20] = {
3781 "You should have received a copy of the GNU General Public License\n",
3782 "char c, cm, *cmd, *cmd1;\n",
3783 "generate a command by percentages\n",
3784 "Numbers may be typed as a prefix to some commands.\n",
3785 "Quit, discarding changes!\n",
3786 "Forced write, if permission originally not valid.\n",
3787 "In general, any ex or ed command (such as substitute or delete).\n",
3788 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3789 "Please get w/ me and I will go over it with you.\n",
3790 "The following is a list of scheduled, committed changes.\n",
3791 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3792 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3793 "Any question about transactions please contact Sterling Huxley.\n",
3794 "I will try to get back to you by Friday, December 31.\n",
3795 "This Change will be implemented on Friday.\n",
3796 "Let me know if you have problems accessing this;\n",
3797 "Sterling Huxley recently added you to the access list.\n",
3798 "Would you like to go to lunch?\n",
3799 "The last command will be automatically run.\n",
3800 "This is too much english for a computer geek.\n",
3803 // create a random command to execute
3804 static void crash_dummy()
3806 static int sleeptime; // how long to pause between commands
3807 char c, cm, *cmd, *cmd1;
3808 int i, cnt, thing, rbi, startrbi, percent;
3810 // "dot" movement commands
3811 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3813 // is there already a command running?
3814 if (readed_for_parse > 0)
3818 sleeptime = 0; // how long to pause between commands
3819 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3820 // generate a command by percentages
3821 percent = (int) lrand48() % 100; // get a number from 0-99
3822 if (percent < Mp) { // Movement commands
3823 // available commands
3826 } else if (percent < Np) { // non-movement commands
3827 cmd = "mz<>\'\""; // available commands
3829 } else if (percent < Dp) { // Delete commands
3830 cmd = "dx"; // available commands
3832 } else if (percent < Ip) { // Inset commands
3833 cmd = "iIaAsrJ"; // available commands
3835 } else if (percent < Yp) { // Yank commands
3836 cmd = "yY"; // available commands
3838 } else if (percent < Pp) { // Put commands
3839 cmd = "pP"; // available commands
3842 // We do not know how to handle this command, try again
3846 // randomly pick one of the available cmds from "cmd[]"
3847 i = (int) lrand48() % strlen(cmd);
3849 if (strchr(":\024", cm))
3850 goto cd0; // dont allow colon or ctrl-T commands
3851 readbuffer[rbi++] = cm; // put cmd into input buffer
3853 // now we have the command-
3854 // there are 1, 2, and multi char commands
3855 // find out which and generate the rest of command as necessary
3856 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3857 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3858 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3859 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3861 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3863 readbuffer[rbi++] = c; // add movement to input buffer
3865 if (strchr("iIaAsc", cm)) { // multi-char commands
3867 // change some thing
3868 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3870 readbuffer[rbi++] = c; // add movement to input buffer
3872 thing = (int) lrand48() % 4; // what thing to insert
3873 cnt = (int) lrand48() % 10; // how many to insert
3874 for (i = 0; i < cnt; i++) {
3875 if (thing == 0) { // insert chars
3876 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3877 } else if (thing == 1) { // insert words
3878 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3879 strcat((char *) readbuffer, " ");
3880 sleeptime = 0; // how fast to type
3881 } else if (thing == 2) { // insert lines
3882 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3883 sleeptime = 0; // how fast to type
3884 } else { // insert multi-lines
3885 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3886 sleeptime = 0; // how fast to type
3889 strcat((char *) readbuffer, "\033");
3891 readed_for_parse = strlen(readbuffer);
3895 (void) mysleep(sleeptime); // sleep 1/100 sec
3898 // test to see if there are any errors
3899 static void crash_test()
3901 static time_t oldtim;
3903 char d[2], msg[BUFSIZ];
3907 strcat((char *) msg, "end<text ");
3909 if (end > textend) {
3910 strcat((char *) msg, "end>textend ");
3913 strcat((char *) msg, "dot<text ");
3916 strcat((char *) msg, "dot>end ");
3918 if (screenbegin < text) {
3919 strcat((char *) msg, "screenbegin<text ");
3921 if (screenbegin > end - 1) {
3922 strcat((char *) msg, "screenbegin>end-1 ");
3925 if (strlen(msg) > 0) {
3927 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3928 totalcmds, last_input_char, msg, SOs, SOn);
3930 while (read(0, d, 1) > 0) {
3931 if (d[0] == '\n' || d[0] == '\r')
3936 tim = (time_t) time((time_t *) 0);
3937 if (tim >= (oldtim + 3)) {
3938 sprintf((char *) status_buffer,
3939 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3940 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3945 #endif /* CONFIG_FEATURE_VI_CRASHME */