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; // 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
1098 beg_scr = end_scr = screenbegin; // first char of screen
1099 end_scr = end_screen(); // last char of screen
1101 if (beg_cur < screenbegin) {
1102 // "d" is before top line on screen
1103 // how many lines do we have to move
1104 cnt = count_lines(beg_cur, screenbegin);
1106 screenbegin = beg_cur;
1107 if (cnt > (rows - 1) / 2) {
1108 // we moved too many lines. put "dot" in middle of screen
1109 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1110 screenbegin = prev_line(screenbegin);
1113 } else if (beg_cur > end_scr) {
1114 // "d" is after bottom line on screen
1115 // how many lines do we have to move
1116 cnt = count_lines(end_scr, beg_cur);
1117 if (cnt > (rows - 1) / 2)
1118 goto sc1; // too many lines
1119 for (ro = 0; ro < cnt - 1; ro++) {
1120 // move screen begin the same amount
1121 screenbegin = next_line(screenbegin);
1122 // now, move the end of screen
1123 end_scr = next_line(end_scr);
1124 end_scr = end_line(end_scr);
1127 // "d" is on screen- find out which row
1129 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1135 // find out what col "d" is on
1137 do { // drive "co" to correct column
1138 if (*tp == '\n' || *tp == '\0')
1142 co += ((tabstop - 1) - (co % tabstop));
1143 } else if (*tp < ' ' || *tp == 127) {
1144 co++; // display as ^X, use 2 columns
1146 } while (tp++ < d && ++co);
1148 // "co" is the column where "dot" is.
1149 // The screen has "columns" columns.
1150 // The currently displayed columns are 0+offset -- columns+ofset
1151 // |-------------------------------------------------------------|
1153 // offset | |------- columns ----------------|
1155 // If "co" is already in this range then we do not have to adjust offset
1156 // but, we do have to subtract the "offset" bias from "co".
1157 // If "co" is outside this range then we have to change "offset".
1158 // If the first char of a line is a tab the cursor will try to stay
1159 // in column 7, but we have to set offset to 0.
1161 if (co < 0 + offset) {
1164 if (co >= columns + offset) {
1165 offset = co - columns + 1;
1167 // if the first char of the line is a tab, and "dot" is sitting on it
1168 // force offset to 0.
1169 if (d == beg_cur && *d == '\t') {
1178 //----- Text Movement Routines ---------------------------------
1179 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1181 while (p > text && p[-1] != '\n')
1182 p--; // go to cur line B-o-l
1186 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1188 while (p < end - 1 && *p != '\n')
1189 p++; // go to cur line E-o-l
1193 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1195 while (p < end - 1 && *p != '\n')
1196 p++; // go to cur line E-o-l
1197 // Try to stay off of the Newline
1198 if (*p == '\n' && (p - begin_line(p)) > 0)
1203 static Byte *prev_line(Byte * p) // return pointer first char prev line
1205 p = begin_line(p); // goto begining of cur line
1206 if (p[-1] == '\n' && p > text)
1207 p--; // step to prev line
1208 p = begin_line(p); // goto begining of prev line
1212 static Byte *next_line(Byte * p) // return pointer first char next line
1215 if (*p == '\n' && p < end - 1)
1216 p++; // step to next line
1220 //----- Text Information Routines ------------------------------
1221 static Byte *end_screen(void)
1226 // find new bottom line
1228 for (cnt = 0; cnt < rows - 2; cnt++)
1234 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1239 if (stop < start) { // start and stop are backwards- reverse them
1245 stop = end_line(stop); // get to end of this line
1246 for (q = start; q <= stop && q <= end - 1; q++) {
1253 static Byte *find_line(int li) // find begining of line #li
1257 for (q = text; li > 1; li--) {
1263 //----- Dot Movement Routines ----------------------------------
1264 static void dot_left(void)
1266 if (dot > text && dot[-1] != '\n')
1270 static void dot_right(void)
1272 if (dot < end - 1 && *dot != '\n')
1276 static void dot_begin(void)
1278 dot = begin_line(dot); // return pointer to first char cur line
1281 static void dot_end(void)
1283 dot = end_line(dot); // return pointer to last char cur line
1286 static Byte *move_to_col(Byte * p, int l)
1293 if (*p == '\n' || *p == '\0')
1297 co += ((tabstop - 1) - (co % tabstop));
1298 } else if (*p < ' ' || *p == 127) {
1299 co++; // display as ^X, use 2 columns
1301 } while (++co <= l && p++ < end);
1305 static void dot_next(void)
1307 dot = next_line(dot);
1310 static void dot_prev(void)
1312 dot = prev_line(dot);
1315 static void dot_scroll(int cnt, int dir)
1319 for (; cnt > 0; cnt--) {
1322 // ctrl-Y scroll up one line
1323 screenbegin = prev_line(screenbegin);
1326 // ctrl-E scroll down one line
1327 screenbegin = next_line(screenbegin);
1330 // make sure "dot" stays on the screen so we dont scroll off
1331 if (dot < screenbegin)
1333 q = end_screen(); // find new bottom line
1335 dot = begin_line(q); // is dot is below bottom line?
1339 static void dot_skip_over_ws(void)
1342 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1346 static void dot_delete(void) // delete the char at 'dot'
1348 (void) text_hole_delete(dot, dot);
1351 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1353 if (p >= end && end > text) {
1355 indicate_error('1');
1359 indicate_error('2');
1364 //----- Helper Utility Routines --------------------------------
1366 //----------------------------------------------------------------
1367 //----- Char Routines --------------------------------------------
1368 /* Chars that are part of a word-
1369 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1370 * Chars that are Not part of a word (stoppers)
1371 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1372 * Chars that are WhiteSpace
1373 * TAB NEWLINE VT FF RETURN SPACE
1374 * DO NOT COUNT NEWLINE AS WHITESPACE
1377 static Byte *new_screen(int ro, int co)
1382 screensize = ro * co + 8;
1383 screen = (Byte *) xmalloc(screensize);
1384 // initialize the new screen. assume this will be a empty file.
1386 // non-existent text[] lines start with a tilde (~).
1387 for (li = 1; li < ro - 1; li++) {
1388 screen[(li * co) + 0] = '~';
1393 static Byte *new_text(int size)
1396 size = 10240; // have a minimum size for new files
1398 text = (Byte *) xmalloc(size + 8);
1399 memset(text, '\0', size); // clear new text[]
1400 //text += 4; // leave some room for "oops"
1401 textend = text + size - 1;
1402 //textend -= 4; // leave some root for "oops"
1406 #ifdef CONFIG_FEATURE_VI_SEARCH
1407 static int mycmp(Byte * s1, Byte * s2, int len)
1411 i = strncmp((char *) s1, (char *) s2, len);
1412 #ifdef CONFIG_FEATURE_VI_SETOPTS
1414 i = strncasecmp((char *) s1, (char *) s2, len);
1416 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1420 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1422 #ifndef REGEX_SEARCH
1426 len = strlen((char *) pat);
1427 if (dir == FORWARD) {
1428 stop = end - 1; // assume range is p - end-1
1429 if (range == LIMITED)
1430 stop = next_line(p); // range is to next line
1431 for (start = p; start < stop; start++) {
1432 if (mycmp(start, pat, len) == 0) {
1436 } else if (dir == BACK) {
1437 stop = text; // assume range is text - p
1438 if (range == LIMITED)
1439 stop = prev_line(p); // range is to prev line
1440 for (start = p - len; start >= stop; start--) {
1441 if (mycmp(start, pat, len) == 0) {
1446 // pattern not found
1448 #else /*REGEX_SEARCH */
1450 struct re_pattern_buffer preg;
1454 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1460 // assume a LIMITED forward search
1468 // count the number of chars to search over, forward or backward
1472 // RANGE could be negative if we are searching backwards
1475 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1477 // The pattern was not compiled
1478 psbs("bad search pattern: \"%s\": %s", pat, q);
1479 i = 0; // return p if pattern not compiled
1489 // search for the compiled pattern, preg, in p[]
1490 // range < 0- search backward
1491 // range > 0- search forward
1493 // re_search() < 0 not found or error
1494 // re_search() > 0 index of found pattern
1495 // struct pattern char int int int struct reg
1496 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1497 i = re_search(&preg, q, size, 0, range, 0);
1500 i = 0; // return NULL if pattern not found
1503 if (dir == FORWARD) {
1509 #endif /*REGEX_SEARCH */
1511 #endif /* CONFIG_FEATURE_VI_SEARCH */
1513 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1515 if (c == 22) { // Is this an ctrl-V?
1516 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1517 p--; // backup onto ^
1518 refresh(FALSE); // show the ^
1522 file_modified++; // has the file been modified
1523 } else if (c == 27) { // Is this an ESC?
1526 end_cmd_q(); // stop adding to q
1527 last_status_cksum = 0; // force status update
1528 if ((p[-1] != '\n') && (dot>text)) {
1531 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1533 if ((p[-1] != '\n') && (dot>text)) {
1535 p = text_hole_delete(p, p); // shrink buffer 1 char
1538 // insert a char into text[]
1539 Byte *sp; // "save p"
1542 c = '\n'; // translate \r to \n
1543 sp = p; // remember addr of insert
1544 p = stupid_insert(p, c); // insert the char
1545 #ifdef CONFIG_FEATURE_VI_SETOPTS
1546 if (showmatch && strchr(")]}", *sp) != NULL) {
1549 if (autoindent && c == '\n') { // auto indent the new line
1552 q = prev_line(p); // use prev line as templet
1553 for (; isblnk(*q); q++) {
1554 p = stupid_insert(p, *q); // insert the char
1557 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1562 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1564 p = text_hole_make(p, 1);
1567 file_modified++; // has the file been modified
1573 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1575 Byte *save_dot, *p, *q;
1581 if (strchr("cdy><", c)) {
1582 // these cmds operate on whole lines
1583 p = q = begin_line(p);
1584 for (cnt = 1; cnt < cmdcnt; cnt++) {
1588 } else if (strchr("^%$0bBeEft", c)) {
1589 // These cmds operate on char positions
1590 do_cmd(c); // execute movement cmd
1592 } else if (strchr("wW", c)) {
1593 do_cmd(c); // execute movement cmd
1594 // if we are at the next word's first char
1595 // step back one char
1596 // but check the possibilities when it is true
1597 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1598 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1599 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1600 dot--; // move back off of next word
1601 if (dot > text && *dot == '\n')
1602 dot--; // stay off NL
1604 } else if (strchr("H-k{", c)) {
1605 // these operate on multi-lines backwards
1606 q = end_line(dot); // find NL
1607 do_cmd(c); // execute movement cmd
1610 } else if (strchr("L+j}\r\n", c)) {
1611 // these operate on multi-lines forwards
1612 p = begin_line(dot);
1613 do_cmd(c); // execute movement cmd
1614 dot_end(); // find NL
1617 c = 27; // error- return an ESC char
1630 static int st_test(Byte * p, int type, int dir, Byte * tested)
1640 if (type == S_BEFORE_WS) {
1642 test = ((!isspace(c)) || c == '\n');
1644 if (type == S_TO_WS) {
1646 test = ((!isspace(c)) || c == '\n');
1648 if (type == S_OVER_WS) {
1650 test = ((isspace(c)));
1652 if (type == S_END_PUNCT) {
1654 test = ((ispunct(c)));
1656 if (type == S_END_ALNUM) {
1658 test = ((isalnum(c)) || c == '_');
1664 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1668 while (st_test(p, type, dir, &c)) {
1669 // make sure we limit search to correct number of lines
1670 if (c == '\n' && --linecnt < 1)
1672 if (dir >= 0 && p >= end - 1)
1674 if (dir < 0 && p <= text)
1676 p += dir; // move to next char
1681 // find matching char of pair () [] {}
1682 static Byte *find_pair(Byte * p, Byte c)
1689 dir = 1; // assume forward
1713 for (q = p + dir; text <= q && q < end; q += dir) {
1714 // look for match, count levels of pairs (( ))
1716 level++; // increase pair levels
1718 level--; // reduce pair level
1720 break; // found matching pair
1723 q = NULL; // indicate no match
1727 #ifdef CONFIG_FEATURE_VI_SETOPTS
1728 // show the matching char of a pair, () [] {}
1729 static void showmatching(Byte * p)
1733 // we found half of a pair
1734 q = find_pair(p, *p); // get loc of matching char
1736 indicate_error('3'); // no matching char
1738 // "q" now points to matching pair
1739 save_dot = dot; // remember where we are
1740 dot = q; // go to new loc
1741 refresh(FALSE); // let the user see it
1742 (void) mysleep(40); // give user some time
1743 dot = save_dot; // go back to old loc
1747 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1749 // open a hole in text[]
1750 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1759 cnt = end - src; // the rest of buffer
1760 if (memmove(dest, src, cnt) != dest) {
1761 psbs("can't create room for new characters");
1763 memset(p, ' ', size); // clear new hole
1764 end = end + size; // adjust the new END
1765 file_modified++; // has the file been modified
1770 // close a hole in text[]
1771 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1776 // move forwards, from beginning
1780 if (q < p) { // they are backward- swap them
1784 hole_size = q - p + 1;
1786 if (src < text || src > end)
1788 if (dest < text || dest >= end)
1791 goto thd_atend; // just delete the end of the buffer
1792 if (memmove(dest, src, cnt) != dest) {
1793 psbs("can't delete the character");
1796 end = end - hole_size; // adjust the new END
1798 dest = end - 1; // make sure dest in below end-1
1800 dest = end = text; // keep pointers valid
1801 file_modified++; // has the file been modified
1806 // copy text into register, then delete text.
1807 // if dist <= 0, do not include, or go past, a NewLine
1809 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1813 // make sure start <= stop
1815 // they are backwards, reverse them
1821 // we can not cross NL boundaries
1825 // dont go past a NewLine
1826 for (; p + 1 <= stop; p++) {
1828 stop = p; // "stop" just before NewLine
1834 #ifdef CONFIG_FEATURE_VI_YANKMARK
1835 text_yank(start, stop, YDreg);
1836 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1837 if (yf == YANKDEL) {
1838 p = text_hole_delete(start, stop);
1843 static void show_help(void)
1845 puts("These features are available:"
1846 #ifdef CONFIG_FEATURE_VI_SEARCH
1847 "\n\tPattern searches with / and ?"
1848 #endif /* CONFIG_FEATURE_VI_SEARCH */
1849 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1850 "\n\tLast command repeat with \'.\'"
1851 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1852 #ifdef CONFIG_FEATURE_VI_YANKMARK
1853 "\n\tLine marking with 'x"
1854 "\n\tNamed buffers with \"x"
1855 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1856 #ifdef CONFIG_FEATURE_VI_READONLY
1857 "\n\tReadonly if vi is called as \"view\""
1858 "\n\tReadonly with -R command line arg"
1859 #endif /* CONFIG_FEATURE_VI_READONLY */
1860 #ifdef CONFIG_FEATURE_VI_SET
1861 "\n\tSome colon mode commands with \':\'"
1862 #endif /* CONFIG_FEATURE_VI_SET */
1863 #ifdef CONFIG_FEATURE_VI_SETOPTS
1864 "\n\tSettable options with \":set\""
1865 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1866 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1867 "\n\tSignal catching- ^C"
1868 "\n\tJob suspend and resume with ^Z"
1869 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1870 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1871 "\n\tAdapt to window re-sizes"
1872 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1876 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1881 strcpy((char *) buf, ""); // init buf
1882 if (strlen((char *) s) <= 0)
1883 s = (Byte *) "(NULL)";
1884 for (; *s > '\0'; s++) {
1888 c_is_no_print = c > 127 && !Isprint(c);
1889 if (c_is_no_print) {
1890 strcat((char *) buf, SOn);
1893 if (c < ' ' || c == 127) {
1894 strcat((char *) buf, "^");
1901 strcat((char *) buf, (char *) b);
1903 strcat((char *) buf, SOs);
1905 strcat((char *) buf, "$");
1910 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1911 static void start_new_cmd_q(Byte c)
1914 free(last_modifying_cmd);
1915 // get buffer for new cmd
1916 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1917 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1918 // if there is a current cmd count put it in the buffer first
1920 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1921 else // just save char c onto queue
1922 last_modifying_cmd[0] = c;
1926 static void end_cmd_q(void)
1928 #ifdef CONFIG_FEATURE_VI_YANKMARK
1929 YDreg = 26; // go back to default Yank/Delete reg
1930 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1934 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1936 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1937 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1941 i = strlen((char *) s);
1942 p = text_hole_make(p, i);
1943 strncpy((char *) p, (char *) s, i);
1944 for (cnt = 0; *s != '\0'; s++) {
1948 #ifdef CONFIG_FEATURE_VI_YANKMARK
1949 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1950 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1953 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1955 #ifdef CONFIG_FEATURE_VI_YANKMARK
1956 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1961 if (q < p) { // they are backwards- reverse them
1968 free(t); // if already a yank register, free it
1969 t = (Byte *) xmalloc(cnt + 1); // get a new register
1970 memset(t, '\0', cnt + 1); // clear new text[]
1971 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1976 static Byte what_reg(void)
1982 c = 'D'; // default to D-reg
1983 if (0 <= YDreg && YDreg <= 25)
1984 c = 'a' + (Byte) YDreg;
1992 static void check_context(Byte cmd)
1994 // A context is defined to be "modifying text"
1995 // Any modifying command establishes a new context.
1997 if (dot < context_start || dot > context_end) {
1998 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1999 // we are trying to modify text[]- make this the current context
2000 mark[27] = mark[26]; // move cur to prev
2001 mark[26] = dot; // move local to cur
2002 context_start = prev_line(prev_line(dot));
2003 context_end = next_line(next_line(dot));
2004 //loiter= start_loiter= now;
2010 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2014 // the current context is in mark[26]
2015 // the previous context is in mark[27]
2016 // only swap context if other context is valid
2017 if (text <= mark[27] && mark[27] <= end - 1) {
2019 mark[27] = mark[26];
2021 p = mark[26]; // where we are going- previous context
2022 context_start = prev_line(prev_line(prev_line(p)));
2023 context_end = next_line(next_line(next_line(p)));
2027 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2029 static int isblnk(Byte c) // is the char a blank or tab
2031 return (c == ' ' || c == '\t');
2034 //----- Set terminal attributes --------------------------------
2035 static void rawmode(void)
2037 tcgetattr(0, &term_orig);
2038 term_vi = term_orig;
2039 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2040 term_vi.c_iflag &= (~IXON & ~ICRNL);
2041 term_vi.c_oflag &= (~ONLCR);
2042 term_vi.c_cc[VMIN] = 1;
2043 term_vi.c_cc[VTIME] = 0;
2044 erase_char = term_vi.c_cc[VERASE];
2045 tcsetattr(0, TCSANOW, &term_vi);
2048 static void cookmode(void)
2051 tcsetattr(0, TCSANOW, &term_orig);
2054 //----- Come here when we get a window resize signal ---------
2055 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2056 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2058 signal(SIGWINCH, winch_sig);
2059 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2060 get_terminal_width_height(0, &columns, &rows);
2061 new_screen(rows, columns); // get memory for virtual screen
2062 redraw(TRUE); // re-draw the screen
2065 //----- Come here when we get a continue signal -------------------
2066 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2068 rawmode(); // terminal to "raw"
2069 last_status_cksum = 0; // force status update
2070 redraw(TRUE); // re-draw the screen
2072 signal(SIGTSTP, suspend_sig);
2073 signal(SIGCONT, SIG_DFL);
2074 kill(my_pid, SIGCONT);
2077 //----- Come here when we get a Suspend signal -------------------
2078 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2080 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2081 clear_to_eol(); // Erase to end of line
2082 cookmode(); // terminal to "cooked"
2084 signal(SIGCONT, cont_sig);
2085 signal(SIGTSTP, SIG_DFL);
2086 kill(my_pid, SIGTSTP);
2089 //----- Come here when we get a signal ---------------------------
2090 static void catch_sig(int sig)
2092 signal(SIGINT, catch_sig);
2094 longjmp(restart, sig);
2096 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2098 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2100 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2105 tv.tv_usec = hund * 10000;
2106 select(1, &rfds, NULL, NULL, &tv);
2107 return (FD_ISSET(0, &rfds));
2110 #define readbuffer bb_common_bufsiz1
2112 static int readed_for_parse;
2114 //----- IO Routines --------------------------------------------
2115 static Byte readit(void) // read (maybe cursor) key from stdin
2124 static const struct esc_cmds esccmds[] = {
2125 {"OA", (Byte) VI_K_UP}, // cursor key Up
2126 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2127 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2128 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2129 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2130 {"OF", (Byte) VI_K_END}, // Cursor Key End
2131 {"[A", (Byte) VI_K_UP}, // cursor key Up
2132 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2133 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2134 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2135 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2136 {"[F", (Byte) VI_K_END}, // Cursor Key End
2137 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2138 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2139 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2140 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2141 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2142 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2143 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2144 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2145 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2146 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2147 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2148 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2149 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2150 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2151 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2152 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2153 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2154 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2155 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2156 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2157 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2160 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2162 (void) alarm(0); // turn alarm OFF while we wait for input
2164 n = readed_for_parse;
2165 // get input from User- are there already input chars in Q?
2168 // the Q is empty, wait for a typed char
2169 n = read(0, readbuffer, BUFSIZ - 1);
2172 goto ri0; // interrupted sys call
2175 if (errno == EFAULT)
2177 if (errno == EINVAL)
2185 if (readbuffer[0] == 27) {
2186 // This is an ESC char. Is this Esc sequence?
2187 // Could be bare Esc key. See if there are any
2188 // more chars to read after the ESC. This would
2189 // be a Function or Cursor Key sequence.
2193 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2195 // keep reading while there are input chars and room in buffer
2196 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2197 // read the rest of the ESC string
2198 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2204 readed_for_parse = n;
2207 if(c == 27 && n > 1) {
2208 // Maybe cursor or function key?
2209 const struct esc_cmds *eindex;
2211 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2212 int cnt = strlen(eindex->seq);
2216 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2218 // is a Cursor key- put derived value back into Q
2220 // for squeeze out the ESC sequence
2224 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2225 /* defined ESC sequence not found, set only one ESC */
2231 // remove key sequence from Q
2232 readed_for_parse -= n;
2233 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2234 (void) alarm(3); // we are done waiting for input, turn alarm ON
2238 //----- IO Routines --------------------------------------------
2239 static Byte get_one_char(void)
2243 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2244 // ! adding2q && ioq == 0 read()
2245 // ! adding2q && ioq != 0 *ioq
2246 // adding2q *last_modifying_cmd= read()
2248 // we are not adding to the q.
2249 // but, we may be reading from a q
2251 // there is no current q, read from STDIN
2252 c = readit(); // get the users input
2254 // there is a queue to get chars from first
2257 // the end of the q, read from STDIN
2259 ioq_start = ioq = 0;
2260 c = readit(); // get the users input
2264 // adding STDIN chars to q
2265 c = readit(); // get the users input
2266 if (last_modifying_cmd != 0) {
2267 int len = strlen((char *) last_modifying_cmd);
2268 if (len + 1 >= BUFSIZ) {
2269 psbs("last_modifying_cmd overrun");
2271 // add new char to q
2272 last_modifying_cmd[len] = c;
2276 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2277 c = readit(); // get the users input
2278 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2279 return (c); // return the char, where ever it came from
2282 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2287 static Byte *obufp = NULL;
2289 strcpy((char *) buf, (char *) prompt);
2290 last_status_cksum = 0; // force status update
2291 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2292 clear_to_eol(); // clear the line
2293 write1((char *) prompt); // write out the :, /, or ? prompt
2295 for (i = strlen((char *) buf); i < BUFSIZ;) {
2296 c = get_one_char(); // read user input
2297 if (c == '\n' || c == '\r' || c == 27)
2298 break; // is this end of input
2299 if (c == erase_char || c == 8 || c == 127) {
2300 // user wants to erase prev char
2301 i--; // backup to prev char
2302 buf[i] = '\0'; // erase the char
2303 buf[i + 1] = '\0'; // null terminate buffer
2304 write1("\b \b"); // erase char on screen
2305 if (i <= 0) { // user backs up before b-o-l, exit
2309 buf[i] = c; // save char in buffer
2310 buf[i + 1] = '\0'; // make sure buffer is null terminated
2311 putchar(c); // echo the char back to user
2317 obufp = (Byte *) bb_xstrdup((char *) buf);
2321 static int file_size(const Byte * fn) // what is the byte size of "fn"
2326 if (fn == 0 || strlen((char *)fn) <= 0)
2329 sr = stat((char *) fn, &st_buf); // see if file exists
2331 cnt = (int) st_buf.st_size;
2336 static int file_insert(Byte * fn, Byte * p, int size)
2341 #ifdef CONFIG_FEATURE_VI_READONLY
2343 #endif /* CONFIG_FEATURE_VI_READONLY */
2344 if (fn == 0 || strlen((char*) fn) <= 0) {
2345 psbs("No filename given");
2349 // OK- this is just a no-op
2354 psbs("Trying to insert a negative number (%d) of characters", size);
2357 if (p < text || p > end) {
2358 psbs("Trying to insert file outside of memory");
2362 // see if we can open the file
2363 #ifdef CONFIG_FEATURE_VI_READONLY
2364 if (vi_readonly) goto fi1; // do not try write-mode
2366 fd = open((char *) fn, O_RDWR); // assume read & write
2368 // could not open for writing- maybe file is read only
2369 #ifdef CONFIG_FEATURE_VI_READONLY
2372 fd = open((char *) fn, O_RDONLY); // try read-only
2374 psbs("\"%s\" %s", fn, "could not open file");
2377 #ifdef CONFIG_FEATURE_VI_READONLY
2378 // got the file- read-only
2380 #endif /* CONFIG_FEATURE_VI_READONLY */
2382 p = text_hole_make(p, size);
2383 cnt = read(fd, p, size);
2387 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2388 psbs("could not read file \"%s\"", fn);
2389 } else if (cnt < size) {
2390 // There was a partial read, shrink unused space text[]
2391 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2392 psbs("could not read all of file \"%s\"", fn);
2400 static int file_write(Byte * fn, Byte * first, Byte * last)
2402 int fd, cnt, charcnt;
2405 psbs("No current filename");
2409 // FIXIT- use the correct umask()
2410 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2413 cnt = last - first + 1;
2414 charcnt = write(fd, first, cnt);
2415 if (charcnt == cnt) {
2417 //file_modified= FALSE; // the file has not been modified
2425 //----- Terminal Drawing ---------------------------------------
2426 // The terminal is made up of 'rows' line of 'columns' columns.
2427 // classically this would be 24 x 80.
2428 // screen coordinates
2434 // 23,0 ... 23,79 status line
2437 //----- Move the cursor to row x col (count from 0, not 1) -------
2438 static void place_cursor(int row, int col, int opti)
2442 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2445 // char cm3[BUFSIZ];
2447 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2449 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2451 if (row < 0) row = 0;
2452 if (row >= rows) row = rows - 1;
2453 if (col < 0) col = 0;
2454 if (col >= columns) col = columns - 1;
2456 //----- 1. Try the standard terminal ESC sequence
2457 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2459 if (! opti) goto pc0;
2461 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2462 //----- find the minimum # of chars to move cursor -------------
2463 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2464 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2466 // move to the correct row
2467 while (row < Rrow) {
2468 // the cursor has to move up
2472 while (row > Rrow) {
2473 // the cursor has to move down
2474 strcat(cm2, CMdown);
2478 // now move to the correct column
2479 strcat(cm2, "\r"); // start at col 0
2480 // just send out orignal source char to get to correct place
2481 screenp = &screen[row * columns]; // start of screen line
2482 strncat(cm2, (char* )screenp, col);
2484 //----- 3. Try some other way of moving cursor
2485 //---------------------------------------------
2487 // pick the shortest cursor motion to send out
2489 if (strlen(cm2) < strlen(cm)) {
2491 } /* else if (strlen(cm3) < strlen(cm)) {
2494 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2496 write1(cm); // move the cursor
2499 //----- Erase from cursor to end of line -----------------------
2500 static void clear_to_eol(void)
2502 write1(Ceol); // Erase from cursor to end of line
2505 //----- Erase from cursor to end of screen -----------------------
2506 static void clear_to_eos(void)
2508 write1(Ceos); // Erase from cursor to end of screen
2511 //----- Start standout mode ------------------------------------
2512 static void standout_start(void) // send "start reverse video" sequence
2514 write1(SOs); // Start reverse video mode
2517 //----- End standout mode --------------------------------------
2518 static void standout_end(void) // send "end reverse video" sequence
2520 write1(SOn); // End reverse video mode
2523 //----- Flash the screen --------------------------------------
2524 static void flash(int h)
2526 standout_start(); // send "start reverse video" sequence
2529 standout_end(); // send "end reverse video" sequence
2533 static void Indicate_Error(void)
2535 #ifdef CONFIG_FEATURE_VI_CRASHME
2537 return; // generate a random command
2538 #endif /* CONFIG_FEATURE_VI_CRASHME */
2540 write1(bell); // send out a bell character
2546 //----- Screen[] Routines --------------------------------------
2547 //----- Erase the Screen[] memory ------------------------------
2548 static void screen_erase(void)
2550 memset(screen, ' ', screensize); // clear new screen
2553 static int bufsum(unsigned char *buf, int count)
2556 unsigned char *e = buf + count;
2562 //----- Draw the status line at bottom of the screen -------------
2563 static void show_status_line(void)
2565 int cnt = 0, cksum = 0;
2567 // either we already have an error or status message, or we
2569 if (!have_status_msg) {
2570 cnt = format_edit_status();
2571 cksum = bufsum(status_buffer, cnt);
2573 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2574 last_status_cksum= cksum; // remember if we have seen this line
2575 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2576 write1((char*)status_buffer);
2578 if (have_status_msg) {
2579 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2581 have_status_msg = 0;
2584 have_status_msg = 0;
2586 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2591 //----- format the status buffer, the bottom line of screen ------
2592 // format status buffer, with STANDOUT mode
2593 static void psbs(const char *format, ...)
2597 va_start(args, format);
2598 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2599 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2600 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2603 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2608 // format status buffer
2609 static void psb(const char *format, ...)
2613 va_start(args, format);
2614 vsprintf((char *) status_buffer, format, args);
2617 have_status_msg = 1;
2622 static void ni(Byte * s) // display messages
2626 print_literal(buf, s);
2627 psbs("\'%s\' is not implemented", buf);
2630 static int format_edit_status(void) // show file status on status line
2632 int cur, percent, ret, trunc_at;
2635 // file_modified is now a counter rather than a flag. this
2636 // helps reduce the amount of line counting we need to do.
2637 // (this will cause a mis-reporting of modified status
2638 // once every MAXINT editing operations.)
2640 // it would be nice to do a similar optimization here -- if
2641 // we haven't done a motion that could have changed which line
2642 // we're on, then we shouldn't have to do this count_lines()
2643 cur = count_lines(text, dot);
2645 // reduce counting -- the total lines can't have
2646 // changed if we haven't done any edits.
2647 if (file_modified != last_file_modified) {
2648 tot = cur + count_lines(dot, end - 1) - 1;
2649 last_file_modified = file_modified;
2652 // current line percent
2653 // ------------- ~~ ----------
2656 percent = (100 * cur) / tot;
2662 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2663 columns : STATUS_BUFFER_LEN-1;
2665 ret = snprintf((char *) status_buffer, trunc_at+1,
2666 #ifdef CONFIG_FEATURE_VI_READONLY
2667 "%c %s%s%s %d/%d %d%%",
2669 "%c %s%s %d/%d %d%%",
2671 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2672 (cfn != 0 ? (char *) cfn : "No file"),
2673 #ifdef CONFIG_FEATURE_VI_READONLY
2674 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2676 (file_modified ? " [modified]" : ""),
2679 if (ret >= 0 && ret < trunc_at)
2680 return ret; /* it all fit */
2682 return trunc_at; /* had to truncate */
2685 //----- Force refresh of all Lines -----------------------------
2686 static void redraw(int full_screen)
2688 place_cursor(0, 0, FALSE); // put cursor in correct place
2689 clear_to_eos(); // tel terminal to erase display
2690 screen_erase(); // erase the internal screen buffer
2691 last_status_cksum = 0; // force status update
2692 refresh(full_screen); // this will redraw the entire display
2696 //----- Format a text[] line into a buffer ---------------------
2697 static void format_line(Byte *dest, Byte *src, int li)
2702 for (co= 0; co < MAX_SCR_COLS; co++) {
2703 c= ' '; // assume blank
2704 if (li > 0 && co == 0) {
2705 c = '~'; // not first line, assume Tilde
2707 // are there chars in text[] and have we gone past the end
2708 if (text < end && src < end) {
2713 if (c > 127 && !Isprint(c)) {
2716 if (c < ' ' || c == 127) {
2720 for (; (co % tabstop) != (tabstop - 1); co++) {
2728 c += '@'; // make it visible
2731 // the co++ is done here so that the column will
2732 // not be overwritten when we blank-out the rest of line
2739 //----- Refresh the changed screen lines -----------------------
2740 // Copy the source line from text[] into the buffer and note
2741 // if the current screenline is different from the new buffer.
2742 // If they differ then that line needs redrawing on the terminal.
2744 static void refresh(int full_screen)
2746 static int old_offset;
2748 Byte buf[MAX_SCR_COLS];
2749 Byte *tp, *sp; // pointer into text[] and screen[]
2750 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2751 int last_li= -2; // last line that changed- for optimizing cursor movement
2752 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2754 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2755 get_terminal_width_height(0, &columns, &rows);
2756 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2757 tp = screenbegin; // index into text[] of top line
2759 // compare text[] to screen[] and mark screen[] lines that need updating
2760 for (li = 0; li < rows - 1; li++) {
2761 int cs, ce; // column start & end
2762 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2763 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2764 // format current text line into buf
2765 format_line(buf, tp, li);
2767 // skip to the end of the current text[] line
2768 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2770 // see if there are any changes between vitual screen and buf
2771 changed = FALSE; // assume no change
2774 sp = &screen[li * columns]; // start of screen line
2776 // force re-draw of every single column from 0 - columns-1
2779 // compare newly formatted buffer with virtual screen
2780 // look forward for first difference between buf and screen
2781 for ( ; cs <= ce; cs++) {
2782 if (buf[cs + offset] != sp[cs]) {
2783 changed = TRUE; // mark for redraw
2788 // look backward for last difference between buf and screen
2789 for ( ; ce >= cs; ce--) {
2790 if (buf[ce + offset] != sp[ce]) {
2791 changed = TRUE; // mark for redraw
2795 // now, cs is index of first diff, and ce is index of last diff
2797 // if horz offset has changed, force a redraw
2798 if (offset != old_offset) {
2803 // make a sanity check of columns indexes
2805 if (ce > columns-1) ce= columns-1;
2806 if (cs > ce) { cs= 0; ce= columns-1; }
2807 // is there a change between vitual screen and buf
2809 // copy changed part of buffer to virtual screen
2810 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2812 // move cursor to column of first change
2813 if (offset != old_offset) {
2814 // opti_cur_move is still too stupid
2815 // to handle offsets correctly
2816 place_cursor(li, cs, FALSE);
2818 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2819 // if this just the next line
2820 // try to optimize cursor movement
2821 // otherwise, use standard ESC sequence
2822 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2824 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2825 place_cursor(li, cs, FALSE); // use standard ESC sequence
2826 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2829 // write line out to terminal
2832 char *out = (char*)sp+cs;
2839 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2841 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2845 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2846 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2849 place_cursor(crow, ccol, FALSE);
2850 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2852 if (offset != old_offset)
2853 old_offset = offset;
2856 //---------------------------------------------------------------------
2857 //----- the Ascii Chart -----------------------------------------------
2859 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2860 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2861 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2862 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2863 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2864 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2865 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2866 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2867 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2868 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2869 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2870 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2871 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2872 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2873 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2874 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2875 //---------------------------------------------------------------------
2877 //----- Execute a Vi Command -----------------------------------
2878 static void do_cmd(Byte c)
2880 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2881 int cnt, i, j, dir, yf;
2883 c1 = c; // quiet the compiler
2884 cnt = yf = dir = 0; // quiet the compiler
2885 p = q = save_dot = msg = buf; // quiet the compiler
2886 memset(buf, '\0', 9); // clear buf
2890 /* if this is a cursor key, skip these checks */
2903 if (cmd_mode == 2) {
2904 // flip-flop Insert/Replace mode
2905 if (c == VI_K_INSERT) goto dc_i;
2906 // we are 'R'eplacing the current *dot with new char
2908 // don't Replace past E-o-l
2909 cmd_mode = 1; // convert to insert
2911 if (1 <= c || Isprint(c)) {
2913 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2914 dot = char_insert(dot, c); // insert new char
2919 if (cmd_mode == 1) {
2920 // hitting "Insert" twice means "R" replace mode
2921 if (c == VI_K_INSERT) goto dc5;
2922 // insert the char c at "dot"
2923 if (1 <= c || Isprint(c)) {
2924 dot = char_insert(dot, c);
2939 #ifdef CONFIG_FEATURE_VI_CRASHME
2940 case 0x14: // dc4 ctrl-T
2941 crashme = (crashme == 0) ? 1 : 0;
2943 #endif /* CONFIG_FEATURE_VI_CRASHME */
2972 //case 'u': // u- FIXME- there is no undo
2974 default: // unrecognised command
2983 end_cmd_q(); // stop adding to q
2984 case 0x00: // nul- ignore
2986 case 2: // ctrl-B scroll up full screen
2987 case VI_K_PAGEUP: // Cursor Key Page Up
2988 dot_scroll(rows - 2, -1);
2990 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2991 case 0x03: // ctrl-C interrupt
2992 longjmp(restart, 1);
2994 case 26: // ctrl-Z suspend
2995 suspend_sig(SIGTSTP);
2997 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2998 case 4: // ctrl-D scroll down half screen
2999 dot_scroll((rows - 2) / 2, 1);
3001 case 5: // ctrl-E scroll down one line
3004 case 6: // ctrl-F scroll down full screen
3005 case VI_K_PAGEDOWN: // Cursor Key Page Down
3006 dot_scroll(rows - 2, 1);
3008 case 7: // ctrl-G show current status
3009 last_status_cksum = 0; // force status update
3011 case 'h': // h- move left
3012 case VI_K_LEFT: // cursor key Left
3013 case 8: // ctrl-H- move left (This may be ERASE char)
3014 case 127: // DEL- move left (This may be ERASE char)
3020 case 10: // Newline ^J
3021 case 'j': // j- goto next line, same col
3022 case VI_K_DOWN: // cursor key Down
3026 dot_next(); // go to next B-o-l
3027 dot = move_to_col(dot, ccol + offset); // try stay in same col
3029 case 12: // ctrl-L force redraw whole screen
3030 case 18: // ctrl-R force redraw
3031 place_cursor(0, 0, FALSE); // put cursor in correct place
3032 clear_to_eos(); // tel terminal to erase display
3034 screen_erase(); // erase the internal screen buffer
3035 last_status_cksum = 0; // force status update
3036 refresh(TRUE); // this will redraw the entire display
3038 case 13: // Carriage Return ^M
3039 case '+': // +- goto next line
3046 case 21: // ctrl-U scroll up half screen
3047 dot_scroll((rows - 2) / 2, -1);
3049 case 25: // ctrl-Y scroll up one line
3055 cmd_mode = 0; // stop insrting
3057 last_status_cksum = 0; // force status update
3059 case ' ': // move right
3060 case 'l': // move right
3061 case VI_K_RIGHT: // Cursor Key Right
3067 #ifdef CONFIG_FEATURE_VI_YANKMARK
3068 case '"': // "- name a register to use for Delete/Yank
3069 c1 = get_one_char();
3077 case '\'': // '- goto a specific mark
3078 c1 = get_one_char();
3084 if (text <= q && q < end) {
3086 dot_begin(); // go to B-o-l
3089 } else if (c1 == '\'') { // goto previous context
3090 dot = swap_context(dot); // swap current and previous context
3091 dot_begin(); // go to B-o-l
3097 case 'm': // m- Mark a line
3098 // this is really stupid. If there are any inserts or deletes
3099 // between text[0] and dot then this mark will not point to the
3100 // correct location! It could be off by many lines!
3101 // Well..., at least its quick and dirty.
3102 c1 = get_one_char();
3106 // remember the line
3107 mark[(int) c1] = dot;
3112 case 'P': // P- Put register before
3113 case 'p': // p- put register after
3116 psbs("Nothing in register %c", what_reg());
3119 // are we putting whole lines or strings
3120 if (strchr((char *) p, '\n') != NULL) {
3122 dot_begin(); // putting lines- Put above
3125 // are we putting after very last line?
3126 if (end_line(dot) == (end - 1)) {
3127 dot = end; // force dot to end of text[]
3129 dot_next(); // next line, then put before
3134 dot_right(); // move to right, can move to NL
3136 dot = string_insert(dot, p); // insert the string
3137 end_cmd_q(); // stop adding to q
3139 case 'U': // U- Undo; replace current line with original version
3140 if (reg[Ureg] != 0) {
3141 p = begin_line(dot);
3143 p = text_hole_delete(p, q); // delete cur line
3144 p = string_insert(p, reg[Ureg]); // insert orig line
3149 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3150 case '$': // $- goto end of line
3151 case VI_K_END: // Cursor Key End
3155 dot = end_line(dot);
3157 case '%': // %- find matching char of pair () [] {}
3158 for (q = dot; q < end && *q != '\n'; q++) {
3159 if (strchr("()[]{}", *q) != NULL) {
3160 // we found half of a pair
3161 p = find_pair(q, *q);
3173 case 'f': // f- forward to a user specified char
3174 last_forward_char = get_one_char(); // get the search char
3176 // dont separate these two commands. 'f' depends on ';'
3178 //**** fall thru to ... ';'
3179 case ';': // ;- look at rest of line for last forward char
3183 if (last_forward_char == 0) break;
3185 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3188 if (*q == last_forward_char)
3191 case '-': // -- goto prev line
3198 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3199 case '.': // .- repeat the last modifying command
3200 // Stuff the last_modifying_cmd back into stdin
3201 // and let it be re-executed.
3202 if (last_modifying_cmd != 0) {
3203 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3206 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3207 #ifdef CONFIG_FEATURE_VI_SEARCH
3208 case '?': // /- search for a pattern
3209 case '/': // /- search for a pattern
3212 q = get_input_line(buf); // get input line- use "status line"
3213 if (strlen((char *) q) == 1)
3214 goto dc3; // if no pat re-use old pat
3215 if (strlen((char *) q) > 1) { // new pat- save it and find
3216 // there is a new pat
3217 free(last_search_pattern);
3218 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3219 goto dc3; // now find the pattern
3221 // user changed mind and erased the "/"- do nothing
3223 case 'N': // N- backward search for last pattern
3227 dir = BACK; // assume BACKWARD search
3229 if (last_search_pattern[0] == '?') {
3233 goto dc4; // now search for pattern
3235 case 'n': // n- repeat search for last pattern
3236 // search rest of text[] starting at next char
3237 // if search fails return orignal "p" not the "p+1" address
3242 if (last_search_pattern == 0) {
3243 msg = (Byte *) "No previous regular expression";
3246 if (last_search_pattern[0] == '/') {
3247 dir = FORWARD; // assume FORWARD search
3250 if (last_search_pattern[0] == '?') {
3255 q = char_search(p, last_search_pattern + 1, dir, FULL);
3257 dot = q; // good search, update "dot"
3261 // no pattern found between "dot" and "end"- continue at top
3266 q = char_search(p, last_search_pattern + 1, dir, FULL);
3267 if (q != NULL) { // found something
3268 dot = q; // found new pattern- goto it
3269 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3271 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3274 msg = (Byte *) "Pattern not found";
3277 if (*msg) psbs("%s", msg);
3279 case '{': // {- move backward paragraph
3280 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3281 if (q != NULL) { // found blank line
3282 dot = next_line(q); // move to next blank line
3285 case '}': // }- move forward paragraph
3286 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3287 if (q != NULL) { // found blank line
3288 dot = next_line(q); // move to next blank line
3291 #endif /* CONFIG_FEATURE_VI_SEARCH */
3292 case '0': // 0- goto begining of line
3302 if (c == '0' && cmdcnt < 1) {
3303 dot_begin(); // this was a standalone zero
3305 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3308 case ':': // :- the colon mode commands
3309 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3310 #ifdef CONFIG_FEATURE_VI_COLON
3311 colon(p); // execute the command
3312 #else /* CONFIG_FEATURE_VI_COLON */
3314 p++; // move past the ':'
3315 cnt = strlen((char *) p);
3318 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3319 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3320 if (file_modified && p[1] != '!') {
3321 psbs("No write since last change (:quit! overrides)");
3325 } else if (strncasecmp((char *) p, "write", cnt) == 0
3326 || strncasecmp((char *) p, "wq", cnt) == 0
3327 || strncasecmp((char *) p, "wn", cnt) == 0
3328 || strncasecmp((char *) p, "x", cnt) == 0) {
3329 cnt = file_write(cfn, text, end - 1);
3332 psbs("Write error: %s", strerror(errno));
3335 last_file_modified = -1;
3336 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3337 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3338 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3342 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3343 last_status_cksum = 0; // force status update
3344 } else if (sscanf((char *) p, "%d", &j) > 0) {
3345 dot = find_line(j); // go to line # j
3347 } else { // unrecognised cmd
3350 #endif /* CONFIG_FEATURE_VI_COLON */
3352 case '<': // <- Left shift something
3353 case '>': // >- Right shift something
3354 cnt = count_lines(text, dot); // remember what line we are on
3355 c1 = get_one_char(); // get the type of thing to delete
3356 find_range(&p, &q, c1);
3357 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3360 i = count_lines(p, q); // # of lines we are shifting
3361 for ( ; i > 0; i--, p = next_line(p)) {
3363 // shift left- remove tab or 8 spaces
3365 // shrink buffer 1 char
3366 (void) text_hole_delete(p, p);
3367 } else if (*p == ' ') {
3368 // we should be calculating columns, not just SPACE
3369 for (j = 0; *p == ' ' && j < tabstop; j++) {
3370 (void) text_hole_delete(p, p);
3373 } else if (c == '>') {
3374 // shift right -- add tab or 8 spaces
3375 (void) char_insert(p, '\t');
3378 dot = find_line(cnt); // what line were we on
3380 end_cmd_q(); // stop adding to q
3382 case 'A': // A- append at e-o-l
3383 dot_end(); // go to e-o-l
3384 //**** fall thru to ... 'a'
3385 case 'a': // a- append after current char
3390 case 'B': // B- back a blank-delimited Word
3391 case 'E': // E- end of a blank-delimited word
3392 case 'W': // W- forward a blank-delimited word
3399 if (c == 'W' || isspace(dot[dir])) {
3400 dot = skip_thing(dot, 1, dir, S_TO_WS);
3401 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3404 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3406 case 'C': // C- Change to e-o-l
3407 case 'D': // D- delete to e-o-l
3409 dot = dollar_line(dot); // move to before NL
3410 // copy text into a register and delete
3411 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3413 goto dc_i; // start inserting
3414 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3416 end_cmd_q(); // stop adding to q
3417 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3419 case 'G': // G- goto to a line number (default= E-O-F)
3420 dot = end - 1; // assume E-O-F
3422 dot = find_line(cmdcnt); // what line is #cmdcnt
3426 case 'H': // H- goto top line on screen
3428 if (cmdcnt > (rows - 1)) {
3429 cmdcnt = (rows - 1);
3436 case 'I': // I- insert before first non-blank
3439 //**** fall thru to ... 'i'
3440 case 'i': // i- insert before current char
3441 case VI_K_INSERT: // Cursor Key Insert
3443 cmd_mode = 1; // start insrting
3445 case 'J': // J- join current and next lines together
3449 dot_end(); // move to NL
3450 if (dot < end - 1) { // make sure not last char in text[]
3451 *dot++ = ' '; // replace NL with space
3453 while (isblnk(*dot)) { // delete leading WS
3457 end_cmd_q(); // stop adding to q
3459 case 'L': // L- goto bottom line on screen
3461 if (cmdcnt > (rows - 1)) {
3462 cmdcnt = (rows - 1);
3470 case 'M': // M- goto middle line on screen
3472 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3473 dot = next_line(dot);
3475 case 'O': // O- open a empty line above
3477 p = begin_line(dot);
3478 if (p[-1] == '\n') {
3480 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3482 dot = char_insert(dot, '\n');
3485 dot = char_insert(dot, '\n'); // i\n ESC
3490 case 'R': // R- continuous Replace char
3494 case 'X': // X- delete char before dot
3495 case 'x': // x- delete the current char
3496 case 's': // s- substitute the current char
3503 if (dot[dir] != '\n') {
3505 dot--; // delete prev char
3506 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3509 goto dc_i; // start insrting
3510 end_cmd_q(); // stop adding to q
3512 case 'Z': // Z- if modified, {write}; exit
3513 // ZZ means to save file (if necessary), then exit
3514 c1 = get_one_char();
3519 if (file_modified) {
3520 #ifdef CONFIG_FEATURE_VI_READONLY
3521 if (vi_readonly || readonly) {
3522 psbs("\"%s\" File is read only", cfn);
3525 #endif /* CONFIG_FEATURE_VI_READONLY */
3526 cnt = file_write(cfn, text, end - 1);
3529 psbs("Write error: %s", strerror(errno));
3530 } else if (cnt == (end - 1 - text + 1)) {
3537 case '^': // ^- move to first non-blank on line
3541 case 'b': // b- back a word
3542 case 'e': // e- end of word
3549 if ((dot + dir) < text || (dot + dir) > end - 1)
3552 if (isspace(*dot)) {
3553 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3555 if (isalnum(*dot) || *dot == '_') {
3556 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3557 } else if (ispunct(*dot)) {
3558 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3561 case 'c': // c- change something
3562 case 'd': // d- delete something
3563 #ifdef CONFIG_FEATURE_VI_YANKMARK
3564 case 'y': // y- yank something
3565 case 'Y': // Y- Yank a line
3566 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3567 yf = YANKDEL; // assume either "c" or "d"
3568 #ifdef CONFIG_FEATURE_VI_YANKMARK
3569 if (c == 'y' || c == 'Y')
3571 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3574 c1 = get_one_char(); // get the type of thing to delete
3575 find_range(&p, &q, c1);
3576 if (c1 == 27) { // ESC- user changed mind and wants out
3577 c = c1 = 27; // Escape- do nothing
3578 } else if (strchr("wW", c1)) {
3580 // don't include trailing WS as part of word
3581 while (isblnk(*q)) {
3582 if (q <= text || q[-1] == '\n')
3587 dot = yank_delete(p, q, 0, yf); // delete word
3588 } else if (strchr("^0bBeEft$", c1)) {
3589 // single line copy text into a register and delete
3590 dot = yank_delete(p, q, 0, yf); // delete word
3591 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3592 // multiple line copy text into a register and delete
3593 dot = yank_delete(p, q, 1, yf); // delete lines
3595 dot = char_insert(dot, '\n');
3596 // on the last line of file don't move to prev line
3597 if (dot != (end-1)) {
3600 } else if (c == 'd') {
3605 // could not recognize object
3606 c = c1 = 27; // error-
3610 // if CHANGING, not deleting, start inserting after the delete
3612 strcpy((char *) buf, "Change");
3613 goto dc_i; // start inserting
3616 strcpy((char *) buf, "Delete");
3618 #ifdef CONFIG_FEATURE_VI_YANKMARK
3619 if (c == 'y' || c == 'Y') {
3620 strcpy((char *) buf, "Yank");
3623 q = p + strlen((char *) p);
3624 for (cnt = 0; p <= q; p++) {
3628 psb("%s %d lines (%d chars) using [%c]",
3629 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3630 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3631 end_cmd_q(); // stop adding to q
3634 case 'k': // k- goto prev line, same col
3635 case VI_K_UP: // cursor key Up
3640 dot = move_to_col(dot, ccol + offset); // try stay in same col
3642 case 'r': // r- replace the current char with user input
3643 c1 = get_one_char(); // get the replacement char
3646 file_modified++; // has the file been modified
3648 end_cmd_q(); // stop adding to q
3650 case 't': // t- move to char prior to next x
3651 last_forward_char = get_one_char();
3653 if (*dot == last_forward_char)
3655 last_forward_char= 0;
3657 case 'w': // w- forward a word
3661 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3662 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3663 } else if (ispunct(*dot)) { // we are on PUNCT
3664 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3667 dot++; // move over word
3668 if (isspace(*dot)) {
3669 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3673 c1 = get_one_char(); // get the replacement char
3676 cnt = (rows - 2) / 2; // put dot at center
3678 cnt = rows - 2; // put dot at bottom
3679 screenbegin = begin_line(dot); // start dot at top
3680 dot_scroll(cnt, -1);
3682 case '|': // |- move to column "cmdcnt"
3683 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3685 case '~': // ~- flip the case of letters a-z -> A-Z
3689 if (islower(*dot)) {
3690 *dot = toupper(*dot);
3691 file_modified++; // has the file been modified
3692 } else if (isupper(*dot)) {
3693 *dot = tolower(*dot);
3694 file_modified++; // has the file been modified
3697 end_cmd_q(); // stop adding to q
3699 //----- The Cursor and Function Keys -----------------------------
3700 case VI_K_HOME: // Cursor Key Home
3703 // The Fn keys could point to do_macro which could translate them
3704 case VI_K_FUN1: // Function Key F1
3705 case VI_K_FUN2: // Function Key F2
3706 case VI_K_FUN3: // Function Key F3
3707 case VI_K_FUN4: // Function Key F4
3708 case VI_K_FUN5: // Function Key F5
3709 case VI_K_FUN6: // Function Key F6
3710 case VI_K_FUN7: // Function Key F7
3711 case VI_K_FUN8: // Function Key F8
3712 case VI_K_FUN9: // Function Key F9
3713 case VI_K_FUN10: // Function Key F10
3714 case VI_K_FUN11: // Function Key F11
3715 case VI_K_FUN12: // Function Key F12
3720 // if text[] just became empty, add back an empty line
3722 (void) char_insert(text, '\n'); // start empty buf with dummy line
3725 // it is OK for dot to exactly equal to end, otherwise check dot validity
3727 dot = bound_dot(dot); // make sure "dot" is valid
3729 #ifdef CONFIG_FEATURE_VI_YANKMARK
3730 check_context(c); // update the current context
3731 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3734 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3735 cnt = dot - begin_line(dot);
3736 // Try to stay off of the Newline
3737 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3741 #ifdef CONFIG_FEATURE_VI_CRASHME
3742 static int totalcmds = 0;
3743 static int Mp = 85; // Movement command Probability
3744 static int Np = 90; // Non-movement command Probability
3745 static int Dp = 96; // Delete command Probability
3746 static int Ip = 97; // Insert command Probability
3747 static int Yp = 98; // Yank command Probability
3748 static int Pp = 99; // Put command Probability
3749 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3750 char chars[20] = "\t012345 abcdABCD-=.$";
3751 char *words[20] = { "this", "is", "a", "test",
3752 "broadcast", "the", "emergency", "of",
3753 "system", "quick", "brown", "fox",
3754 "jumped", "over", "lazy", "dogs",
3755 "back", "January", "Febuary", "March"
3758 "You should have received a copy of the GNU General Public License\n",
3759 "char c, cm, *cmd, *cmd1;\n",
3760 "generate a command by percentages\n",
3761 "Numbers may be typed as a prefix to some commands.\n",
3762 "Quit, discarding changes!\n",
3763 "Forced write, if permission originally not valid.\n",
3764 "In general, any ex or ed command (such as substitute or delete).\n",
3765 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3766 "Please get w/ me and I will go over it with you.\n",
3767 "The following is a list of scheduled, committed changes.\n",
3768 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3769 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3770 "Any question about transactions please contact Sterling Huxley.\n",
3771 "I will try to get back to you by Friday, December 31.\n",
3772 "This Change will be implemented on Friday.\n",
3773 "Let me know if you have problems accessing this;\n",
3774 "Sterling Huxley recently added you to the access list.\n",
3775 "Would you like to go to lunch?\n",
3776 "The last command will be automatically run.\n",
3777 "This is too much english for a computer geek.\n",
3779 char *multilines[20] = {
3780 "You should have received a copy of the GNU General Public License\n",
3781 "char c, cm, *cmd, *cmd1;\n",
3782 "generate a command by percentages\n",
3783 "Numbers may be typed as a prefix to some commands.\n",
3784 "Quit, discarding changes!\n",
3785 "Forced write, if permission originally not valid.\n",
3786 "In general, any ex or ed command (such as substitute or delete).\n",
3787 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3788 "Please get w/ me and I will go over it with you.\n",
3789 "The following is a list of scheduled, committed changes.\n",
3790 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3791 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3792 "Any question about transactions please contact Sterling Huxley.\n",
3793 "I will try to get back to you by Friday, December 31.\n",
3794 "This Change will be implemented on Friday.\n",
3795 "Let me know if you have problems accessing this;\n",
3796 "Sterling Huxley recently added you to the access list.\n",
3797 "Would you like to go to lunch?\n",
3798 "The last command will be automatically run.\n",
3799 "This is too much english for a computer geek.\n",
3802 // create a random command to execute
3803 static void crash_dummy()
3805 static int sleeptime; // how long to pause between commands
3806 char c, cm, *cmd, *cmd1;
3807 int i, cnt, thing, rbi, startrbi, percent;
3809 // "dot" movement commands
3810 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3812 // is there already a command running?
3813 if (readed_for_parse > 0)
3817 sleeptime = 0; // how long to pause between commands
3818 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3819 // generate a command by percentages
3820 percent = (int) lrand48() % 100; // get a number from 0-99
3821 if (percent < Mp) { // Movement commands
3822 // available commands
3825 } else if (percent < Np) { // non-movement commands
3826 cmd = "mz<>\'\""; // available commands
3828 } else if (percent < Dp) { // Delete commands
3829 cmd = "dx"; // available commands
3831 } else if (percent < Ip) { // Inset commands
3832 cmd = "iIaAsrJ"; // available commands
3834 } else if (percent < Yp) { // Yank commands
3835 cmd = "yY"; // available commands
3837 } else if (percent < Pp) { // Put commands
3838 cmd = "pP"; // available commands
3841 // We do not know how to handle this command, try again
3845 // randomly pick one of the available cmds from "cmd[]"
3846 i = (int) lrand48() % strlen(cmd);
3848 if (strchr(":\024", cm))
3849 goto cd0; // dont allow colon or ctrl-T commands
3850 readbuffer[rbi++] = cm; // put cmd into input buffer
3852 // now we have the command-
3853 // there are 1, 2, and multi char commands
3854 // find out which and generate the rest of command as necessary
3855 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3856 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3857 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3858 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3860 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3862 readbuffer[rbi++] = c; // add movement to input buffer
3864 if (strchr("iIaAsc", cm)) { // multi-char commands
3866 // change some thing
3867 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3869 readbuffer[rbi++] = c; // add movement to input buffer
3871 thing = (int) lrand48() % 4; // what thing to insert
3872 cnt = (int) lrand48() % 10; // how many to insert
3873 for (i = 0; i < cnt; i++) {
3874 if (thing == 0) { // insert chars
3875 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3876 } else if (thing == 1) { // insert words
3877 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3878 strcat((char *) readbuffer, " ");
3879 sleeptime = 0; // how fast to type
3880 } else if (thing == 2) { // insert lines
3881 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3882 sleeptime = 0; // how fast to type
3883 } else { // insert multi-lines
3884 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3885 sleeptime = 0; // how fast to type
3888 strcat((char *) readbuffer, "\033");
3890 readed_for_parse = strlen(readbuffer);
3894 (void) mysleep(sleeptime); // sleep 1/100 sec
3897 // test to see if there are any errors
3898 static void crash_test()
3900 static time_t oldtim;
3902 char d[2], msg[BUFSIZ];
3906 strcat((char *) msg, "end<text ");
3908 if (end > textend) {
3909 strcat((char *) msg, "end>textend ");
3912 strcat((char *) msg, "dot<text ");
3915 strcat((char *) msg, "dot>end ");
3917 if (screenbegin < text) {
3918 strcat((char *) msg, "screenbegin<text ");
3920 if (screenbegin > end - 1) {
3921 strcat((char *) msg, "screenbegin>end-1 ");
3924 if (strlen(msg) > 0) {
3926 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3927 totalcmds, last_input_char, msg, SOs, SOn);
3929 while (read(0, d, 1) > 0) {
3930 if (d[0] == '\n' || d[0] == '\r')
3935 tim = (time_t) time((time_t *) 0);
3936 if (tim >= (oldtim + 3)) {
3937 sprintf((char *) status_buffer,
3938 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3939 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3944 #endif /* CONFIG_FEATURE_VI_CRASHME */