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 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
29 #include <sys/ioctl.h>
37 #define vi_Version BB_VER " " BB_BT
39 #ifdef CONFIG_LOCALE_SUPPORT
40 #define Isprint(c) isprint((c))
42 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
45 #define MAX_SCR_COLS BUFSIZ
47 // Misc. non-Ascii keys that report an escape sequence
48 #define VI_K_UP 128 // cursor key Up
49 #define VI_K_DOWN 129 // cursor key Down
50 #define VI_K_RIGHT 130 // Cursor Key Right
51 #define VI_K_LEFT 131 // cursor key Left
52 #define VI_K_HOME 132 // Cursor Key Home
53 #define VI_K_END 133 // Cursor Key End
54 #define VI_K_INSERT 134 // Cursor Key Insert
55 #define VI_K_PAGEUP 135 // Cursor Key Page Up
56 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
57 #define VI_K_FUN1 137 // Function Key F1
58 #define VI_K_FUN2 138 // Function Key F2
59 #define VI_K_FUN3 139 // Function Key F3
60 #define VI_K_FUN4 140 // Function Key F4
61 #define VI_K_FUN5 141 // Function Key F5
62 #define VI_K_FUN6 142 // Function Key F6
63 #define VI_K_FUN7 143 // Function Key F7
64 #define VI_K_FUN8 144 // Function Key F8
65 #define VI_K_FUN9 145 // Function Key F9
66 #define VI_K_FUN10 146 // Function Key F10
67 #define VI_K_FUN11 147 // Function Key F11
68 #define VI_K_FUN12 148 // Function Key F12
70 /* vt102 typical ESC sequence */
71 /* terminal standout start/normal ESC sequence */
72 static const char SOs[] = "\033[7m";
73 static const char SOn[] = "\033[0m";
74 /* terminal bell sequence */
75 static const char bell[] = "\007";
76 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
77 static const char Ceol[] = "\033[0K";
78 static const char Ceos [] = "\033[0J";
79 /* Cursor motion arbitrary destination ESC sequence */
80 static const char CMrc[] = "\033[%d;%dH";
81 /* Cursor motion up and down ESC sequence */
82 static const char CMup[] = "\033[A";
83 static const char CMdown[] = "\n";
89 FORWARD = 1, // code depends on "1" for array index
90 BACK = -1, // code depends on "-1" for array index
91 LIMITED = 0, // how much of text[] in char_search
92 FULL = 1, // how much of text[] in char_search
94 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
95 S_TO_WS = 2, // used in skip_thing() for moving "dot"
96 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
97 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
98 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
101 typedef unsigned char Byte;
103 static int vi_setops;
104 #define VI_AUTOINDENT 1
105 #define VI_SHOWMATCH 2
106 #define VI_IGNORECASE 4
107 #define VI_ERR_METHOD 8
108 #define autoindent (vi_setops & VI_AUTOINDENT)
109 #define showmatch (vi_setops & VI_SHOWMATCH )
110 #define ignorecase (vi_setops & VI_IGNORECASE)
111 /* indicate error with beep or flash */
112 #define err_method (vi_setops & VI_ERR_METHOD)
115 static int editing; // >0 while we are editing a file
116 static int cmd_mode; // 0=command 1=insert 2=replace
117 static int file_modified; // buffer contents changed
118 static int last_file_modified = -1;
119 static int fn_start; // index of first cmd line file name
120 static int save_argc; // how many file names on cmd line
121 static int cmdcnt; // repetition count
122 static fd_set rfds; // use select() for small sleeps
123 static struct timeval tv; // use select() for small sleeps
124 static int rows, columns; // the terminal screen is this size
125 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
126 static Byte *status_buffer; // mesages to the user
127 #define STATUS_BUFFER_LEN 200
128 static int have_status_msg; // is default edit status needed?
129 static int last_status_cksum; // hash of current status line
130 static Byte *cfn; // previous, current, and next file name
131 static Byte *text, *end, *textend; // pointers to the user data in memory
132 static Byte *screen; // pointer to the virtual screen buffer
133 static int screensize; // and its size
134 static Byte *screenbegin; // index into text[], of top line on the screen
135 static Byte *dot; // where all the action takes place
137 static struct termios term_orig, term_vi; // remember what the cooked mode was
138 static Byte erase_char; // the users erase character
139 static Byte last_input_char; // last char read from user
140 static Byte last_forward_char; // last char searched for with 'f'
142 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
143 static int last_row; // where the cursor was last moved to
144 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
145 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
146 static jmp_buf restart; // catch_sig()
147 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
148 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
151 #ifdef CONFIG_FEATURE_VI_DOT_CMD
152 static int adding2q; // are we currently adding user input to q
153 static Byte *last_modifying_cmd; // last modifying cmd for "."
154 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
155 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
156 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
157 static Byte *modifying_cmds; // cmds that modify text[]
158 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
159 #ifdef CONFIG_FEATURE_VI_READONLY
160 static int vi_readonly, readonly;
161 #endif /* CONFIG_FEATURE_VI_READONLY */
162 #ifdef CONFIG_FEATURE_VI_YANKMARK
163 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
164 static int YDreg, Ureg; // default delete register and orig line for "U"
165 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
166 static Byte *context_start, *context_end;
167 #endif /* CONFIG_FEATURE_VI_YANKMARK */
168 #ifdef CONFIG_FEATURE_VI_SEARCH
169 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
170 #endif /* CONFIG_FEATURE_VI_SEARCH */
173 static void edit_file(Byte *); // edit one file
174 static void do_cmd(Byte); // execute a command
175 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
176 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
177 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
178 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
179 static Byte *next_line(Byte *); // return pointer to next line B-o-l
180 static Byte *end_screen(void); // get pointer to last char on screen
181 static int count_lines(Byte *, Byte *); // count line from start to stop
182 static Byte *find_line(int); // find begining of line #li
183 static Byte *move_to_col(Byte *, int); // move "p" to column l
184 static int isblnk(Byte); // is the char a blank or tab
185 static void dot_left(void); // move dot left- dont leave line
186 static void dot_right(void); // move dot right- dont leave line
187 static void dot_begin(void); // move dot to B-o-l
188 static void dot_end(void); // move dot to E-o-l
189 static void dot_next(void); // move dot to next line B-o-l
190 static void dot_prev(void); // move dot to prev line B-o-l
191 static void dot_scroll(int, int); // move the screen up or down
192 static void dot_skip_over_ws(void); // move dot pat WS
193 static void dot_delete(void); // delete the char at 'dot'
194 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
195 static Byte *new_screen(int, int); // malloc virtual screen memory
196 static Byte *new_text(int); // malloc memory for text[] buffer
197 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
198 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
199 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
200 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
201 static Byte *skip_thing(Byte *, int, int, int); // skip some object
202 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
203 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
204 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
205 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
206 static void show_help(void); // display some help info
207 static void rawmode(void); // set "raw" mode on tty
208 static void cookmode(void); // return to "cooked" mode on tty
209 static int mysleep(int); // sleep for 'h' 1/100 seconds
210 static Byte readit(void); // read (maybe cursor) key from stdin
211 static Byte get_one_char(void); // read 1 char from stdin
212 static int file_size(const Byte *); // what is the byte size of "fn"
213 static int file_insert(Byte *, Byte *, int);
214 static int file_write(Byte *, Byte *, Byte *);
215 static void place_cursor(int, int, int);
216 static void screen_erase(void);
217 static void clear_to_eol(void);
218 static void clear_to_eos(void);
219 static void standout_start(void); // send "start reverse video" sequence
220 static void standout_end(void); // send "end reverse video" sequence
221 static void flash(int); // flash the terminal screen
222 static void show_status_line(void); // put a message on the bottom line
223 static void psb(const char *, ...); // Print Status Buf
224 static void psbs(const char *, ...); // Print Status Buf in standout mode
225 static void ni(Byte *); // display messages
226 static int format_edit_status(void); // format file status on status line
227 static void redraw(int); // force a full screen refresh
228 static void format_line(Byte*, Byte*, int);
229 static void refresh(int); // update the terminal from screen[]
231 static void Indicate_Error(void); // use flash or beep to indicate error
232 #define indicate_error(c) Indicate_Error()
233 static void Hit_Return(void);
235 #ifdef CONFIG_FEATURE_VI_SEARCH
236 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
237 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
238 #endif /* CONFIG_FEATURE_VI_SEARCH */
239 #ifdef CONFIG_FEATURE_VI_COLON
240 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
241 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
242 static void colon(Byte *); // execute the "colon" mode cmds
243 #endif /* CONFIG_FEATURE_VI_COLON */
244 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
245 static void winch_sig(int); // catch window size changes
246 static void suspend_sig(int); // catch ctrl-Z
247 static void catch_sig(int); // catch ctrl-C and alarm time-outs
248 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
249 #ifdef CONFIG_FEATURE_VI_DOT_CMD
250 static void start_new_cmd_q(Byte); // new queue for command
251 static void end_cmd_q(void); // stop saving input chars
252 #else /* CONFIG_FEATURE_VI_DOT_CMD */
254 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
255 #ifdef CONFIG_FEATURE_VI_SETOPTS
256 static void showmatching(Byte *); // show the matching pair () [] {}
257 #endif /* CONFIG_FEATURE_VI_SETOPTS */
258 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
259 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
260 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
261 #ifdef CONFIG_FEATURE_VI_YANKMARK
262 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
263 static Byte what_reg(void); // what is letter of current YDreg
264 static void check_context(Byte); // remember context for '' command
265 #endif /* CONFIG_FEATURE_VI_YANKMARK */
266 #ifdef CONFIG_FEATURE_VI_CRASHME
267 static void crash_dummy();
268 static void crash_test();
269 static int crashme = 0;
270 #endif /* CONFIG_FEATURE_VI_CRASHME */
273 static void write1(const char *out)
278 int vi_main(int argc, char **argv)
281 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
283 #ifdef CONFIG_FEATURE_VI_YANKMARK
285 #endif /* CONFIG_FEATURE_VI_YANKMARK */
286 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
289 #ifdef CONFIG_FEATURE_VI_CRASHME
290 (void) srand((long) my_pid);
291 #endif /* CONFIG_FEATURE_VI_CRASHME */
293 status_buffer = (Byte *)STATUS_BUFFER;
294 last_status_cksum = 0;
296 #ifdef CONFIG_FEATURE_VI_READONLY
297 vi_readonly = readonly = FALSE;
298 if (strncmp(argv[0], "view", 4) == 0) {
302 #endif /* CONFIG_FEATURE_VI_READONLY */
303 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
304 #ifdef CONFIG_FEATURE_VI_YANKMARK
305 for (i = 0; i < 28; i++) {
307 } // init the yank regs
308 #endif /* CONFIG_FEATURE_VI_YANKMARK */
309 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
310 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
311 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
313 // 1- process $HOME/.exrc file
314 // 2- process EXINIT variable from environment
315 // 3- process command line args
316 while ((c = getopt(argc, argv, "hCR")) != -1) {
318 #ifdef CONFIG_FEATURE_VI_CRASHME
322 #endif /* CONFIG_FEATURE_VI_CRASHME */
323 #ifdef CONFIG_FEATURE_VI_READONLY
324 case 'R': // Read-only flag
328 #endif /* CONFIG_FEATURE_VI_READONLY */
329 //case 'r': // recover flag- ignore- we don't use tmp file
330 //case 'x': // encryption flag- ignore
331 //case 'c': // execute command first
332 //case 'h': // help -- just use default
339 // The argv array can be used by the ":next" and ":rewind" commands
341 fn_start = optind; // remember first file name for :next and :rew
344 //----- This is the main file handling loop --------------
345 if (optind >= argc) {
346 editing = 1; // 0= exit, 1= one file, 2= multiple files
349 for (; optind < argc; optind++) {
350 editing = 1; // 0=exit, 1=one file, 2+ =many files
352 cfn = (Byte *) bb_xstrdup(argv[optind]);
356 //-----------------------------------------------------------
361 static void edit_file(Byte * fn)
366 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
368 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
369 #ifdef CONFIG_FEATURE_VI_YANKMARK
370 static Byte *cur_line;
371 #endif /* CONFIG_FEATURE_VI_YANKMARK */
377 if (ENABLE_FEATURE_VI_WIN_RESIZE)
378 get_terminal_width_height(0, &columns, &rows);
379 new_screen(rows, columns); // get memory for virtual screen
381 cnt = file_size(fn); // file size
382 size = 2 * cnt; // 200% of file size
383 new_text(size); // get a text[] buffer
384 screenbegin = dot = end = text;
386 ch= file_insert(fn, text, cnt);
389 (void) char_insert(text, '\n'); // start empty buf with dummy line
392 last_file_modified = -1;
393 #ifdef CONFIG_FEATURE_VI_YANKMARK
394 YDreg = 26; // default Yank/Delete reg
395 Ureg = 27; // hold orig line for "U" cmd
396 for (cnt = 0; cnt < 28; cnt++) {
399 mark[26] = mark[27] = text; // init "previous context"
400 #endif /* CONFIG_FEATURE_VI_YANKMARK */
402 last_forward_char = last_input_char = '\0';
406 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
408 signal(SIGWINCH, winch_sig);
409 signal(SIGTSTP, suspend_sig);
410 sig = setjmp(restart);
412 screenbegin = dot = text;
414 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
417 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
420 offset = 0; // no horizontal offset
422 #ifdef CONFIG_FEATURE_VI_DOT_CMD
423 free(last_modifying_cmd);
425 ioq = ioq_start = last_modifying_cmd = 0;
427 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
428 redraw(FALSE); // dont force every col re-draw
431 //------This is the main Vi cmd handling loop -----------------------
432 while (editing > 0) {
433 #ifdef CONFIG_FEATURE_VI_CRASHME
435 if ((end - text) > 1) {
436 crash_dummy(); // generate a random command
440 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
444 #endif /* CONFIG_FEATURE_VI_CRASHME */
445 last_input_char = c = get_one_char(); // get a cmd from user
446 #ifdef CONFIG_FEATURE_VI_YANKMARK
447 // save a copy of the current line- for the 'U" command
448 if (begin_line(dot) != cur_line) {
449 cur_line = begin_line(dot);
450 text_yank(begin_line(dot), end_line(dot), Ureg);
452 #endif /* CONFIG_FEATURE_VI_YANKMARK */
453 #ifdef CONFIG_FEATURE_VI_DOT_CMD
454 // These are commands that change text[].
455 // Remember the input for the "." command
456 if (!adding2q && ioq_start == 0
457 && strchr((char *) modifying_cmds, c) != NULL) {
460 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
461 do_cmd(c); // execute the user command
463 // poll to see if there is input already waiting. if we are
464 // not able to display output fast enough to keep up, skip
465 // the display update until we catch up with input.
466 if (mysleep(0) == 0) {
467 // no input pending- so update output
471 #ifdef CONFIG_FEATURE_VI_CRASHME
473 crash_test(); // test editor variables
474 #endif /* CONFIG_FEATURE_VI_CRASHME */
476 //-------------------------------------------------------------------
478 place_cursor(rows, 0, FALSE); // go to bottom of screen
479 clear_to_eol(); // Erase to end of line
483 //----- The Colon commands -------------------------------------
484 #ifdef CONFIG_FEATURE_VI_COLON
485 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
490 #ifdef CONFIG_FEATURE_VI_YANKMARK
492 #endif /* CONFIG_FEATURE_VI_YANKMARK */
493 #ifdef CONFIG_FEATURE_VI_SEARCH
494 Byte *pat, buf[BUFSIZ];
495 #endif /* CONFIG_FEATURE_VI_SEARCH */
497 *addr = -1; // assume no addr
498 if (*p == '.') { // the current line
501 *addr = count_lines(text, q);
502 #ifdef CONFIG_FEATURE_VI_YANKMARK
503 } else if (*p == '\'') { // is this a mark addr
507 if (c >= 'a' && c <= 'z') {
511 if (q != NULL) { // is mark valid
512 *addr = count_lines(text, q); // count lines
515 #endif /* CONFIG_FEATURE_VI_YANKMARK */
516 #ifdef CONFIG_FEATURE_VI_SEARCH
517 } else if (*p == '/') { // a search pattern
525 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
528 q = char_search(dot, pat, FORWARD, FULL);
530 *addr = count_lines(text, q);
533 #endif /* CONFIG_FEATURE_VI_SEARCH */
534 } else if (*p == '$') { // the last line in file
536 q = begin_line(end - 1);
537 *addr = count_lines(text, q);
538 } else if (isdigit(*p)) { // specific line number
539 sscanf((char *) p, "%d%n", addr, &st);
541 } else { // I don't reconise this
542 // unrecognised address- assume -1
548 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
550 //----- get the address' i.e., 1,3 'a,'b -----
551 // get FIRST addr, if present
553 p++; // skip over leading spaces
554 if (*p == '%') { // alias for 1,$
557 *e = count_lines(text, end-1);
560 p = get_one_address(p, b);
563 if (*p == ',') { // is there a address separator
567 // get SECOND addr, if present
568 p = get_one_address(p, e);
572 p++; // skip over trailing spaces
576 #ifdef CONFIG_FEATURE_VI_SETOPTS
577 static void setops(const Byte *args, const char *opname, int flg_no,
578 const char *short_opname, int opt)
580 const char *a = (char *) args + flg_no;
581 int l = strlen(opname) - 1; /* opname have + ' ' */
583 if (strncasecmp(a, opname, l) == 0 ||
584 strncasecmp(a, short_opname, 2) == 0) {
593 static void colon(Byte * buf)
595 Byte c, *orig_buf, *buf1, *q, *r;
596 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
597 int i, l, li, ch, st, b, e;
598 int useforce = FALSE, forced = FALSE;
601 // :3154 // if (-e line 3154) goto it else stay put
602 // :4,33w! foo // write a portion of buffer to file "foo"
603 // :w // write all of buffer to current file
605 // :q! // quit- dont care about modified file
606 // :'a,'z!sort -u // filter block through sort
607 // :'f // goto mark "f"
608 // :'fl // list literal the mark "f" line
609 // :.r bar // read file "bar" into buffer before dot
610 // :/123/,/abc/d // delete lines from "123" line to "abc" line
611 // :/xyz/ // goto the "xyz" line
612 // :s/find/replace/ // substitute pattern "find" with "replace"
613 // :!<cmd> // run <cmd> then return
616 if (strlen((char *) buf) <= 0)
619 buf++; // move past the ':'
621 li = st = ch = i = 0;
623 q = text; // assume 1,$ for the range
625 li = count_lines(text, end - 1);
626 fn = cfn; // default to current file
627 memset(cmd, '\0', BUFSIZ); // clear cmd[]
628 memset(args, '\0', BUFSIZ); // clear args[]
630 // look for optional address(es) :. :1 :1,9 :'q,'a :%
631 buf = get_address(buf, &b, &e);
633 // remember orig command line
636 // get the COMMAND into cmd[]
638 while (*buf != '\0') {
646 strcpy((char *) args, (char *) buf);
647 buf1 = (Byte*)last_char_is((char *)cmd, '!');
650 *buf1 = '\0'; // get rid of !
653 // if there is only one addr, then the addr
654 // is the line number of the single line the
655 // user wants. So, reset the end
656 // pointer to point at end of the "b" line
657 q = find_line(b); // what line is #b
662 // we were given two addrs. change the
663 // end pointer to the addr given by user.
664 r = find_line(e); // what line is #e
668 // ------------ now look for the command ------------
669 i = strlen((char *) cmd);
670 if (i == 0) { // :123CR goto line #123
672 dot = find_line(b); // what line is #b
675 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
676 // :!ls run the <cmd>
677 (void) alarm(0); // wait for input- no alarms
678 place_cursor(rows - 1, 0, FALSE); // go to Status line
679 clear_to_eol(); // clear the line
681 system((char*)(orig_buf+1)); // run the cmd
683 Hit_Return(); // let user see results
684 (void) alarm(3); // done waiting for input
685 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
686 if (b < 0) { // no addr given- use defaults
687 b = e = count_lines(text, dot);
690 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
691 if (b < 0) { // no addr given- use defaults
692 q = begin_line(dot); // assume .,. for the range
695 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
697 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
700 // don't edit, if the current file has been modified
701 if (file_modified && ! useforce) {
702 psbs("No write since last change (:edit! overrides)");
705 if (strlen((char*)args) > 0) {
706 // the user supplied a file name
708 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
709 // no user supplied name- use the current filename
713 // no user file name, no current name- punt
714 psbs("No current filename");
718 // see if file exists- if not, its just a new file request
719 if ((sr=stat((char*)fn, &st_buf)) < 0) {
720 // This is just a request for a new file creation.
721 // The file_insert below will fail but we get
722 // an empty buffer with a file name. Then the "write"
723 // command can do the create.
725 if ((st_buf.st_mode & (S_IFREG)) == 0) {
726 // This is not a regular file
727 psbs("\"%s\" is not a regular file", fn);
730 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
731 // dont have any read permissions
732 psbs("\"%s\" is not readable", fn);
737 // There is a read-able regular file
738 // make this the current file
739 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
740 free(cfn); // free the old name
741 cfn = q; // remember new cfn
744 // delete all the contents of text[]
745 new_text(2 * file_size(fn));
746 screenbegin = dot = end = text;
749 ch = file_insert(fn, text, file_size(fn));
752 // start empty buf with dummy line
753 (void) char_insert(text, '\n');
757 last_file_modified = -1;
758 #ifdef CONFIG_FEATURE_VI_YANKMARK
759 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
760 free(reg[Ureg]); // free orig line reg- for 'U'
763 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
764 free(reg[YDreg]); // free default yank/delete register
767 for (li = 0; li < 28; li++) {
770 #endif /* CONFIG_FEATURE_VI_YANKMARK */
771 // how many lines in text[]?
772 li = count_lines(text, end - 1);
774 #ifdef CONFIG_FEATURE_VI_READONLY
776 #endif /* CONFIG_FEATURE_VI_READONLY */
778 (sr < 0 ? " [New file]" : ""),
779 #ifdef CONFIG_FEATURE_VI_READONLY
780 ((vi_readonly || readonly) ? " [Read only]" : ""),
781 #endif /* CONFIG_FEATURE_VI_READONLY */
783 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
784 if (b != -1 || e != -1) {
785 ni((Byte *) "No address allowed on this command");
788 if (strlen((char *) args) > 0) {
789 // user wants a new filename
791 cfn = (Byte *) bb_xstrdup((char *) args);
793 // user wants file status info
794 last_status_cksum = 0; // force status update
796 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
797 // print out values of all features
798 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
799 clear_to_eol(); // clear the line
804 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
805 if (b < 0) { // no addr given- use defaults
806 q = begin_line(dot); // assume .,. for the range
809 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
810 clear_to_eol(); // clear the line
812 for (; q <= r; q++) {
816 c_is_no_print = c > 127 && !Isprint(c);
823 } else if (c < ' ' || c == 127) {
834 #ifdef CONFIG_FEATURE_VI_SET
836 #endif /* CONFIG_FEATURE_VI_SET */
838 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
839 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
841 // force end of argv list
848 // don't exit if the file been modified
850 psbs("No write since last change (:%s! overrides)",
851 (*cmd == 'q' ? "quit" : "next"));
854 // are there other file to edit
855 if (*cmd == 'q' && optind < save_argc - 1) {
856 psbs("%d more file to edit", (save_argc - optind - 1));
859 if (*cmd == 'n' && optind >= save_argc - 1) {
860 psbs("No more files to edit");
864 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
866 if (strlen((char *) fn) <= 0) {
867 psbs("No filename given");
870 if (b < 0) { // no addr given- use defaults
871 q = begin_line(dot); // assume "dot"
873 // read after current line- unless user said ":0r foo"
876 #ifdef CONFIG_FEATURE_VI_READONLY
877 l= readonly; // remember current files' status
879 ch = file_insert(fn, q, file_size(fn));
880 #ifdef CONFIG_FEATURE_VI_READONLY
884 goto vc1; // nothing was inserted
885 // how many lines in text[]?
886 li = count_lines(q, q + ch - 1);
888 #ifdef CONFIG_FEATURE_VI_READONLY
890 #endif /* CONFIG_FEATURE_VI_READONLY */
892 #ifdef CONFIG_FEATURE_VI_READONLY
893 ((vi_readonly || readonly) ? " [Read only]" : ""),
894 #endif /* CONFIG_FEATURE_VI_READONLY */
897 // if the insert is before "dot" then we need to update
902 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
903 if (file_modified && ! useforce) {
904 psbs("No write since last change (:rewind! overrides)");
906 // reset the filenames to edit
907 optind = fn_start - 1;
910 #ifdef CONFIG_FEATURE_VI_SET
911 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
912 i = 0; // offset into args
913 if (strlen((char *) args) == 0) {
914 // print out values of all options
915 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
916 clear_to_eol(); // clear the line
917 printf("----------------------------------------\r\n");
918 #ifdef CONFIG_FEATURE_VI_SETOPTS
921 printf("autoindent ");
927 printf("ignorecase ");
930 printf("showmatch ");
931 printf("tabstop=%d ", tabstop);
932 #endif /* CONFIG_FEATURE_VI_SETOPTS */
936 if (strncasecmp((char *) args, "no", 2) == 0)
937 i = 2; // ":set noautoindent"
938 #ifdef CONFIG_FEATURE_VI_SETOPTS
939 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
940 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
941 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
942 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
943 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
944 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
945 if (ch > 0 && ch < columns - 1)
948 #endif /* CONFIG_FEATURE_VI_SETOPTS */
949 #endif /* CONFIG_FEATURE_VI_SET */
950 #ifdef CONFIG_FEATURE_VI_SEARCH
951 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
955 // F points to the "find" pattern
956 // R points to the "replace" pattern
957 // replace the cmd line delimiters "/" with NULLs
958 gflag = 0; // global replace flag
959 c = orig_buf[1]; // what is the delimiter
960 F = orig_buf + 2; // start of "find"
961 R = (Byte *) strchr((char *) F, c); // middle delimiter
962 if (!R) goto colon_s_fail;
963 *R++ = '\0'; // terminate "find"
964 buf1 = (Byte *) strchr((char *) R, c);
965 if (!buf1) goto colon_s_fail;
966 *buf1++ = '\0'; // terminate "replace"
967 if (*buf1 == 'g') { // :s/foo/bar/g
969 gflag++; // turn on gflag
972 if (b < 0) { // maybe :s/foo/bar/
973 q = begin_line(dot); // start with cur line
974 b = count_lines(text, q); // cur line number
977 e = b; // maybe :.s/foo/bar/
978 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
979 ls = q; // orig line start
981 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
983 // we found the "find" pattern- delete it
984 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
985 // inset the "replace" patern
986 (void) string_insert(buf1, R); // insert the string
987 // check for "global" :s/foo/bar/g
989 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
990 q = buf1 + strlen((char *) R);
991 goto vc4; // don't let q move past cur line
997 #endif /* CONFIG_FEATURE_VI_SEARCH */
998 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
999 psb("%s", vi_Version);
1000 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
1001 || strncasecmp((char *) cmd, "wq", i) == 0
1002 || strncasecmp((char *) cmd, "wn", i) == 0
1003 || strncasecmp((char *) cmd, "x", i) == 0) {
1004 // is there a file name to write to?
1005 if (strlen((char *) args) > 0) {
1008 #ifdef CONFIG_FEATURE_VI_READONLY
1009 if ((vi_readonly || readonly) && ! useforce) {
1010 psbs("\"%s\" File is read only", fn);
1013 #endif /* CONFIG_FEATURE_VI_READONLY */
1014 // how many lines in text[]?
1015 li = count_lines(q, r);
1017 // see if file exists- if not, its just a new file request
1019 // if "fn" is not write-able, chmod u+w
1020 // sprintf(syscmd, "chmod u+w %s", fn);
1024 l = file_write(fn, q, r);
1025 if (useforce && forced) {
1027 // sprintf(syscmd, "chmod u-w %s", fn);
1033 psbs("Write error: %s", strerror(errno));
1035 psb("\"%s\" %dL, %dC", fn, li, l);
1036 if (q == text && r == end - 1 && l == ch) {
1038 last_file_modified = -1;
1040 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1041 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1046 #ifdef CONFIG_FEATURE_VI_READONLY
1048 #endif /* CONFIG_FEATURE_VI_READONLY */
1049 #ifdef CONFIG_FEATURE_VI_YANKMARK
1050 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1051 if (b < 0) { // no addr given- use defaults
1052 q = begin_line(dot); // assume .,. for the range
1055 text_yank(q, r, YDreg);
1056 li = count_lines(q, r);
1057 psb("Yank %d lines (%d chars) into [%c]",
1058 li, strlen((char *) reg[YDreg]), what_reg());
1059 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1065 dot = bound_dot(dot); // make sure "dot" is valid
1067 #ifdef CONFIG_FEATURE_VI_SEARCH
1069 psb(":s expression missing delimiters");
1073 #endif /* CONFIG_FEATURE_VI_COLON */
1075 static void Hit_Return(void)
1079 standout_start(); // start reverse video
1080 write1("[Hit return to continue]");
1081 standout_end(); // end reverse video
1082 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1084 redraw(TRUE); // force redraw all
1087 //----- Synchronize the cursor to Dot --------------------------
1088 static void sync_cursor(Byte * d, int *row, int *col)
1090 Byte *beg_cur; // begin and end of "d" line
1091 Byte *beg_scr, *end_scr; // begin and end of screen
1095 beg_cur = begin_line(d); // first char of cur line
1097 beg_scr = end_scr = screenbegin; // first char of screen
1098 end_scr = end_screen(); // last char of screen
1100 if (beg_cur < screenbegin) {
1101 // "d" is before top line on screen
1102 // how many lines do we have to move
1103 cnt = count_lines(beg_cur, screenbegin);
1105 screenbegin = beg_cur;
1106 if (cnt > (rows - 1) / 2) {
1107 // we moved too many lines. put "dot" in middle of screen
1108 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1109 screenbegin = prev_line(screenbegin);
1112 } else if (beg_cur > end_scr) {
1113 // "d" is after bottom line on screen
1114 // how many lines do we have to move
1115 cnt = count_lines(end_scr, beg_cur);
1116 if (cnt > (rows - 1) / 2)
1117 goto sc1; // too many lines
1118 for (ro = 0; ro < cnt - 1; ro++) {
1119 // move screen begin the same amount
1120 screenbegin = next_line(screenbegin);
1121 // now, move the end of screen
1122 end_scr = next_line(end_scr);
1123 end_scr = end_line(end_scr);
1126 // "d" is on screen- find out which row
1128 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1134 // find out what col "d" is on
1136 do { // drive "co" to correct column
1137 if (*tp == '\n' || *tp == '\0')
1141 co += ((tabstop - 1) - (co % tabstop));
1142 } else if (*tp < ' ' || *tp == 127) {
1143 co++; // display as ^X, use 2 columns
1145 } while (tp++ < d && ++co);
1147 // "co" is the column where "dot" is.
1148 // The screen has "columns" columns.
1149 // The currently displayed columns are 0+offset -- columns+ofset
1150 // |-------------------------------------------------------------|
1152 // offset | |------- columns ----------------|
1154 // If "co" is already in this range then we do not have to adjust offset
1155 // but, we do have to subtract the "offset" bias from "co".
1156 // If "co" is outside this range then we have to change "offset".
1157 // If the first char of a line is a tab the cursor will try to stay
1158 // in column 7, but we have to set offset to 0.
1160 if (co < 0 + offset) {
1163 if (co >= columns + offset) {
1164 offset = co - columns + 1;
1166 // if the first char of the line is a tab, and "dot" is sitting on it
1167 // force offset to 0.
1168 if (d == beg_cur && *d == '\t') {
1177 //----- Text Movement Routines ---------------------------------
1178 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1180 while (p > text && p[-1] != '\n')
1181 p--; // go to cur line B-o-l
1185 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1187 while (p < end - 1 && *p != '\n')
1188 p++; // go to cur line E-o-l
1192 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1194 while (p < end - 1 && *p != '\n')
1195 p++; // go to cur line E-o-l
1196 // Try to stay off of the Newline
1197 if (*p == '\n' && (p - begin_line(p)) > 0)
1202 static Byte *prev_line(Byte * p) // return pointer first char prev line
1204 p = begin_line(p); // goto begining of cur line
1205 if (p[-1] == '\n' && p > text)
1206 p--; // step to prev line
1207 p = begin_line(p); // goto begining of prev line
1211 static Byte *next_line(Byte * p) // return pointer first char next line
1214 if (*p == '\n' && p < end - 1)
1215 p++; // step to next line
1219 //----- Text Information Routines ------------------------------
1220 static Byte *end_screen(void)
1225 // find new bottom line
1227 for (cnt = 0; cnt < rows - 2; cnt++)
1233 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1238 if (stop < start) { // start and stop are backwards- reverse them
1244 stop = end_line(stop); // get to end of this line
1245 for (q = start; q <= stop && q <= end - 1; q++) {
1252 static Byte *find_line(int li) // find begining of line #li
1256 for (q = text; li > 1; li--) {
1262 //----- Dot Movement Routines ----------------------------------
1263 static void dot_left(void)
1265 if (dot > text && dot[-1] != '\n')
1269 static void dot_right(void)
1271 if (dot < end - 1 && *dot != '\n')
1275 static void dot_begin(void)
1277 dot = begin_line(dot); // return pointer to first char cur line
1280 static void dot_end(void)
1282 dot = end_line(dot); // return pointer to last char cur line
1285 static Byte *move_to_col(Byte * p, int l)
1292 if (*p == '\n' || *p == '\0')
1296 co += ((tabstop - 1) - (co % tabstop));
1297 } else if (*p < ' ' || *p == 127) {
1298 co++; // display as ^X, use 2 columns
1300 } while (++co <= l && p++ < end);
1304 static void dot_next(void)
1306 dot = next_line(dot);
1309 static void dot_prev(void)
1311 dot = prev_line(dot);
1314 static void dot_scroll(int cnt, int dir)
1318 for (; cnt > 0; cnt--) {
1321 // ctrl-Y scroll up one line
1322 screenbegin = prev_line(screenbegin);
1325 // ctrl-E scroll down one line
1326 screenbegin = next_line(screenbegin);
1329 // make sure "dot" stays on the screen so we dont scroll off
1330 if (dot < screenbegin)
1332 q = end_screen(); // find new bottom line
1334 dot = begin_line(q); // is dot is below bottom line?
1338 static void dot_skip_over_ws(void)
1341 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1345 static void dot_delete(void) // delete the char at 'dot'
1347 (void) text_hole_delete(dot, dot);
1350 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1352 if (p >= end && end > text) {
1354 indicate_error('1');
1358 indicate_error('2');
1363 //----- Helper Utility Routines --------------------------------
1365 //----------------------------------------------------------------
1366 //----- Char Routines --------------------------------------------
1367 /* Chars that are part of a word-
1368 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1369 * Chars that are Not part of a word (stoppers)
1370 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1371 * Chars that are WhiteSpace
1372 * TAB NEWLINE VT FF RETURN SPACE
1373 * DO NOT COUNT NEWLINE AS WHITESPACE
1376 static Byte *new_screen(int ro, int co)
1381 screensize = ro * co + 8;
1382 screen = (Byte *) xmalloc(screensize);
1383 // initialize the new screen. assume this will be a empty file.
1385 // non-existent text[] lines start with a tilde (~).
1386 for (li = 1; li < ro - 1; li++) {
1387 screen[(li * co) + 0] = '~';
1392 static Byte *new_text(int size)
1395 size = 10240; // have a minimum size for new files
1397 text = (Byte *) xmalloc(size + 8);
1398 memset(text, '\0', size); // clear new text[]
1399 //text += 4; // leave some room for "oops"
1400 textend = text + size - 1;
1401 //textend -= 4; // leave some root for "oops"
1405 #ifdef CONFIG_FEATURE_VI_SEARCH
1406 static int mycmp(Byte * s1, Byte * s2, int len)
1410 i = strncmp((char *) s1, (char *) s2, len);
1411 #ifdef CONFIG_FEATURE_VI_SETOPTS
1413 i = strncasecmp((char *) s1, (char *) s2, len);
1415 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1419 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1421 #ifndef REGEX_SEARCH
1425 len = strlen((char *) pat);
1426 if (dir == FORWARD) {
1427 stop = end - 1; // assume range is p - end-1
1428 if (range == LIMITED)
1429 stop = next_line(p); // range is to next line
1430 for (start = p; start < stop; start++) {
1431 if (mycmp(start, pat, len) == 0) {
1435 } else if (dir == BACK) {
1436 stop = text; // assume range is text - p
1437 if (range == LIMITED)
1438 stop = prev_line(p); // range is to prev line
1439 for (start = p - len; start >= stop; start--) {
1440 if (mycmp(start, pat, len) == 0) {
1445 // pattern not found
1447 #else /*REGEX_SEARCH */
1449 struct re_pattern_buffer preg;
1453 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1459 // assume a LIMITED forward search
1467 // count the number of chars to search over, forward or backward
1471 // RANGE could be negative if we are searching backwards
1474 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1476 // The pattern was not compiled
1477 psbs("bad search pattern: \"%s\": %s", pat, q);
1478 i = 0; // return p if pattern not compiled
1488 // search for the compiled pattern, preg, in p[]
1489 // range < 0- search backward
1490 // range > 0- search forward
1492 // re_search() < 0 not found or error
1493 // re_search() > 0 index of found pattern
1494 // struct pattern char int int int struct reg
1495 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1496 i = re_search(&preg, q, size, 0, range, 0);
1499 i = 0; // return NULL if pattern not found
1502 if (dir == FORWARD) {
1508 #endif /*REGEX_SEARCH */
1510 #endif /* CONFIG_FEATURE_VI_SEARCH */
1512 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1514 if (c == 22) { // Is this an ctrl-V?
1515 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1516 p--; // backup onto ^
1517 refresh(FALSE); // show the ^
1521 file_modified++; // has the file been modified
1522 } else if (c == 27) { // Is this an ESC?
1525 end_cmd_q(); // stop adding to q
1526 last_status_cksum = 0; // force status update
1527 if ((p[-1] != '\n') && (dot>text)) {
1530 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1532 if ((p[-1] != '\n') && (dot>text)) {
1534 p = text_hole_delete(p, p); // shrink buffer 1 char
1537 // insert a char into text[]
1538 Byte *sp; // "save p"
1541 c = '\n'; // translate \r to \n
1542 sp = p; // remember addr of insert
1543 p = stupid_insert(p, c); // insert the char
1544 #ifdef CONFIG_FEATURE_VI_SETOPTS
1545 if (showmatch && strchr(")]}", *sp) != NULL) {
1548 if (autoindent && c == '\n') { // auto indent the new line
1551 q = prev_line(p); // use prev line as templet
1552 for (; isblnk(*q); q++) {
1553 p = stupid_insert(p, *q); // insert the char
1556 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1561 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1563 p = text_hole_make(p, 1);
1566 file_modified++; // has the file been modified
1572 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1574 Byte *save_dot, *p, *q;
1580 if (strchr("cdy><", c)) {
1581 // these cmds operate on whole lines
1582 p = q = begin_line(p);
1583 for (cnt = 1; cnt < cmdcnt; cnt++) {
1587 } else if (strchr("^%$0bBeEft", c)) {
1588 // These cmds operate on char positions
1589 do_cmd(c); // execute movement cmd
1591 } else if (strchr("wW", c)) {
1592 do_cmd(c); // execute movement cmd
1593 // if we are at the next word's first char
1594 // step back one char
1595 // but check the possibilities when it is true
1596 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1597 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1598 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1599 dot--; // move back off of next word
1600 if (dot > text && *dot == '\n')
1601 dot--; // stay off NL
1603 } else if (strchr("H-k{", c)) {
1604 // these operate on multi-lines backwards
1605 q = end_line(dot); // find NL
1606 do_cmd(c); // execute movement cmd
1609 } else if (strchr("L+j}\r\n", c)) {
1610 // these operate on multi-lines forwards
1611 p = begin_line(dot);
1612 do_cmd(c); // execute movement cmd
1613 dot_end(); // find NL
1616 c = 27; // error- return an ESC char
1629 static int st_test(Byte * p, int type, int dir, Byte * tested)
1639 if (type == S_BEFORE_WS) {
1641 test = ((!isspace(c)) || c == '\n');
1643 if (type == S_TO_WS) {
1645 test = ((!isspace(c)) || c == '\n');
1647 if (type == S_OVER_WS) {
1649 test = ((isspace(c)));
1651 if (type == S_END_PUNCT) {
1653 test = ((ispunct(c)));
1655 if (type == S_END_ALNUM) {
1657 test = ((isalnum(c)) || c == '_');
1663 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1667 while (st_test(p, type, dir, &c)) {
1668 // make sure we limit search to correct number of lines
1669 if (c == '\n' && --linecnt < 1)
1671 if (dir >= 0 && p >= end - 1)
1673 if (dir < 0 && p <= text)
1675 p += dir; // move to next char
1680 // find matching char of pair () [] {}
1681 static Byte *find_pair(Byte * p, Byte c)
1688 dir = 1; // assume forward
1712 for (q = p + dir; text <= q && q < end; q += dir) {
1713 // look for match, count levels of pairs (( ))
1715 level++; // increase pair levels
1717 level--; // reduce pair level
1719 break; // found matching pair
1722 q = NULL; // indicate no match
1726 #ifdef CONFIG_FEATURE_VI_SETOPTS
1727 // show the matching char of a pair, () [] {}
1728 static void showmatching(Byte * p)
1732 // we found half of a pair
1733 q = find_pair(p, *p); // get loc of matching char
1735 indicate_error('3'); // no matching char
1737 // "q" now points to matching pair
1738 save_dot = dot; // remember where we are
1739 dot = q; // go to new loc
1740 refresh(FALSE); // let the user see it
1741 (void) mysleep(40); // give user some time
1742 dot = save_dot; // go back to old loc
1746 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1748 // open a hole in text[]
1749 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1758 cnt = end - src; // the rest of buffer
1759 if (memmove(dest, src, cnt) != dest) {
1760 psbs("can't create room for new characters");
1762 memset(p, ' ', size); // clear new hole
1763 end = end + size; // adjust the new END
1764 file_modified++; // has the file been modified
1769 // close a hole in text[]
1770 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1775 // move forwards, from beginning
1779 if (q < p) { // they are backward- swap them
1783 hole_size = q - p + 1;
1785 if (src < text || src > end)
1787 if (dest < text || dest >= end)
1790 goto thd_atend; // just delete the end of the buffer
1791 if (memmove(dest, src, cnt) != dest) {
1792 psbs("can't delete the character");
1795 end = end - hole_size; // adjust the new END
1797 dest = end - 1; // make sure dest in below end-1
1799 dest = end = text; // keep pointers valid
1800 file_modified++; // has the file been modified
1805 // copy text into register, then delete text.
1806 // if dist <= 0, do not include, or go past, a NewLine
1808 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1812 // make sure start <= stop
1814 // they are backwards, reverse them
1820 // we can not cross NL boundaries
1824 // dont go past a NewLine
1825 for (; p + 1 <= stop; p++) {
1827 stop = p; // "stop" just before NewLine
1833 #ifdef CONFIG_FEATURE_VI_YANKMARK
1834 text_yank(start, stop, YDreg);
1835 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1836 if (yf == YANKDEL) {
1837 p = text_hole_delete(start, stop);
1842 static void show_help(void)
1844 puts("These features are available:"
1845 #ifdef CONFIG_FEATURE_VI_SEARCH
1846 "\n\tPattern searches with / and ?"
1847 #endif /* CONFIG_FEATURE_VI_SEARCH */
1848 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1849 "\n\tLast command repeat with \'.\'"
1850 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1851 #ifdef CONFIG_FEATURE_VI_YANKMARK
1852 "\n\tLine marking with 'x"
1853 "\n\tNamed buffers with \"x"
1854 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1855 #ifdef CONFIG_FEATURE_VI_READONLY
1856 "\n\tReadonly if vi is called as \"view\""
1857 "\n\tReadonly with -R command line arg"
1858 #endif /* CONFIG_FEATURE_VI_READONLY */
1859 #ifdef CONFIG_FEATURE_VI_SET
1860 "\n\tSome colon mode commands with \':\'"
1861 #endif /* CONFIG_FEATURE_VI_SET */
1862 #ifdef CONFIG_FEATURE_VI_SETOPTS
1863 "\n\tSettable options with \":set\""
1864 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1865 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1866 "\n\tSignal catching- ^C"
1867 "\n\tJob suspend and resume with ^Z"
1868 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1869 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1870 "\n\tAdapt to window re-sizes"
1871 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1875 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1880 strcpy((char *) buf, ""); // init buf
1881 if (strlen((char *) s) <= 0)
1882 s = (Byte *) "(NULL)";
1883 for (; *s > '\0'; s++) {
1887 c_is_no_print = c > 127 && !Isprint(c);
1888 if (c_is_no_print) {
1889 strcat((char *) buf, SOn);
1892 if (c < ' ' || c == 127) {
1893 strcat((char *) buf, "^");
1900 strcat((char *) buf, (char *) b);
1902 strcat((char *) buf, SOs);
1904 strcat((char *) buf, "$");
1909 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1910 static void start_new_cmd_q(Byte c)
1913 free(last_modifying_cmd);
1914 // get buffer for new cmd
1915 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1916 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1917 // if there is a current cmd count put it in the buffer first
1919 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1920 else // just save char c onto queue
1921 last_modifying_cmd[0] = c;
1925 static void end_cmd_q(void)
1927 #ifdef CONFIG_FEATURE_VI_YANKMARK
1928 YDreg = 26; // go back to default Yank/Delete reg
1929 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1933 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1935 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1936 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1940 i = strlen((char *) s);
1941 p = text_hole_make(p, i);
1942 strncpy((char *) p, (char *) s, i);
1943 for (cnt = 0; *s != '\0'; s++) {
1947 #ifdef CONFIG_FEATURE_VI_YANKMARK
1948 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1949 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1952 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1954 #ifdef CONFIG_FEATURE_VI_YANKMARK
1955 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1960 if (q < p) { // they are backwards- reverse them
1967 free(t); // if already a yank register, free it
1968 t = (Byte *) xmalloc(cnt + 1); // get a new register
1969 memset(t, '\0', cnt + 1); // clear new text[]
1970 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1975 static Byte what_reg(void)
1981 c = 'D'; // default to D-reg
1982 if (0 <= YDreg && YDreg <= 25)
1983 c = 'a' + (Byte) YDreg;
1991 static void check_context(Byte cmd)
1993 // A context is defined to be "modifying text"
1994 // Any modifying command establishes a new context.
1996 if (dot < context_start || dot > context_end) {
1997 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1998 // we are trying to modify text[]- make this the current context
1999 mark[27] = mark[26]; // move cur to prev
2000 mark[26] = dot; // move local to cur
2001 context_start = prev_line(prev_line(dot));
2002 context_end = next_line(next_line(dot));
2003 //loiter= start_loiter= now;
2009 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2013 // the current context is in mark[26]
2014 // the previous context is in mark[27]
2015 // only swap context if other context is valid
2016 if (text <= mark[27] && mark[27] <= end - 1) {
2018 mark[27] = mark[26];
2020 p = mark[26]; // where we are going- previous context
2021 context_start = prev_line(prev_line(prev_line(p)));
2022 context_end = next_line(next_line(next_line(p)));
2026 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2028 static int isblnk(Byte c) // is the char a blank or tab
2030 return (c == ' ' || c == '\t');
2033 //----- Set terminal attributes --------------------------------
2034 static void rawmode(void)
2036 tcgetattr(0, &term_orig);
2037 term_vi = term_orig;
2038 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2039 term_vi.c_iflag &= (~IXON & ~ICRNL);
2040 term_vi.c_oflag &= (~ONLCR);
2041 term_vi.c_cc[VMIN] = 1;
2042 term_vi.c_cc[VTIME] = 0;
2043 erase_char = term_vi.c_cc[VERASE];
2044 tcsetattr(0, TCSANOW, &term_vi);
2047 static void cookmode(void)
2050 tcsetattr(0, TCSANOW, &term_orig);
2053 //----- Come here when we get a window resize signal ---------
2054 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2055 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2057 signal(SIGWINCH, winch_sig);
2058 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2059 get_terminal_width_height(0, &columns, &rows);
2060 new_screen(rows, columns); // get memory for virtual screen
2061 redraw(TRUE); // re-draw the screen
2064 //----- Come here when we get a continue signal -------------------
2065 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2067 rawmode(); // terminal to "raw"
2068 last_status_cksum = 0; // force status update
2069 redraw(TRUE); // re-draw the screen
2071 signal(SIGTSTP, suspend_sig);
2072 signal(SIGCONT, SIG_DFL);
2073 kill(my_pid, SIGCONT);
2076 //----- Come here when we get a Suspend signal -------------------
2077 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2079 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2080 clear_to_eol(); // Erase to end of line
2081 cookmode(); // terminal to "cooked"
2083 signal(SIGCONT, cont_sig);
2084 signal(SIGTSTP, SIG_DFL);
2085 kill(my_pid, SIGTSTP);
2088 //----- Come here when we get a signal ---------------------------
2089 static void catch_sig(int sig)
2091 signal(SIGINT, catch_sig);
2093 longjmp(restart, sig);
2095 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2097 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2099 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2104 tv.tv_usec = hund * 10000;
2105 select(1, &rfds, NULL, NULL, &tv);
2106 return (FD_ISSET(0, &rfds));
2109 #define readbuffer bb_common_bufsiz1
2111 static int readed_for_parse;
2113 //----- IO Routines --------------------------------------------
2114 static Byte readit(void) // read (maybe cursor) key from stdin
2123 static const struct esc_cmds esccmds[] = {
2124 {"OA", (Byte) VI_K_UP}, // cursor key Up
2125 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2126 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2127 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2128 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2129 {"OF", (Byte) VI_K_END}, // Cursor Key End
2130 {"[A", (Byte) VI_K_UP}, // cursor key Up
2131 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2132 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2133 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2134 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2135 {"[F", (Byte) VI_K_END}, // Cursor Key End
2136 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2137 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2138 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2139 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2140 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2141 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2142 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2143 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2144 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2145 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2146 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2147 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2148 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2149 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2150 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2151 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2152 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2153 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2154 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2155 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2156 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2159 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2161 (void) alarm(0); // turn alarm OFF while we wait for input
2163 n = readed_for_parse;
2164 // get input from User- are there already input chars in Q?
2167 // the Q is empty, wait for a typed char
2168 n = read(0, readbuffer, BUFSIZ - 1);
2171 goto ri0; // interrupted sys call
2174 if (errno == EFAULT)
2176 if (errno == EINVAL)
2184 if (readbuffer[0] == 27) {
2185 // This is an ESC char. Is this Esc sequence?
2186 // Could be bare Esc key. See if there are any
2187 // more chars to read after the ESC. This would
2188 // be a Function or Cursor Key sequence.
2192 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2194 // keep reading while there are input chars and room in buffer
2195 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2196 // read the rest of the ESC string
2197 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2203 readed_for_parse = n;
2206 if(c == 27 && n > 1) {
2207 // Maybe cursor or function key?
2208 const struct esc_cmds *eindex;
2210 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2211 int cnt = strlen(eindex->seq);
2215 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2217 // is a Cursor key- put derived value back into Q
2219 // for squeeze out the ESC sequence
2223 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2224 /* defined ESC sequence not found, set only one ESC */
2230 // remove key sequence from Q
2231 readed_for_parse -= n;
2232 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2233 (void) alarm(3); // we are done waiting for input, turn alarm ON
2237 //----- IO Routines --------------------------------------------
2238 static Byte get_one_char(void)
2242 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2243 // ! adding2q && ioq == 0 read()
2244 // ! adding2q && ioq != 0 *ioq
2245 // adding2q *last_modifying_cmd= read()
2247 // we are not adding to the q.
2248 // but, we may be reading from a q
2250 // there is no current q, read from STDIN
2251 c = readit(); // get the users input
2253 // there is a queue to get chars from first
2256 // the end of the q, read from STDIN
2258 ioq_start = ioq = 0;
2259 c = readit(); // get the users input
2263 // adding STDIN chars to q
2264 c = readit(); // get the users input
2265 if (last_modifying_cmd != 0) {
2266 int len = strlen((char *) last_modifying_cmd);
2267 if (len + 1 >= BUFSIZ) {
2268 psbs("last_modifying_cmd overrun");
2270 // add new char to q
2271 last_modifying_cmd[len] = c;
2275 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2276 c = readit(); // get the users input
2277 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2278 return (c); // return the char, where ever it came from
2281 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2286 static Byte *obufp = NULL;
2288 strcpy((char *) buf, (char *) prompt);
2289 last_status_cksum = 0; // force status update
2290 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2291 clear_to_eol(); // clear the line
2292 write1((char *) prompt); // write out the :, /, or ? prompt
2294 for (i = strlen((char *) buf); i < BUFSIZ;) {
2295 c = get_one_char(); // read user input
2296 if (c == '\n' || c == '\r' || c == 27)
2297 break; // is this end of input
2298 if (c == erase_char || c == 8 || c == 127) {
2299 // user wants to erase prev char
2300 i--; // backup to prev char
2301 buf[i] = '\0'; // erase the char
2302 buf[i + 1] = '\0'; // null terminate buffer
2303 write1("\b \b"); // erase char on screen
2304 if (i <= 0) { // user backs up before b-o-l, exit
2308 buf[i] = c; // save char in buffer
2309 buf[i + 1] = '\0'; // make sure buffer is null terminated
2310 putchar(c); // echo the char back to user
2316 obufp = (Byte *) bb_xstrdup((char *) buf);
2320 static int file_size(const Byte * fn) // what is the byte size of "fn"
2325 if (fn == 0 || strlen((char *)fn) <= 0)
2328 sr = stat((char *) fn, &st_buf); // see if file exists
2330 cnt = (int) st_buf.st_size;
2335 static int file_insert(Byte * fn, Byte * p, int size)
2340 #ifdef CONFIG_FEATURE_VI_READONLY
2342 #endif /* CONFIG_FEATURE_VI_READONLY */
2343 if (fn == 0 || strlen((char*) fn) <= 0) {
2344 psbs("No filename given");
2348 // OK- this is just a no-op
2353 psbs("Trying to insert a negative number (%d) of characters", size);
2356 if (p < text || p > end) {
2357 psbs("Trying to insert file outside of memory");
2361 // see if we can open the file
2362 #ifdef CONFIG_FEATURE_VI_READONLY
2363 if (vi_readonly) goto fi1; // do not try write-mode
2365 fd = open((char *) fn, O_RDWR); // assume read & write
2367 // could not open for writing- maybe file is read only
2368 #ifdef CONFIG_FEATURE_VI_READONLY
2371 fd = open((char *) fn, O_RDONLY); // try read-only
2373 psbs("\"%s\" %s", fn, "could not open file");
2376 #ifdef CONFIG_FEATURE_VI_READONLY
2377 // got the file- read-only
2379 #endif /* CONFIG_FEATURE_VI_READONLY */
2381 p = text_hole_make(p, size);
2382 cnt = read(fd, p, size);
2386 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2387 psbs("could not read file \"%s\"", fn);
2388 } else if (cnt < size) {
2389 // There was a partial read, shrink unused space text[]
2390 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2391 psbs("could not read all of file \"%s\"", fn);
2399 static int file_write(Byte * fn, Byte * first, Byte * last)
2401 int fd, cnt, charcnt;
2404 psbs("No current filename");
2408 // FIXIT- use the correct umask()
2409 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2412 cnt = last - first + 1;
2413 charcnt = write(fd, first, cnt);
2414 if (charcnt == cnt) {
2416 //file_modified= FALSE; // the file has not been modified
2424 //----- Terminal Drawing ---------------------------------------
2425 // The terminal is made up of 'rows' line of 'columns' columns.
2426 // classically this would be 24 x 80.
2427 // screen coordinates
2433 // 23,0 ... 23,79 status line
2436 //----- Move the cursor to row x col (count from 0, not 1) -------
2437 static void place_cursor(int row, int col, int opti)
2441 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2444 // char cm3[BUFSIZ];
2446 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2448 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2450 if (row < 0) row = 0;
2451 if (row >= rows) row = rows - 1;
2452 if (col < 0) col = 0;
2453 if (col >= columns) col = columns - 1;
2455 //----- 1. Try the standard terminal ESC sequence
2456 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2458 if (! opti) goto pc0;
2460 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2461 //----- find the minimum # of chars to move cursor -------------
2462 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2463 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2465 // move to the correct row
2466 while (row < Rrow) {
2467 // the cursor has to move up
2471 while (row > Rrow) {
2472 // the cursor has to move down
2473 strcat(cm2, CMdown);
2477 // now move to the correct column
2478 strcat(cm2, "\r"); // start at col 0
2479 // just send out orignal source char to get to correct place
2480 screenp = &screen[row * columns]; // start of screen line
2481 strncat(cm2, (char* )screenp, col);
2483 //----- 3. Try some other way of moving cursor
2484 //---------------------------------------------
2486 // pick the shortest cursor motion to send out
2488 if (strlen(cm2) < strlen(cm)) {
2490 } /* else if (strlen(cm3) < strlen(cm)) {
2493 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2495 write1(cm); // move the cursor
2498 //----- Erase from cursor to end of line -----------------------
2499 static void clear_to_eol(void)
2501 write1(Ceol); // Erase from cursor to end of line
2504 //----- Erase from cursor to end of screen -----------------------
2505 static void clear_to_eos(void)
2507 write1(Ceos); // Erase from cursor to end of screen
2510 //----- Start standout mode ------------------------------------
2511 static void standout_start(void) // send "start reverse video" sequence
2513 write1(SOs); // Start reverse video mode
2516 //----- End standout mode --------------------------------------
2517 static void standout_end(void) // send "end reverse video" sequence
2519 write1(SOn); // End reverse video mode
2522 //----- Flash the screen --------------------------------------
2523 static void flash(int h)
2525 standout_start(); // send "start reverse video" sequence
2528 standout_end(); // send "end reverse video" sequence
2532 static void Indicate_Error(void)
2534 #ifdef CONFIG_FEATURE_VI_CRASHME
2536 return; // generate a random command
2537 #endif /* CONFIG_FEATURE_VI_CRASHME */
2539 write1(bell); // send out a bell character
2545 //----- Screen[] Routines --------------------------------------
2546 //----- Erase the Screen[] memory ------------------------------
2547 static void screen_erase(void)
2549 memset(screen, ' ', screensize); // clear new screen
2552 static int bufsum(unsigned char *buf, int count)
2555 unsigned char *e = buf + count;
2561 //----- Draw the status line at bottom of the screen -------------
2562 static void show_status_line(void)
2564 int cnt = 0, cksum = 0;
2566 // either we already have an error or status message, or we
2568 if (!have_status_msg) {
2569 cnt = format_edit_status();
2570 cksum = bufsum(status_buffer, cnt);
2572 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2573 last_status_cksum= cksum; // remember if we have seen this line
2574 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2575 write1((char*)status_buffer);
2577 if (have_status_msg) {
2578 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2580 have_status_msg = 0;
2583 have_status_msg = 0;
2585 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2590 //----- format the status buffer, the bottom line of screen ------
2591 // format status buffer, with STANDOUT mode
2592 static void psbs(const char *format, ...)
2596 va_start(args, format);
2597 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2598 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2599 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2602 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2607 // format status buffer
2608 static void psb(const char *format, ...)
2612 va_start(args, format);
2613 vsprintf((char *) status_buffer, format, args);
2616 have_status_msg = 1;
2621 static void ni(Byte * s) // display messages
2625 print_literal(buf, s);
2626 psbs("\'%s\' is not implemented", buf);
2629 static int format_edit_status(void) // show file status on status line
2631 int cur, percent, ret, trunc_at;
2634 // file_modified is now a counter rather than a flag. this
2635 // helps reduce the amount of line counting we need to do.
2636 // (this will cause a mis-reporting of modified status
2637 // once every MAXINT editing operations.)
2639 // it would be nice to do a similar optimization here -- if
2640 // we haven't done a motion that could have changed which line
2641 // we're on, then we shouldn't have to do this count_lines()
2642 cur = count_lines(text, dot);
2644 // reduce counting -- the total lines can't have
2645 // changed if we haven't done any edits.
2646 if (file_modified != last_file_modified) {
2647 tot = cur + count_lines(dot, end - 1) - 1;
2648 last_file_modified = file_modified;
2651 // current line percent
2652 // ------------- ~~ ----------
2655 percent = (100 * cur) / tot;
2661 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2662 columns : STATUS_BUFFER_LEN-1;
2664 ret = snprintf((char *) status_buffer, trunc_at+1,
2665 #ifdef CONFIG_FEATURE_VI_READONLY
2666 "%c %s%s%s %d/%d %d%%",
2668 "%c %s%s %d/%d %d%%",
2670 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2671 (cfn != 0 ? (char *) cfn : "No file"),
2672 #ifdef CONFIG_FEATURE_VI_READONLY
2673 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2675 (file_modified ? " [modified]" : ""),
2678 if (ret >= 0 && ret < trunc_at)
2679 return ret; /* it all fit */
2681 return trunc_at; /* had to truncate */
2684 //----- Force refresh of all Lines -----------------------------
2685 static void redraw(int full_screen)
2687 place_cursor(0, 0, FALSE); // put cursor in correct place
2688 clear_to_eos(); // tel terminal to erase display
2689 screen_erase(); // erase the internal screen buffer
2690 last_status_cksum = 0; // force status update
2691 refresh(full_screen); // this will redraw the entire display
2695 //----- Format a text[] line into a buffer ---------------------
2696 static void format_line(Byte *dest, Byte *src, int li)
2701 for (co= 0; co < MAX_SCR_COLS; co++) {
2702 c= ' '; // assume blank
2703 if (li > 0 && co == 0) {
2704 c = '~'; // not first line, assume Tilde
2706 // are there chars in text[] and have we gone past the end
2707 if (text < end && src < end) {
2712 if (c > 127 && !Isprint(c)) {
2715 if (c < ' ' || c == 127) {
2719 for (; (co % tabstop) != (tabstop - 1); co++) {
2727 c += '@'; // make it visible
2730 // the co++ is done here so that the column will
2731 // not be overwritten when we blank-out the rest of line
2738 //----- Refresh the changed screen lines -----------------------
2739 // Copy the source line from text[] into the buffer and note
2740 // if the current screenline is different from the new buffer.
2741 // If they differ then that line needs redrawing on the terminal.
2743 static void refresh(int full_screen)
2745 static int old_offset;
2747 Byte buf[MAX_SCR_COLS];
2748 Byte *tp, *sp; // pointer into text[] and screen[]
2749 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2750 int last_li= -2; // last line that changed- for optimizing cursor movement
2751 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2753 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2754 get_terminal_width_height(0, &columns, &rows);
2755 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2756 tp = screenbegin; // index into text[] of top line
2758 // compare text[] to screen[] and mark screen[] lines that need updating
2759 for (li = 0; li < rows - 1; li++) {
2760 int cs, ce; // column start & end
2761 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2762 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2763 // format current text line into buf
2764 format_line(buf, tp, li);
2766 // skip to the end of the current text[] line
2767 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2769 // see if there are any changes between vitual screen and buf
2770 changed = FALSE; // assume no change
2773 sp = &screen[li * columns]; // start of screen line
2775 // force re-draw of every single column from 0 - columns-1
2778 // compare newly formatted buffer with virtual screen
2779 // look forward for first difference between buf and screen
2780 for ( ; cs <= ce; cs++) {
2781 if (buf[cs + offset] != sp[cs]) {
2782 changed = TRUE; // mark for redraw
2787 // look backward for last difference between buf and screen
2788 for ( ; ce >= cs; ce--) {
2789 if (buf[ce + offset] != sp[ce]) {
2790 changed = TRUE; // mark for redraw
2794 // now, cs is index of first diff, and ce is index of last diff
2796 // if horz offset has changed, force a redraw
2797 if (offset != old_offset) {
2802 // make a sanity check of columns indexes
2804 if (ce > columns-1) ce= columns-1;
2805 if (cs > ce) { cs= 0; ce= columns-1; }
2806 // is there a change between vitual screen and buf
2808 // copy changed part of buffer to virtual screen
2809 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2811 // move cursor to column of first change
2812 if (offset != old_offset) {
2813 // opti_cur_move is still too stupid
2814 // to handle offsets correctly
2815 place_cursor(li, cs, FALSE);
2817 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2818 // if this just the next line
2819 // try to optimize cursor movement
2820 // otherwise, use standard ESC sequence
2821 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2823 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2824 place_cursor(li, cs, FALSE); // use standard ESC sequence
2825 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2828 // write line out to terminal
2831 char *out = (char*)sp+cs;
2838 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2840 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2844 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2845 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2848 place_cursor(crow, ccol, FALSE);
2849 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2851 if (offset != old_offset)
2852 old_offset = offset;
2855 //---------------------------------------------------------------------
2856 //----- the Ascii Chart -----------------------------------------------
2858 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2859 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2860 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2861 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2862 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2863 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2864 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2865 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2866 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2867 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2868 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2869 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2870 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2871 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2872 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2873 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2874 //---------------------------------------------------------------------
2876 //----- Execute a Vi Command -----------------------------------
2877 static void do_cmd(Byte c)
2879 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2880 int cnt, i, j, dir, yf;
2882 c1 = c; // quiet the compiler
2883 cnt = yf = dir = 0; // quiet the compiler
2884 p = q = save_dot = msg = buf; // quiet the compiler
2885 memset(buf, '\0', 9); // clear buf
2889 /* if this is a cursor key, skip these checks */
2902 if (cmd_mode == 2) {
2903 // flip-flop Insert/Replace mode
2904 if (c == VI_K_INSERT) goto dc_i;
2905 // we are 'R'eplacing the current *dot with new char
2907 // don't Replace past E-o-l
2908 cmd_mode = 1; // convert to insert
2910 if (1 <= c || Isprint(c)) {
2912 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2913 dot = char_insert(dot, c); // insert new char
2918 if (cmd_mode == 1) {
2919 // hitting "Insert" twice means "R" replace mode
2920 if (c == VI_K_INSERT) goto dc5;
2921 // insert the char c at "dot"
2922 if (1 <= c || Isprint(c)) {
2923 dot = char_insert(dot, c);
2938 #ifdef CONFIG_FEATURE_VI_CRASHME
2939 case 0x14: // dc4 ctrl-T
2940 crashme = (crashme == 0) ? 1 : 0;
2942 #endif /* CONFIG_FEATURE_VI_CRASHME */
2971 //case 'u': // u- FIXME- there is no undo
2973 default: // unrecognised command
2982 end_cmd_q(); // stop adding to q
2983 case 0x00: // nul- ignore
2985 case 2: // ctrl-B scroll up full screen
2986 case VI_K_PAGEUP: // Cursor Key Page Up
2987 dot_scroll(rows - 2, -1);
2989 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2990 case 0x03: // ctrl-C interrupt
2991 longjmp(restart, 1);
2993 case 26: // ctrl-Z suspend
2994 suspend_sig(SIGTSTP);
2996 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2997 case 4: // ctrl-D scroll down half screen
2998 dot_scroll((rows - 2) / 2, 1);
3000 case 5: // ctrl-E scroll down one line
3003 case 6: // ctrl-F scroll down full screen
3004 case VI_K_PAGEDOWN: // Cursor Key Page Down
3005 dot_scroll(rows - 2, 1);
3007 case 7: // ctrl-G show current status
3008 last_status_cksum = 0; // force status update
3010 case 'h': // h- move left
3011 case VI_K_LEFT: // cursor key Left
3012 case 8: // ctrl-H- move left (This may be ERASE char)
3013 case 127: // DEL- move left (This may be ERASE char)
3019 case 10: // Newline ^J
3020 case 'j': // j- goto next line, same col
3021 case VI_K_DOWN: // cursor key Down
3025 dot_next(); // go to next B-o-l
3026 dot = move_to_col(dot, ccol + offset); // try stay in same col
3028 case 12: // ctrl-L force redraw whole screen
3029 case 18: // ctrl-R force redraw
3030 place_cursor(0, 0, FALSE); // put cursor in correct place
3031 clear_to_eos(); // tel terminal to erase display
3033 screen_erase(); // erase the internal screen buffer
3034 last_status_cksum = 0; // force status update
3035 refresh(TRUE); // this will redraw the entire display
3037 case 13: // Carriage Return ^M
3038 case '+': // +- goto next line
3045 case 21: // ctrl-U scroll up half screen
3046 dot_scroll((rows - 2) / 2, -1);
3048 case 25: // ctrl-Y scroll up one line
3054 cmd_mode = 0; // stop insrting
3056 last_status_cksum = 0; // force status update
3058 case ' ': // move right
3059 case 'l': // move right
3060 case VI_K_RIGHT: // Cursor Key Right
3066 #ifdef CONFIG_FEATURE_VI_YANKMARK
3067 case '"': // "- name a register to use for Delete/Yank
3068 c1 = get_one_char();
3076 case '\'': // '- goto a specific mark
3077 c1 = get_one_char();
3083 if (text <= q && q < end) {
3085 dot_begin(); // go to B-o-l
3088 } else if (c1 == '\'') { // goto previous context
3089 dot = swap_context(dot); // swap current and previous context
3090 dot_begin(); // go to B-o-l
3096 case 'm': // m- Mark a line
3097 // this is really stupid. If there are any inserts or deletes
3098 // between text[0] and dot then this mark will not point to the
3099 // correct location! It could be off by many lines!
3100 // Well..., at least its quick and dirty.
3101 c1 = get_one_char();
3105 // remember the line
3106 mark[(int) c1] = dot;
3111 case 'P': // P- Put register before
3112 case 'p': // p- put register after
3115 psbs("Nothing in register %c", what_reg());
3118 // are we putting whole lines or strings
3119 if (strchr((char *) p, '\n') != NULL) {
3121 dot_begin(); // putting lines- Put above
3124 // are we putting after very last line?
3125 if (end_line(dot) == (end - 1)) {
3126 dot = end; // force dot to end of text[]
3128 dot_next(); // next line, then put before
3133 dot_right(); // move to right, can move to NL
3135 dot = string_insert(dot, p); // insert the string
3136 end_cmd_q(); // stop adding to q
3138 case 'U': // U- Undo; replace current line with original version
3139 if (reg[Ureg] != 0) {
3140 p = begin_line(dot);
3142 p = text_hole_delete(p, q); // delete cur line
3143 p = string_insert(p, reg[Ureg]); // insert orig line
3148 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3149 case '$': // $- goto end of line
3150 case VI_K_END: // Cursor Key End
3154 dot = end_line(dot);
3156 case '%': // %- find matching char of pair () [] {}
3157 for (q = dot; q < end && *q != '\n'; q++) {
3158 if (strchr("()[]{}", *q) != NULL) {
3159 // we found half of a pair
3160 p = find_pair(q, *q);
3172 case 'f': // f- forward to a user specified char
3173 last_forward_char = get_one_char(); // get the search char
3175 // dont separate these two commands. 'f' depends on ';'
3177 //**** fall thru to ... ';'
3178 case ';': // ;- look at rest of line for last forward char
3182 if (last_forward_char == 0) break;
3184 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3187 if (*q == last_forward_char)
3190 case '-': // -- goto prev line
3197 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3198 case '.': // .- repeat the last modifying command
3199 // Stuff the last_modifying_cmd back into stdin
3200 // and let it be re-executed.
3201 if (last_modifying_cmd != 0) {
3202 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3205 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3206 #ifdef CONFIG_FEATURE_VI_SEARCH
3207 case '?': // /- search for a pattern
3208 case '/': // /- search for a pattern
3211 q = get_input_line(buf); // get input line- use "status line"
3212 if (strlen((char *) q) == 1)
3213 goto dc3; // if no pat re-use old pat
3214 if (strlen((char *) q) > 1) { // new pat- save it and find
3215 // there is a new pat
3216 free(last_search_pattern);
3217 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3218 goto dc3; // now find the pattern
3220 // user changed mind and erased the "/"- do nothing
3222 case 'N': // N- backward search for last pattern
3226 dir = BACK; // assume BACKWARD search
3228 if (last_search_pattern[0] == '?') {
3232 goto dc4; // now search for pattern
3234 case 'n': // n- repeat search for last pattern
3235 // search rest of text[] starting at next char
3236 // if search fails return orignal "p" not the "p+1" address
3241 if (last_search_pattern == 0) {
3242 msg = (Byte *) "No previous regular expression";
3245 if (last_search_pattern[0] == '/') {
3246 dir = FORWARD; // assume FORWARD search
3249 if (last_search_pattern[0] == '?') {
3254 q = char_search(p, last_search_pattern + 1, dir, FULL);
3256 dot = q; // good search, update "dot"
3260 // no pattern found between "dot" and "end"- continue at top
3265 q = char_search(p, last_search_pattern + 1, dir, FULL);
3266 if (q != NULL) { // found something
3267 dot = q; // found new pattern- goto it
3268 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3270 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3273 msg = (Byte *) "Pattern not found";
3276 if (*msg) psbs("%s", msg);
3278 case '{': // {- move backward paragraph
3279 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3280 if (q != NULL) { // found blank line
3281 dot = next_line(q); // move to next blank line
3284 case '}': // }- move forward paragraph
3285 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3286 if (q != NULL) { // found blank line
3287 dot = next_line(q); // move to next blank line
3290 #endif /* CONFIG_FEATURE_VI_SEARCH */
3291 case '0': // 0- goto begining of line
3301 if (c == '0' && cmdcnt < 1) {
3302 dot_begin(); // this was a standalone zero
3304 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3307 case ':': // :- the colon mode commands
3308 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3309 #ifdef CONFIG_FEATURE_VI_COLON
3310 colon(p); // execute the command
3311 #else /* CONFIG_FEATURE_VI_COLON */
3313 p++; // move past the ':'
3314 cnt = strlen((char *) p);
3317 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3318 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3319 if (file_modified && p[1] != '!') {
3320 psbs("No write since last change (:quit! overrides)");
3324 } else if (strncasecmp((char *) p, "write", cnt) == 0
3325 || strncasecmp((char *) p, "wq", cnt) == 0
3326 || strncasecmp((char *) p, "wn", cnt) == 0
3327 || strncasecmp((char *) p, "x", cnt) == 0) {
3328 cnt = file_write(cfn, text, end - 1);
3331 psbs("Write error: %s", strerror(errno));
3334 last_file_modified = -1;
3335 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3336 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3337 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3341 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3342 last_status_cksum = 0; // force status update
3343 } else if (sscanf((char *) p, "%d", &j) > 0) {
3344 dot = find_line(j); // go to line # j
3346 } else { // unrecognised cmd
3349 #endif /* CONFIG_FEATURE_VI_COLON */
3351 case '<': // <- Left shift something
3352 case '>': // >- Right shift something
3353 cnt = count_lines(text, dot); // remember what line we are on
3354 c1 = get_one_char(); // get the type of thing to delete
3355 find_range(&p, &q, c1);
3356 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3359 i = count_lines(p, q); // # of lines we are shifting
3360 for ( ; i > 0; i--, p = next_line(p)) {
3362 // shift left- remove tab or 8 spaces
3364 // shrink buffer 1 char
3365 (void) text_hole_delete(p, p);
3366 } else if (*p == ' ') {
3367 // we should be calculating columns, not just SPACE
3368 for (j = 0; *p == ' ' && j < tabstop; j++) {
3369 (void) text_hole_delete(p, p);
3372 } else if (c == '>') {
3373 // shift right -- add tab or 8 spaces
3374 (void) char_insert(p, '\t');
3377 dot = find_line(cnt); // what line were we on
3379 end_cmd_q(); // stop adding to q
3381 case 'A': // A- append at e-o-l
3382 dot_end(); // go to e-o-l
3383 //**** fall thru to ... 'a'
3384 case 'a': // a- append after current char
3389 case 'B': // B- back a blank-delimited Word
3390 case 'E': // E- end of a blank-delimited word
3391 case 'W': // W- forward a blank-delimited word
3398 if (c == 'W' || isspace(dot[dir])) {
3399 dot = skip_thing(dot, 1, dir, S_TO_WS);
3400 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3403 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3405 case 'C': // C- Change to e-o-l
3406 case 'D': // D- delete to e-o-l
3408 dot = dollar_line(dot); // move to before NL
3409 // copy text into a register and delete
3410 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3412 goto dc_i; // start inserting
3413 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3415 end_cmd_q(); // stop adding to q
3416 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3418 case 'G': // G- goto to a line number (default= E-O-F)
3419 dot = end - 1; // assume E-O-F
3421 dot = find_line(cmdcnt); // what line is #cmdcnt
3425 case 'H': // H- goto top line on screen
3427 if (cmdcnt > (rows - 1)) {
3428 cmdcnt = (rows - 1);
3435 case 'I': // I- insert before first non-blank
3438 //**** fall thru to ... 'i'
3439 case 'i': // i- insert before current char
3440 case VI_K_INSERT: // Cursor Key Insert
3442 cmd_mode = 1; // start insrting
3444 case 'J': // J- join current and next lines together
3448 dot_end(); // move to NL
3449 if (dot < end - 1) { // make sure not last char in text[]
3450 *dot++ = ' '; // replace NL with space
3452 while (isblnk(*dot)) { // delete leading WS
3456 end_cmd_q(); // stop adding to q
3458 case 'L': // L- goto bottom line on screen
3460 if (cmdcnt > (rows - 1)) {
3461 cmdcnt = (rows - 1);
3469 case 'M': // M- goto middle line on screen
3471 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3472 dot = next_line(dot);
3474 case 'O': // O- open a empty line above
3476 p = begin_line(dot);
3477 if (p[-1] == '\n') {
3479 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3481 dot = char_insert(dot, '\n');
3484 dot = char_insert(dot, '\n'); // i\n ESC
3489 case 'R': // R- continuous Replace char
3493 case 'X': // X- delete char before dot
3494 case 'x': // x- delete the current char
3495 case 's': // s- substitute the current char
3502 if (dot[dir] != '\n') {
3504 dot--; // delete prev char
3505 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3508 goto dc_i; // start insrting
3509 end_cmd_q(); // stop adding to q
3511 case 'Z': // Z- if modified, {write}; exit
3512 // ZZ means to save file (if necessary), then exit
3513 c1 = get_one_char();
3518 if (file_modified) {
3519 #ifdef CONFIG_FEATURE_VI_READONLY
3520 if (vi_readonly || readonly) {
3521 psbs("\"%s\" File is read only", cfn);
3524 #endif /* CONFIG_FEATURE_VI_READONLY */
3525 cnt = file_write(cfn, text, end - 1);
3528 psbs("Write error: %s", strerror(errno));
3529 } else if (cnt == (end - 1 - text + 1)) {
3536 case '^': // ^- move to first non-blank on line
3540 case 'b': // b- back a word
3541 case 'e': // e- end of word
3548 if ((dot + dir) < text || (dot + dir) > end - 1)
3551 if (isspace(*dot)) {
3552 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3554 if (isalnum(*dot) || *dot == '_') {
3555 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3556 } else if (ispunct(*dot)) {
3557 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3560 case 'c': // c- change something
3561 case 'd': // d- delete something
3562 #ifdef CONFIG_FEATURE_VI_YANKMARK
3563 case 'y': // y- yank something
3564 case 'Y': // Y- Yank a line
3565 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3566 yf = YANKDEL; // assume either "c" or "d"
3567 #ifdef CONFIG_FEATURE_VI_YANKMARK
3568 if (c == 'y' || c == 'Y')
3570 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3573 c1 = get_one_char(); // get the type of thing to delete
3574 find_range(&p, &q, c1);
3575 if (c1 == 27) { // ESC- user changed mind and wants out
3576 c = c1 = 27; // Escape- do nothing
3577 } else if (strchr("wW", c1)) {
3579 // don't include trailing WS as part of word
3580 while (isblnk(*q)) {
3581 if (q <= text || q[-1] == '\n')
3586 dot = yank_delete(p, q, 0, yf); // delete word
3587 } else if (strchr("^0bBeEft$", c1)) {
3588 // single line copy text into a register and delete
3589 dot = yank_delete(p, q, 0, yf); // delete word
3590 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3591 // multiple line copy text into a register and delete
3592 dot = yank_delete(p, q, 1, yf); // delete lines
3594 dot = char_insert(dot, '\n');
3595 // on the last line of file don't move to prev line
3596 if (dot != (end-1)) {
3599 } else if (c == 'd') {
3604 // could not recognize object
3605 c = c1 = 27; // error-
3609 // if CHANGING, not deleting, start inserting after the delete
3611 strcpy((char *) buf, "Change");
3612 goto dc_i; // start inserting
3615 strcpy((char *) buf, "Delete");
3617 #ifdef CONFIG_FEATURE_VI_YANKMARK
3618 if (c == 'y' || c == 'Y') {
3619 strcpy((char *) buf, "Yank");
3622 q = p + strlen((char *) p);
3623 for (cnt = 0; p <= q; p++) {
3627 psb("%s %d lines (%d chars) using [%c]",
3628 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3629 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3630 end_cmd_q(); // stop adding to q
3633 case 'k': // k- goto prev line, same col
3634 case VI_K_UP: // cursor key Up
3639 dot = move_to_col(dot, ccol + offset); // try stay in same col
3641 case 'r': // r- replace the current char with user input
3642 c1 = get_one_char(); // get the replacement char
3645 file_modified++; // has the file been modified
3647 end_cmd_q(); // stop adding to q
3649 case 't': // t- move to char prior to next x
3650 last_forward_char = get_one_char();
3652 if (*dot == last_forward_char)
3654 last_forward_char= 0;
3656 case 'w': // w- forward a word
3660 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3661 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3662 } else if (ispunct(*dot)) { // we are on PUNCT
3663 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3666 dot++; // move over word
3667 if (isspace(*dot)) {
3668 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3672 c1 = get_one_char(); // get the replacement char
3675 cnt = (rows - 2) / 2; // put dot at center
3677 cnt = rows - 2; // put dot at bottom
3678 screenbegin = begin_line(dot); // start dot at top
3679 dot_scroll(cnt, -1);
3681 case '|': // |- move to column "cmdcnt"
3682 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3684 case '~': // ~- flip the case of letters a-z -> A-Z
3688 if (islower(*dot)) {
3689 *dot = toupper(*dot);
3690 file_modified++; // has the file been modified
3691 } else if (isupper(*dot)) {
3692 *dot = tolower(*dot);
3693 file_modified++; // has the file been modified
3696 end_cmd_q(); // stop adding to q
3698 //----- The Cursor and Function Keys -----------------------------
3699 case VI_K_HOME: // Cursor Key Home
3702 // The Fn keys could point to do_macro which could translate them
3703 case VI_K_FUN1: // Function Key F1
3704 case VI_K_FUN2: // Function Key F2
3705 case VI_K_FUN3: // Function Key F3
3706 case VI_K_FUN4: // Function Key F4
3707 case VI_K_FUN5: // Function Key F5
3708 case VI_K_FUN6: // Function Key F6
3709 case VI_K_FUN7: // Function Key F7
3710 case VI_K_FUN8: // Function Key F8
3711 case VI_K_FUN9: // Function Key F9
3712 case VI_K_FUN10: // Function Key F10
3713 case VI_K_FUN11: // Function Key F11
3714 case VI_K_FUN12: // Function Key F12
3719 // if text[] just became empty, add back an empty line
3721 (void) char_insert(text, '\n'); // start empty buf with dummy line
3724 // it is OK for dot to exactly equal to end, otherwise check dot validity
3726 dot = bound_dot(dot); // make sure "dot" is valid
3728 #ifdef CONFIG_FEATURE_VI_YANKMARK
3729 check_context(c); // update the current context
3730 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3733 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3734 cnt = dot - begin_line(dot);
3735 // Try to stay off of the Newline
3736 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3740 #ifdef CONFIG_FEATURE_VI_CRASHME
3741 static int totalcmds = 0;
3742 static int Mp = 85; // Movement command Probability
3743 static int Np = 90; // Non-movement command Probability
3744 static int Dp = 96; // Delete command Probability
3745 static int Ip = 97; // Insert command Probability
3746 static int Yp = 98; // Yank command Probability
3747 static int Pp = 99; // Put command Probability
3748 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3749 char chars[20] = "\t012345 abcdABCD-=.$";
3750 char *words[20] = { "this", "is", "a", "test",
3751 "broadcast", "the", "emergency", "of",
3752 "system", "quick", "brown", "fox",
3753 "jumped", "over", "lazy", "dogs",
3754 "back", "January", "Febuary", "March"
3757 "You should have received a copy of the GNU General Public License\n",
3758 "char c, cm, *cmd, *cmd1;\n",
3759 "generate a command by percentages\n",
3760 "Numbers may be typed as a prefix to some commands.\n",
3761 "Quit, discarding changes!\n",
3762 "Forced write, if permission originally not valid.\n",
3763 "In general, any ex or ed command (such as substitute or delete).\n",
3764 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3765 "Please get w/ me and I will go over it with you.\n",
3766 "The following is a list of scheduled, committed changes.\n",
3767 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3768 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3769 "Any question about transactions please contact Sterling Huxley.\n",
3770 "I will try to get back to you by Friday, December 31.\n",
3771 "This Change will be implemented on Friday.\n",
3772 "Let me know if you have problems accessing this;\n",
3773 "Sterling Huxley recently added you to the access list.\n",
3774 "Would you like to go to lunch?\n",
3775 "The last command will be automatically run.\n",
3776 "This is too much english for a computer geek.\n",
3778 char *multilines[20] = {
3779 "You should have received a copy of the GNU General Public License\n",
3780 "char c, cm, *cmd, *cmd1;\n",
3781 "generate a command by percentages\n",
3782 "Numbers may be typed as a prefix to some commands.\n",
3783 "Quit, discarding changes!\n",
3784 "Forced write, if permission originally not valid.\n",
3785 "In general, any ex or ed command (such as substitute or delete).\n",
3786 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3787 "Please get w/ me and I will go over it with you.\n",
3788 "The following is a list of scheduled, committed changes.\n",
3789 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3790 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3791 "Any question about transactions please contact Sterling Huxley.\n",
3792 "I will try to get back to you by Friday, December 31.\n",
3793 "This Change will be implemented on Friday.\n",
3794 "Let me know if you have problems accessing this;\n",
3795 "Sterling Huxley recently added you to the access list.\n",
3796 "Would you like to go to lunch?\n",
3797 "The last command will be automatically run.\n",
3798 "This is too much english for a computer geek.\n",
3801 // create a random command to execute
3802 static void crash_dummy()
3804 static int sleeptime; // how long to pause between commands
3805 char c, cm, *cmd, *cmd1;
3806 int i, cnt, thing, rbi, startrbi, percent;
3808 // "dot" movement commands
3809 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3811 // is there already a command running?
3812 if (readed_for_parse > 0)
3816 sleeptime = 0; // how long to pause between commands
3817 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3818 // generate a command by percentages
3819 percent = (int) lrand48() % 100; // get a number from 0-99
3820 if (percent < Mp) { // Movement commands
3821 // available commands
3824 } else if (percent < Np) { // non-movement commands
3825 cmd = "mz<>\'\""; // available commands
3827 } else if (percent < Dp) { // Delete commands
3828 cmd = "dx"; // available commands
3830 } else if (percent < Ip) { // Inset commands
3831 cmd = "iIaAsrJ"; // available commands
3833 } else if (percent < Yp) { // Yank commands
3834 cmd = "yY"; // available commands
3836 } else if (percent < Pp) { // Put commands
3837 cmd = "pP"; // available commands
3840 // We do not know how to handle this command, try again
3844 // randomly pick one of the available cmds from "cmd[]"
3845 i = (int) lrand48() % strlen(cmd);
3847 if (strchr(":\024", cm))
3848 goto cd0; // dont allow colon or ctrl-T commands
3849 readbuffer[rbi++] = cm; // put cmd into input buffer
3851 // now we have the command-
3852 // there are 1, 2, and multi char commands
3853 // find out which and generate the rest of command as necessary
3854 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3855 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3856 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3857 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3859 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3861 readbuffer[rbi++] = c; // add movement to input buffer
3863 if (strchr("iIaAsc", cm)) { // multi-char commands
3865 // change some thing
3866 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3868 readbuffer[rbi++] = c; // add movement to input buffer
3870 thing = (int) lrand48() % 4; // what thing to insert
3871 cnt = (int) lrand48() % 10; // how many to insert
3872 for (i = 0; i < cnt; i++) {
3873 if (thing == 0) { // insert chars
3874 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3875 } else if (thing == 1) { // insert words
3876 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3877 strcat((char *) readbuffer, " ");
3878 sleeptime = 0; // how fast to type
3879 } else if (thing == 2) { // insert lines
3880 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3881 sleeptime = 0; // how fast to type
3882 } else { // insert multi-lines
3883 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3884 sleeptime = 0; // how fast to type
3887 strcat((char *) readbuffer, "\033");
3889 readed_for_parse = strlen(readbuffer);
3893 (void) mysleep(sleeptime); // sleep 1/100 sec
3896 // test to see if there are any errors
3897 static void crash_test()
3899 static time_t oldtim;
3901 char d[2], msg[BUFSIZ];
3905 strcat((char *) msg, "end<text ");
3907 if (end > textend) {
3908 strcat((char *) msg, "end>textend ");
3911 strcat((char *) msg, "dot<text ");
3914 strcat((char *) msg, "dot>end ");
3916 if (screenbegin < text) {
3917 strcat((char *) msg, "screenbegin<text ");
3919 if (screenbegin > end - 1) {
3920 strcat((char *) msg, "screenbegin>end-1 ");
3923 if (strlen(msg) > 0) {
3925 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3926 totalcmds, last_input_char, msg, SOs, SOn);
3928 while (read(0, d, 1) > 0) {
3929 if (d[0] == '\n' || d[0] == '\r')
3934 tim = (time_t) time((time_t *) 0);
3935 if (tim >= (oldtim + 3)) {
3936 sprintf((char *) status_buffer,
3937 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3938 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3943 #endif /* CONFIG_FEATURE_VI_CRASHME */