1 /* vi: set sw=8 ts=8: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * how about mode lines: vi: set sw=8 ts=8:
17 * if mark[] values were line numbers rather than pointers
18 * it would be easier to change the mark when add/delete lines
19 * More intelligence in refresh()
20 * ":r !cmd" and "!cmd" to filter text through an external command
21 * A true "undo" facility
22 * An "ex" line oriented mode- maybe using "cmdedit"
30 #include <sys/ioctl.h>
38 #define vi_Version BB_VER " " BB_BT
40 #ifdef CONFIG_LOCALE_SUPPORT
41 #define Isprint(c) isprint((c))
43 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
46 #define MAX_SCR_COLS BUFSIZ
48 // Misc. non-Ascii keys that report an escape sequence
49 #define VI_K_UP 128 // cursor key Up
50 #define VI_K_DOWN 129 // cursor key Down
51 #define VI_K_RIGHT 130 // Cursor Key Right
52 #define VI_K_LEFT 131 // cursor key Left
53 #define VI_K_HOME 132 // Cursor Key Home
54 #define VI_K_END 133 // Cursor Key End
55 #define VI_K_INSERT 134 // Cursor Key Insert
56 #define VI_K_PAGEUP 135 // Cursor Key Page Up
57 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
58 #define VI_K_FUN1 137 // Function Key F1
59 #define VI_K_FUN2 138 // Function Key F2
60 #define VI_K_FUN3 139 // Function Key F3
61 #define VI_K_FUN4 140 // Function Key F4
62 #define VI_K_FUN5 141 // Function Key F5
63 #define VI_K_FUN6 142 // Function Key F6
64 #define VI_K_FUN7 143 // Function Key F7
65 #define VI_K_FUN8 144 // Function Key F8
66 #define VI_K_FUN9 145 // Function Key F9
67 #define VI_K_FUN10 146 // Function Key F10
68 #define VI_K_FUN11 147 // Function Key F11
69 #define VI_K_FUN12 148 // Function Key F12
71 /* vt102 typical ESC sequence */
72 /* terminal standout start/normal ESC sequence */
73 static const char SOs[] = "\033[7m";
74 static const char SOn[] = "\033[0m";
75 /* terminal bell sequence */
76 static const char bell[] = "\007";
77 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
78 static const char Ceol[] = "\033[0K";
79 static const char Ceos [] = "\033[0J";
80 /* Cursor motion arbitrary destination ESC sequence */
81 static const char CMrc[] = "\033[%d;%dH";
82 /* Cursor motion up and down ESC sequence */
83 static const char CMup[] = "\033[A";
84 static const char CMdown[] = "\n";
90 FORWARD = 1, // code depends on "1" for array index
91 BACK = -1, // code depends on "-1" for array index
92 LIMITED = 0, // how much of text[] in char_search
93 FULL = 1, // how much of text[] in char_search
95 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
96 S_TO_WS = 2, // used in skip_thing() for moving "dot"
97 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
98 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
99 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
102 typedef unsigned char Byte;
104 static int vi_setops;
105 #define VI_AUTOINDENT 1
106 #define VI_SHOWMATCH 2
107 #define VI_IGNORECASE 4
108 #define VI_ERR_METHOD 8
109 #define autoindent (vi_setops & VI_AUTOINDENT)
110 #define showmatch (vi_setops & VI_SHOWMATCH )
111 #define ignorecase (vi_setops & VI_IGNORECASE)
112 /* indicate error with beep or flash */
113 #define err_method (vi_setops & VI_ERR_METHOD)
116 static int editing; // >0 while we are editing a file
117 static int cmd_mode; // 0=command 1=insert 2=replace
118 static int file_modified; // buffer contents changed
119 static int last_file_modified = -1;
120 static int fn_start; // index of first cmd line file name
121 static int save_argc; // how many file names on cmd line
122 static int cmdcnt; // repetition count
123 static fd_set rfds; // use select() for small sleeps
124 static struct timeval tv; // use select() for small sleeps
125 static int rows, columns; // the terminal screen is this size
126 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
127 static Byte *status_buffer; // mesages to the user
128 #define STATUS_BUFFER_LEN 200
129 static int have_status_msg; // is default edit status needed?
130 static int last_status_cksum; // hash of current status line
131 static Byte *cfn; // previous, current, and next file name
132 static Byte *text, *end, *textend; // pointers to the user data in memory
133 static Byte *screen; // pointer to the virtual screen buffer
134 static int screensize; // and its size
135 static Byte *screenbegin; // index into text[], of top line on the screen
136 static Byte *dot; // where all the action takes place
138 static struct termios term_orig, term_vi; // remember what the cooked mode was
139 static Byte erase_char; // the users erase character
140 static Byte last_input_char; // last char read from user
141 static Byte last_forward_char; // last char searched for with 'f'
143 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
144 static int last_row; // where the cursor was last moved to
145 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
146 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
147 static jmp_buf restart; // catch_sig()
148 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
149 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
152 #ifdef CONFIG_FEATURE_VI_DOT_CMD
153 static int adding2q; // are we currently adding user input to q
154 static Byte *last_modifying_cmd; // last modifying cmd for "."
155 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
156 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
157 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
158 static Byte *modifying_cmds; // cmds that modify text[]
159 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
160 #ifdef CONFIG_FEATURE_VI_READONLY
161 static int vi_readonly, readonly;
162 #endif /* CONFIG_FEATURE_VI_READONLY */
163 #ifdef CONFIG_FEATURE_VI_YANKMARK
164 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
165 static int YDreg, Ureg; // default delete register and orig line for "U"
166 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
167 static Byte *context_start, *context_end;
168 #endif /* CONFIG_FEATURE_VI_YANKMARK */
169 #ifdef CONFIG_FEATURE_VI_SEARCH
170 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
171 #endif /* CONFIG_FEATURE_VI_SEARCH */
174 static void edit_file(Byte *); // edit one file
175 static void do_cmd(Byte); // execute a command
176 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
177 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
178 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
179 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
180 static Byte *next_line(Byte *); // return pointer to next line B-o-l
181 static Byte *end_screen(void); // get pointer to last char on screen
182 static int count_lines(Byte *, Byte *); // count line from start to stop
183 static Byte *find_line(int); // find begining of line #li
184 static Byte *move_to_col(Byte *, int); // move "p" to column l
185 static int isblnk(Byte); // is the char a blank or tab
186 static void dot_left(void); // move dot left- dont leave line
187 static void dot_right(void); // move dot right- dont leave line
188 static void dot_begin(void); // move dot to B-o-l
189 static void dot_end(void); // move dot to E-o-l
190 static void dot_next(void); // move dot to next line B-o-l
191 static void dot_prev(void); // move dot to prev line B-o-l
192 static void dot_scroll(int, int); // move the screen up or down
193 static void dot_skip_over_ws(void); // move dot pat WS
194 static void dot_delete(void); // delete the char at 'dot'
195 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
196 static Byte *new_screen(int, int); // malloc virtual screen memory
197 static Byte *new_text(int); // malloc memory for text[] buffer
198 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
199 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
200 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
201 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
202 static Byte *skip_thing(Byte *, int, int, int); // skip some object
203 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
204 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
205 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
206 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
207 static void show_help(void); // display some help info
208 static void rawmode(void); // set "raw" mode on tty
209 static void cookmode(void); // return to "cooked" mode on tty
210 static int mysleep(int); // sleep for 'h' 1/100 seconds
211 static Byte readit(void); // read (maybe cursor) key from stdin
212 static Byte get_one_char(void); // read 1 char from stdin
213 static int file_size(const Byte *); // what is the byte size of "fn"
214 static int file_insert(Byte *, Byte *, int);
215 static int file_write(Byte *, Byte *, Byte *);
216 static void place_cursor(int, int, int);
217 static void screen_erase(void);
218 static void clear_to_eol(void);
219 static void clear_to_eos(void);
220 static void standout_start(void); // send "start reverse video" sequence
221 static void standout_end(void); // send "end reverse video" sequence
222 static void flash(int); // flash the terminal screen
223 static void show_status_line(void); // put a message on the bottom line
224 static void psb(const char *, ...); // Print Status Buf
225 static void psbs(const char *, ...); // Print Status Buf in standout mode
226 static void ni(Byte *); // display messages
227 static int format_edit_status(void); // format file status on status line
228 static void redraw(int); // force a full screen refresh
229 static void format_line(Byte*, Byte*, int);
230 static void refresh(int); // update the terminal from screen[]
232 static void Indicate_Error(void); // use flash or beep to indicate error
233 #define indicate_error(c) Indicate_Error()
234 static void Hit_Return(void);
236 #ifdef CONFIG_FEATURE_VI_SEARCH
237 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
238 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
239 #endif /* CONFIG_FEATURE_VI_SEARCH */
240 #ifdef CONFIG_FEATURE_VI_COLON
241 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
242 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
243 static void colon(Byte *); // execute the "colon" mode cmds
244 #endif /* CONFIG_FEATURE_VI_COLON */
245 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
246 static void winch_sig(int); // catch window size changes
247 static void suspend_sig(int); // catch ctrl-Z
248 static void catch_sig(int); // catch ctrl-C and alarm time-outs
249 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
250 #ifdef CONFIG_FEATURE_VI_DOT_CMD
251 static void start_new_cmd_q(Byte); // new queue for command
252 static void end_cmd_q(void); // stop saving input chars
253 #else /* CONFIG_FEATURE_VI_DOT_CMD */
255 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
256 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
257 static void window_size_get(int); // find out what size the window is
258 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
259 #ifdef CONFIG_FEATURE_VI_SETOPTS
260 static void showmatching(Byte *); // show the matching pair () [] {}
261 #endif /* CONFIG_FEATURE_VI_SETOPTS */
262 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
263 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
264 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
265 #ifdef CONFIG_FEATURE_VI_YANKMARK
266 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
267 static Byte what_reg(void); // what is letter of current YDreg
268 static void check_context(Byte); // remember context for '' command
269 #endif /* CONFIG_FEATURE_VI_YANKMARK */
270 #ifdef CONFIG_FEATURE_VI_CRASHME
271 static void crash_dummy();
272 static void crash_test();
273 static int crashme = 0;
274 #endif /* CONFIG_FEATURE_VI_CRASHME */
277 static void write1(const char *out)
282 int vi_main(int argc, char **argv)
285 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
287 #ifdef CONFIG_FEATURE_VI_YANKMARK
289 #endif /* CONFIG_FEATURE_VI_YANKMARK */
290 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
293 #ifdef CONFIG_FEATURE_VI_CRASHME
294 (void) srand((long) my_pid);
295 #endif /* CONFIG_FEATURE_VI_CRASHME */
297 status_buffer = (Byte *)STATUS_BUFFER;
298 last_status_cksum = 0;
300 #ifdef CONFIG_FEATURE_VI_READONLY
301 vi_readonly = readonly = FALSE;
302 if (strncmp(argv[0], "view", 4) == 0) {
306 #endif /* CONFIG_FEATURE_VI_READONLY */
307 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
308 #ifdef CONFIG_FEATURE_VI_YANKMARK
309 for (i = 0; i < 28; i++) {
311 } // init the yank regs
312 #endif /* CONFIG_FEATURE_VI_YANKMARK */
313 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
314 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
315 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
317 // 1- process $HOME/.exrc file
318 // 2- process EXINIT variable from environment
319 // 3- process command line args
320 while ((c = getopt(argc, argv, "hCR")) != -1) {
322 #ifdef CONFIG_FEATURE_VI_CRASHME
326 #endif /* CONFIG_FEATURE_VI_CRASHME */
327 #ifdef CONFIG_FEATURE_VI_READONLY
328 case 'R': // Read-only flag
332 #endif /* CONFIG_FEATURE_VI_READONLY */
333 //case 'r': // recover flag- ignore- we don't use tmp file
334 //case 'x': // encryption flag- ignore
335 //case 'c': // execute command first
336 //case 'h': // help -- just use default
343 // The argv array can be used by the ":next" and ":rewind" commands
345 fn_start = optind; // remember first file name for :next and :rew
348 //----- This is the main file handling loop --------------
349 if (optind >= argc) {
350 editing = 1; // 0= exit, 1= one file, 2= multiple files
353 for (; optind < argc; optind++) {
354 editing = 1; // 0=exit, 1=one file, 2+ =many files
356 cfn = (Byte *) bb_xstrdup(argv[optind]);
360 //-----------------------------------------------------------
365 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
366 //----- See what the window size currently is --------------------
367 static inline void window_size_get(int fd)
369 get_terminal_width_height(fd, &columns, &rows);
371 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
373 static void edit_file(Byte * fn)
378 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
380 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
381 #ifdef CONFIG_FEATURE_VI_YANKMARK
382 static Byte *cur_line;
383 #endif /* CONFIG_FEATURE_VI_YANKMARK */
389 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
391 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
392 new_screen(rows, columns); // get memory for virtual screen
394 cnt = file_size(fn); // file size
395 size = 2 * cnt; // 200% of file size
396 new_text(size); // get a text[] buffer
397 screenbegin = dot = end = text;
399 ch= file_insert(fn, text, cnt);
402 (void) char_insert(text, '\n'); // start empty buf with dummy line
405 last_file_modified = -1;
406 #ifdef CONFIG_FEATURE_VI_YANKMARK
407 YDreg = 26; // default Yank/Delete reg
408 Ureg = 27; // hold orig line for "U" cmd
409 for (cnt = 0; cnt < 28; cnt++) {
412 mark[26] = mark[27] = text; // init "previous context"
413 #endif /* CONFIG_FEATURE_VI_YANKMARK */
415 last_forward_char = last_input_char = '\0';
419 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
421 signal(SIGWINCH, winch_sig);
422 signal(SIGTSTP, suspend_sig);
423 sig = setjmp(restart);
425 screenbegin = dot = text;
427 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
430 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
433 offset = 0; // no horizontal offset
435 #ifdef CONFIG_FEATURE_VI_DOT_CMD
436 free(last_modifying_cmd);
438 ioq = ioq_start = last_modifying_cmd = 0;
440 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
441 redraw(FALSE); // dont force every col re-draw
444 //------This is the main Vi cmd handling loop -----------------------
445 while (editing > 0) {
446 #ifdef CONFIG_FEATURE_VI_CRASHME
448 if ((end - text) > 1) {
449 crash_dummy(); // generate a random command
453 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
457 #endif /* CONFIG_FEATURE_VI_CRASHME */
458 last_input_char = c = get_one_char(); // get a cmd from user
459 #ifdef CONFIG_FEATURE_VI_YANKMARK
460 // save a copy of the current line- for the 'U" command
461 if (begin_line(dot) != cur_line) {
462 cur_line = begin_line(dot);
463 text_yank(begin_line(dot), end_line(dot), Ureg);
465 #endif /* CONFIG_FEATURE_VI_YANKMARK */
466 #ifdef CONFIG_FEATURE_VI_DOT_CMD
467 // These are commands that change text[].
468 // Remember the input for the "." command
469 if (!adding2q && ioq_start == 0
470 && strchr((char *) modifying_cmds, c) != NULL) {
473 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
474 do_cmd(c); // execute the user command
476 // poll to see if there is input already waiting. if we are
477 // not able to display output fast enough to keep up, skip
478 // the display update until we catch up with input.
479 if (mysleep(0) == 0) {
480 // no input pending- so update output
484 #ifdef CONFIG_FEATURE_VI_CRASHME
486 crash_test(); // test editor variables
487 #endif /* CONFIG_FEATURE_VI_CRASHME */
489 //-------------------------------------------------------------------
491 place_cursor(rows, 0, FALSE); // go to bottom of screen
492 clear_to_eol(); // Erase to end of line
496 //----- The Colon commands -------------------------------------
497 #ifdef CONFIG_FEATURE_VI_COLON
498 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
503 #ifdef CONFIG_FEATURE_VI_YANKMARK
505 #endif /* CONFIG_FEATURE_VI_YANKMARK */
506 #ifdef CONFIG_FEATURE_VI_SEARCH
507 Byte *pat, buf[BUFSIZ];
508 #endif /* CONFIG_FEATURE_VI_SEARCH */
510 *addr = -1; // assume no addr
511 if (*p == '.') { // the current line
514 *addr = count_lines(text, q);
515 #ifdef CONFIG_FEATURE_VI_YANKMARK
516 } else if (*p == '\'') { // is this a mark addr
520 if (c >= 'a' && c <= 'z') {
524 if (q != NULL) { // is mark valid
525 *addr = count_lines(text, q); // count lines
528 #endif /* CONFIG_FEATURE_VI_YANKMARK */
529 #ifdef CONFIG_FEATURE_VI_SEARCH
530 } else if (*p == '/') { // a search pattern
538 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
541 q = char_search(dot, pat, FORWARD, FULL);
543 *addr = count_lines(text, q);
546 #endif /* CONFIG_FEATURE_VI_SEARCH */
547 } else if (*p == '$') { // the last line in file
549 q = begin_line(end - 1);
550 *addr = count_lines(text, q);
551 } else if (isdigit(*p)) { // specific line number
552 sscanf((char *) p, "%d%n", addr, &st);
554 } else { // I don't reconise this
555 // unrecognised address- assume -1
561 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
563 //----- get the address' i.e., 1,3 'a,'b -----
564 // get FIRST addr, if present
566 p++; // skip over leading spaces
567 if (*p == '%') { // alias for 1,$
570 *e = count_lines(text, end-1);
573 p = get_one_address(p, b);
576 if (*p == ',') { // is there a address separator
580 // get SECOND addr, if present
581 p = get_one_address(p, e);
585 p++; // skip over trailing spaces
589 #ifdef CONFIG_FEATURE_VI_SETOPTS
590 static void setops(const Byte *args, const char *opname, int flg_no,
591 const char *short_opname, int opt)
593 const char *a = (char *) args + flg_no;
594 int l = strlen(opname) - 1; /* opname have + ' ' */
596 if (strncasecmp(a, opname, l) == 0 ||
597 strncasecmp(a, short_opname, 2) == 0) {
606 static void colon(Byte * buf)
608 Byte c, *orig_buf, *buf1, *q, *r;
609 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
610 int i, l, li, ch, st, b, e;
611 int useforce = FALSE, forced = FALSE;
614 // :3154 // if (-e line 3154) goto it else stay put
615 // :4,33w! foo // write a portion of buffer to file "foo"
616 // :w // write all of buffer to current file
618 // :q! // quit- dont care about modified file
619 // :'a,'z!sort -u // filter block through sort
620 // :'f // goto mark "f"
621 // :'fl // list literal the mark "f" line
622 // :.r bar // read file "bar" into buffer before dot
623 // :/123/,/abc/d // delete lines from "123" line to "abc" line
624 // :/xyz/ // goto the "xyz" line
625 // :s/find/replace/ // substitute pattern "find" with "replace"
626 // :!<cmd> // run <cmd> then return
629 if (strlen((char *) buf) <= 0)
632 buf++; // move past the ':'
634 li = st = ch = i = 0;
636 q = text; // assume 1,$ for the range
638 li = count_lines(text, end - 1);
639 fn = cfn; // default to current file
640 memset(cmd, '\0', BUFSIZ); // clear cmd[]
641 memset(args, '\0', BUFSIZ); // clear args[]
643 // look for optional address(es) :. :1 :1,9 :'q,'a :%
644 buf = get_address(buf, &b, &e);
646 // remember orig command line
649 // get the COMMAND into cmd[]
651 while (*buf != '\0') {
659 strcpy((char *) args, (char *) buf);
660 buf1 = (Byte*)last_char_is((char *)cmd, '!');
663 *buf1 = '\0'; // get rid of !
666 // if there is only one addr, then the addr
667 // is the line number of the single line the
668 // user wants. So, reset the end
669 // pointer to point at end of the "b" line
670 q = find_line(b); // what line is #b
675 // we were given two addrs. change the
676 // end pointer to the addr given by user.
677 r = find_line(e); // what line is #e
681 // ------------ now look for the command ------------
682 i = strlen((char *) cmd);
683 if (i == 0) { // :123CR goto line #123
685 dot = find_line(b); // what line is #b
688 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
689 // :!ls run the <cmd>
690 (void) alarm(0); // wait for input- no alarms
691 place_cursor(rows - 1, 0, FALSE); // go to Status line
692 clear_to_eol(); // clear the line
694 system((char*)(orig_buf+1)); // run the cmd
696 Hit_Return(); // let user see results
697 (void) alarm(3); // done waiting for input
698 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
699 if (b < 0) { // no addr given- use defaults
700 b = e = count_lines(text, dot);
703 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
704 if (b < 0) { // no addr given- use defaults
705 q = begin_line(dot); // assume .,. for the range
708 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
710 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
713 // don't edit, if the current file has been modified
714 if (file_modified && ! useforce) {
715 psbs("No write since last change (:edit! overrides)");
718 if (strlen((char*)args) > 0) {
719 // the user supplied a file name
721 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
722 // no user supplied name- use the current filename
726 // no user file name, no current name- punt
727 psbs("No current filename");
731 // see if file exists- if not, its just a new file request
732 if ((sr=stat((char*)fn, &st_buf)) < 0) {
733 // This is just a request for a new file creation.
734 // The file_insert below will fail but we get
735 // an empty buffer with a file name. Then the "write"
736 // command can do the create.
738 if ((st_buf.st_mode & (S_IFREG)) == 0) {
739 // This is not a regular file
740 psbs("\"%s\" is not a regular file", fn);
743 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
744 // dont have any read permissions
745 psbs("\"%s\" is not readable", fn);
750 // There is a read-able regular file
751 // make this the current file
752 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
753 free(cfn); // free the old name
754 cfn = q; // remember new cfn
757 // delete all the contents of text[]
758 new_text(2 * file_size(fn));
759 screenbegin = dot = end = text;
762 ch = file_insert(fn, text, file_size(fn));
765 // start empty buf with dummy line
766 (void) char_insert(text, '\n');
770 last_file_modified = -1;
771 #ifdef CONFIG_FEATURE_VI_YANKMARK
772 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
773 free(reg[Ureg]); // free orig line reg- for 'U'
776 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
777 free(reg[YDreg]); // free default yank/delete register
780 for (li = 0; li < 28; li++) {
783 #endif /* CONFIG_FEATURE_VI_YANKMARK */
784 // how many lines in text[]?
785 li = count_lines(text, end - 1);
787 #ifdef CONFIG_FEATURE_VI_READONLY
789 #endif /* CONFIG_FEATURE_VI_READONLY */
791 (sr < 0 ? " [New file]" : ""),
792 #ifdef CONFIG_FEATURE_VI_READONLY
793 ((vi_readonly || readonly) ? " [Read only]" : ""),
794 #endif /* CONFIG_FEATURE_VI_READONLY */
796 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
797 if (b != -1 || e != -1) {
798 ni((Byte *) "No address allowed on this command");
801 if (strlen((char *) args) > 0) {
802 // user wants a new filename
804 cfn = (Byte *) bb_xstrdup((char *) args);
806 // user wants file status info
807 last_status_cksum = 0; // force status update
809 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
810 // print out values of all features
811 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
812 clear_to_eol(); // clear the line
817 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
818 if (b < 0) { // no addr given- use defaults
819 q = begin_line(dot); // assume .,. for the range
822 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
823 clear_to_eol(); // clear the line
825 for (; q <= r; q++) {
829 c_is_no_print = c > 127 && !Isprint(c);
836 } else if (c < ' ' || c == 127) {
847 #ifdef CONFIG_FEATURE_VI_SET
849 #endif /* CONFIG_FEATURE_VI_SET */
851 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
852 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
854 // force end of argv list
861 // don't exit if the file been modified
863 psbs("No write since last change (:%s! overrides)",
864 (*cmd == 'q' ? "quit" : "next"));
867 // are there other file to edit
868 if (*cmd == 'q' && optind < save_argc - 1) {
869 psbs("%d more file to edit", (save_argc - optind - 1));
872 if (*cmd == 'n' && optind >= save_argc - 1) {
873 psbs("No more files to edit");
877 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
879 if (strlen((char *) fn) <= 0) {
880 psbs("No filename given");
883 if (b < 0) { // no addr given- use defaults
884 q = begin_line(dot); // assume "dot"
886 // read after current line- unless user said ":0r foo"
889 #ifdef CONFIG_FEATURE_VI_READONLY
890 l= readonly; // remember current files' status
892 ch = file_insert(fn, q, file_size(fn));
893 #ifdef CONFIG_FEATURE_VI_READONLY
897 goto vc1; // nothing was inserted
898 // how many lines in text[]?
899 li = count_lines(q, q + ch - 1);
901 #ifdef CONFIG_FEATURE_VI_READONLY
903 #endif /* CONFIG_FEATURE_VI_READONLY */
905 #ifdef CONFIG_FEATURE_VI_READONLY
906 ((vi_readonly || readonly) ? " [Read only]" : ""),
907 #endif /* CONFIG_FEATURE_VI_READONLY */
910 // if the insert is before "dot" then we need to update
915 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
916 if (file_modified && ! useforce) {
917 psbs("No write since last change (:rewind! overrides)");
919 // reset the filenames to edit
920 optind = fn_start - 1;
923 #ifdef CONFIG_FEATURE_VI_SET
924 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
925 i = 0; // offset into args
926 if (strlen((char *) args) == 0) {
927 // print out values of all options
928 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
929 clear_to_eol(); // clear the line
930 printf("----------------------------------------\r\n");
931 #ifdef CONFIG_FEATURE_VI_SETOPTS
934 printf("autoindent ");
940 printf("ignorecase ");
943 printf("showmatch ");
944 printf("tabstop=%d ", tabstop);
945 #endif /* CONFIG_FEATURE_VI_SETOPTS */
949 if (strncasecmp((char *) args, "no", 2) == 0)
950 i = 2; // ":set noautoindent"
951 #ifdef CONFIG_FEATURE_VI_SETOPTS
952 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
953 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
954 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
955 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
956 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
957 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
958 if (ch > 0 && ch < columns - 1)
961 #endif /* CONFIG_FEATURE_VI_SETOPTS */
962 #endif /* CONFIG_FEATURE_VI_SET */
963 #ifdef CONFIG_FEATURE_VI_SEARCH
964 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
968 // F points to the "find" pattern
969 // R points to the "replace" pattern
970 // replace the cmd line delimiters "/" with NULLs
971 gflag = 0; // global replace flag
972 c = orig_buf[1]; // what is the delimiter
973 F = orig_buf + 2; // start of "find"
974 R = (Byte *) strchr((char *) F, c); // middle delimiter
975 if (!R) goto colon_s_fail;
976 *R++ = '\0'; // terminate "find"
977 buf1 = (Byte *) strchr((char *) R, c);
978 if (!buf1) goto colon_s_fail;
979 *buf1++ = '\0'; // terminate "replace"
980 if (*buf1 == 'g') { // :s/foo/bar/g
982 gflag++; // turn on gflag
985 if (b < 0) { // maybe :s/foo/bar/
986 q = begin_line(dot); // start with cur line
987 b = count_lines(text, q); // cur line number
990 e = b; // maybe :.s/foo/bar/
991 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
992 ls = q; // orig line start
994 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
996 // we found the "find" pattern- delete it
997 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
998 // inset the "replace" patern
999 (void) string_insert(buf1, R); // insert the string
1000 // check for "global" :s/foo/bar/g
1002 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1003 q = buf1 + strlen((char *) R);
1004 goto vc4; // don't let q move past cur line
1010 #endif /* CONFIG_FEATURE_VI_SEARCH */
1011 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1012 psb("%s", vi_Version);
1013 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
1014 || strncasecmp((char *) cmd, "wq", i) == 0
1015 || strncasecmp((char *) cmd, "wn", i) == 0
1016 || strncasecmp((char *) cmd, "x", i) == 0) {
1017 // is there a file name to write to?
1018 if (strlen((char *) args) > 0) {
1021 #ifdef CONFIG_FEATURE_VI_READONLY
1022 if ((vi_readonly || readonly) && ! useforce) {
1023 psbs("\"%s\" File is read only", fn);
1026 #endif /* CONFIG_FEATURE_VI_READONLY */
1027 // how many lines in text[]?
1028 li = count_lines(q, r);
1030 // see if file exists- if not, its just a new file request
1032 // if "fn" is not write-able, chmod u+w
1033 // sprintf(syscmd, "chmod u+w %s", fn);
1037 l = file_write(fn, q, r);
1038 if (useforce && forced) {
1040 // sprintf(syscmd, "chmod u-w %s", fn);
1046 psbs("Write error: %s", strerror(errno));
1048 psb("\"%s\" %dL, %dC", fn, li, l);
1049 if (q == text && r == end - 1 && l == ch) {
1051 last_file_modified = -1;
1053 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1054 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1059 #ifdef CONFIG_FEATURE_VI_READONLY
1061 #endif /* CONFIG_FEATURE_VI_READONLY */
1062 #ifdef CONFIG_FEATURE_VI_YANKMARK
1063 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1064 if (b < 0) { // no addr given- use defaults
1065 q = begin_line(dot); // assume .,. for the range
1068 text_yank(q, r, YDreg);
1069 li = count_lines(q, r);
1070 psb("Yank %d lines (%d chars) into [%c]",
1071 li, strlen((char *) reg[YDreg]), what_reg());
1072 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1078 dot = bound_dot(dot); // make sure "dot" is valid
1080 #ifdef CONFIG_FEATURE_VI_SEARCH
1082 psb(":s expression missing delimiters");
1086 #endif /* CONFIG_FEATURE_VI_COLON */
1088 static void Hit_Return(void)
1092 standout_start(); // start reverse video
1093 write1("[Hit return to continue]");
1094 standout_end(); // end reverse video
1095 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1097 redraw(TRUE); // force redraw all
1100 //----- Synchronize the cursor to Dot --------------------------
1101 static void sync_cursor(Byte * d, int *row, int *col)
1103 Byte *beg_cur, *end_cur; // begin and end of "d" line
1104 Byte *beg_scr, *end_scr; // begin and end of screen
1108 beg_cur = begin_line(d); // first char of cur line
1109 end_cur = end_line(d); // last char of cur line
1111 beg_scr = end_scr = screenbegin; // first char of screen
1112 end_scr = end_screen(); // last char of screen
1114 if (beg_cur < screenbegin) {
1115 // "d" is before top line on screen
1116 // how many lines do we have to move
1117 cnt = count_lines(beg_cur, screenbegin);
1119 screenbegin = beg_cur;
1120 if (cnt > (rows - 1) / 2) {
1121 // we moved too many lines. put "dot" in middle of screen
1122 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1123 screenbegin = prev_line(screenbegin);
1126 } else if (beg_cur > end_scr) {
1127 // "d" is after bottom line on screen
1128 // how many lines do we have to move
1129 cnt = count_lines(end_scr, beg_cur);
1130 if (cnt > (rows - 1) / 2)
1131 goto sc1; // too many lines
1132 for (ro = 0; ro < cnt - 1; ro++) {
1133 // move screen begin the same amount
1134 screenbegin = next_line(screenbegin);
1135 // now, move the end of screen
1136 end_scr = next_line(end_scr);
1137 end_scr = end_line(end_scr);
1140 // "d" is on screen- find out which row
1142 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1148 // find out what col "d" is on
1150 do { // drive "co" to correct column
1151 if (*tp == '\n' || *tp == '\0')
1155 co += ((tabstop - 1) - (co % tabstop));
1156 } else if (*tp < ' ' || *tp == 127) {
1157 co++; // display as ^X, use 2 columns
1159 } while (tp++ < d && ++co);
1161 // "co" is the column where "dot" is.
1162 // The screen has "columns" columns.
1163 // The currently displayed columns are 0+offset -- columns+ofset
1164 // |-------------------------------------------------------------|
1166 // offset | |------- columns ----------------|
1168 // If "co" is already in this range then we do not have to adjust offset
1169 // but, we do have to subtract the "offset" bias from "co".
1170 // If "co" is outside this range then we have to change "offset".
1171 // If the first char of a line is a tab the cursor will try to stay
1172 // in column 7, but we have to set offset to 0.
1174 if (co < 0 + offset) {
1177 if (co >= columns + offset) {
1178 offset = co - columns + 1;
1180 // if the first char of the line is a tab, and "dot" is sitting on it
1181 // force offset to 0.
1182 if (d == beg_cur && *d == '\t') {
1191 //----- Text Movement Routines ---------------------------------
1192 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1194 while (p > text && p[-1] != '\n')
1195 p--; // go to cur line B-o-l
1199 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1201 while (p < end - 1 && *p != '\n')
1202 p++; // go to cur line E-o-l
1206 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1208 while (p < end - 1 && *p != '\n')
1209 p++; // go to cur line E-o-l
1210 // Try to stay off of the Newline
1211 if (*p == '\n' && (p - begin_line(p)) > 0)
1216 static Byte *prev_line(Byte * p) // return pointer first char prev line
1218 p = begin_line(p); // goto begining of cur line
1219 if (p[-1] == '\n' && p > text)
1220 p--; // step to prev line
1221 p = begin_line(p); // goto begining of prev line
1225 static Byte *next_line(Byte * p) // return pointer first char next line
1228 if (*p == '\n' && p < end - 1)
1229 p++; // step to next line
1233 //----- Text Information Routines ------------------------------
1234 static Byte *end_screen(void)
1239 // find new bottom line
1241 for (cnt = 0; cnt < rows - 2; cnt++)
1247 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1252 if (stop < start) { // start and stop are backwards- reverse them
1258 stop = end_line(stop); // get to end of this line
1259 for (q = start; q <= stop && q <= end - 1; q++) {
1266 static Byte *find_line(int li) // find begining of line #li
1270 for (q = text; li > 1; li--) {
1276 //----- Dot Movement Routines ----------------------------------
1277 static void dot_left(void)
1279 if (dot > text && dot[-1] != '\n')
1283 static void dot_right(void)
1285 if (dot < end - 1 && *dot != '\n')
1289 static void dot_begin(void)
1291 dot = begin_line(dot); // return pointer to first char cur line
1294 static void dot_end(void)
1296 dot = end_line(dot); // return pointer to last char cur line
1299 static Byte *move_to_col(Byte * p, int l)
1306 if (*p == '\n' || *p == '\0')
1310 co += ((tabstop - 1) - (co % tabstop));
1311 } else if (*p < ' ' || *p == 127) {
1312 co++; // display as ^X, use 2 columns
1314 } while (++co <= l && p++ < end);
1318 static void dot_next(void)
1320 dot = next_line(dot);
1323 static void dot_prev(void)
1325 dot = prev_line(dot);
1328 static void dot_scroll(int cnt, int dir)
1332 for (; cnt > 0; cnt--) {
1335 // ctrl-Y scroll up one line
1336 screenbegin = prev_line(screenbegin);
1339 // ctrl-E scroll down one line
1340 screenbegin = next_line(screenbegin);
1343 // make sure "dot" stays on the screen so we dont scroll off
1344 if (dot < screenbegin)
1346 q = end_screen(); // find new bottom line
1348 dot = begin_line(q); // is dot is below bottom line?
1352 static void dot_skip_over_ws(void)
1355 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1359 static void dot_delete(void) // delete the char at 'dot'
1361 (void) text_hole_delete(dot, dot);
1364 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1366 if (p >= end && end > text) {
1368 indicate_error('1');
1372 indicate_error('2');
1377 //----- Helper Utility Routines --------------------------------
1379 //----------------------------------------------------------------
1380 //----- Char Routines --------------------------------------------
1381 /* Chars that are part of a word-
1382 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1383 * Chars that are Not part of a word (stoppers)
1384 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1385 * Chars that are WhiteSpace
1386 * TAB NEWLINE VT FF RETURN SPACE
1387 * DO NOT COUNT NEWLINE AS WHITESPACE
1390 static Byte *new_screen(int ro, int co)
1395 screensize = ro * co + 8;
1396 screen = (Byte *) xmalloc(screensize);
1397 // initialize the new screen. assume this will be a empty file.
1399 // non-existent text[] lines start with a tilde (~).
1400 for (li = 1; li < ro - 1; li++) {
1401 screen[(li * co) + 0] = '~';
1406 static Byte *new_text(int size)
1409 size = 10240; // have a minimum size for new files
1411 text = (Byte *) xmalloc(size + 8);
1412 memset(text, '\0', size); // clear new text[]
1413 //text += 4; // leave some room for "oops"
1414 textend = text + size - 1;
1415 //textend -= 4; // leave some root for "oops"
1419 #ifdef CONFIG_FEATURE_VI_SEARCH
1420 static int mycmp(Byte * s1, Byte * s2, int len)
1424 i = strncmp((char *) s1, (char *) s2, len);
1425 #ifdef CONFIG_FEATURE_VI_SETOPTS
1427 i = strncasecmp((char *) s1, (char *) s2, len);
1429 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1433 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1435 #ifndef REGEX_SEARCH
1439 len = strlen((char *) pat);
1440 if (dir == FORWARD) {
1441 stop = end - 1; // assume range is p - end-1
1442 if (range == LIMITED)
1443 stop = next_line(p); // range is to next line
1444 for (start = p; start < stop; start++) {
1445 if (mycmp(start, pat, len) == 0) {
1449 } else if (dir == BACK) {
1450 stop = text; // assume range is text - p
1451 if (range == LIMITED)
1452 stop = prev_line(p); // range is to prev line
1453 for (start = p - len; start >= stop; start--) {
1454 if (mycmp(start, pat, len) == 0) {
1459 // pattern not found
1461 #else /*REGEX_SEARCH */
1463 struct re_pattern_buffer preg;
1467 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1473 // assume a LIMITED forward search
1481 // count the number of chars to search over, forward or backward
1485 // RANGE could be negative if we are searching backwards
1488 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1490 // The pattern was not compiled
1491 psbs("bad search pattern: \"%s\": %s", pat, q);
1492 i = 0; // return p if pattern not compiled
1502 // search for the compiled pattern, preg, in p[]
1503 // range < 0- search backward
1504 // range > 0- search forward
1506 // re_search() < 0 not found or error
1507 // re_search() > 0 index of found pattern
1508 // struct pattern char int int int struct reg
1509 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1510 i = re_search(&preg, q, size, 0, range, 0);
1513 i = 0; // return NULL if pattern not found
1516 if (dir == FORWARD) {
1522 #endif /*REGEX_SEARCH */
1524 #endif /* CONFIG_FEATURE_VI_SEARCH */
1526 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1528 if (c == 22) { // Is this an ctrl-V?
1529 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1530 p--; // backup onto ^
1531 refresh(FALSE); // show the ^
1535 file_modified++; // has the file been modified
1536 } else if (c == 27) { // Is this an ESC?
1539 end_cmd_q(); // stop adding to q
1540 last_status_cksum = 0; // force status update
1541 if ((p[-1] != '\n') && (dot>text)) {
1544 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1546 if ((p[-1] != '\n') && (dot>text)) {
1548 p = text_hole_delete(p, p); // shrink buffer 1 char
1551 // insert a char into text[]
1552 Byte *sp; // "save p"
1555 c = '\n'; // translate \r to \n
1556 sp = p; // remember addr of insert
1557 p = stupid_insert(p, c); // insert the char
1558 #ifdef CONFIG_FEATURE_VI_SETOPTS
1559 if (showmatch && strchr(")]}", *sp) != NULL) {
1562 if (autoindent && c == '\n') { // auto indent the new line
1565 q = prev_line(p); // use prev line as templet
1566 for (; isblnk(*q); q++) {
1567 p = stupid_insert(p, *q); // insert the char
1570 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1575 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1577 p = text_hole_make(p, 1);
1580 file_modified++; // has the file been modified
1586 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1588 Byte *save_dot, *p, *q;
1594 if (strchr("cdy><", c)) {
1595 // these cmds operate on whole lines
1596 p = q = begin_line(p);
1597 for (cnt = 1; cnt < cmdcnt; cnt++) {
1601 } else if (strchr("^%$0bBeEft", c)) {
1602 // These cmds operate on char positions
1603 do_cmd(c); // execute movement cmd
1605 } else if (strchr("wW", c)) {
1606 do_cmd(c); // execute movement cmd
1607 // if we are at the next word's first char
1608 // step back one char
1609 // but check the possibilities when it is true
1610 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1611 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1612 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1613 dot--; // move back off of next word
1614 if (dot > text && *dot == '\n')
1615 dot--; // stay off NL
1617 } else if (strchr("H-k{", c)) {
1618 // these operate on multi-lines backwards
1619 q = end_line(dot); // find NL
1620 do_cmd(c); // execute movement cmd
1623 } else if (strchr("L+j}\r\n", c)) {
1624 // these operate on multi-lines forwards
1625 p = begin_line(dot);
1626 do_cmd(c); // execute movement cmd
1627 dot_end(); // find NL
1630 c = 27; // error- return an ESC char
1643 static int st_test(Byte * p, int type, int dir, Byte * tested)
1653 if (type == S_BEFORE_WS) {
1655 test = ((!isspace(c)) || c == '\n');
1657 if (type == S_TO_WS) {
1659 test = ((!isspace(c)) || c == '\n');
1661 if (type == S_OVER_WS) {
1663 test = ((isspace(c)));
1665 if (type == S_END_PUNCT) {
1667 test = ((ispunct(c)));
1669 if (type == S_END_ALNUM) {
1671 test = ((isalnum(c)) || c == '_');
1677 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1681 while (st_test(p, type, dir, &c)) {
1682 // make sure we limit search to correct number of lines
1683 if (c == '\n' && --linecnt < 1)
1685 if (dir >= 0 && p >= end - 1)
1687 if (dir < 0 && p <= text)
1689 p += dir; // move to next char
1694 // find matching char of pair () [] {}
1695 static Byte *find_pair(Byte * p, Byte c)
1702 dir = 1; // assume forward
1726 for (q = p + dir; text <= q && q < end; q += dir) {
1727 // look for match, count levels of pairs (( ))
1729 level++; // increase pair levels
1731 level--; // reduce pair level
1733 break; // found matching pair
1736 q = NULL; // indicate no match
1740 #ifdef CONFIG_FEATURE_VI_SETOPTS
1741 // show the matching char of a pair, () [] {}
1742 static void showmatching(Byte * p)
1746 // we found half of a pair
1747 q = find_pair(p, *p); // get loc of matching char
1749 indicate_error('3'); // no matching char
1751 // "q" now points to matching pair
1752 save_dot = dot; // remember where we are
1753 dot = q; // go to new loc
1754 refresh(FALSE); // let the user see it
1755 (void) mysleep(40); // give user some time
1756 dot = save_dot; // go back to old loc
1760 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1762 // open a hole in text[]
1763 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1772 cnt = end - src; // the rest of buffer
1773 if (memmove(dest, src, cnt) != dest) {
1774 psbs("can't create room for new characters");
1776 memset(p, ' ', size); // clear new hole
1777 end = end + size; // adjust the new END
1778 file_modified++; // has the file been modified
1783 // close a hole in text[]
1784 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1789 // move forwards, from beginning
1793 if (q < p) { // they are backward- swap them
1797 hole_size = q - p + 1;
1799 if (src < text || src > end)
1801 if (dest < text || dest >= end)
1804 goto thd_atend; // just delete the end of the buffer
1805 if (memmove(dest, src, cnt) != dest) {
1806 psbs("can't delete the character");
1809 end = end - hole_size; // adjust the new END
1811 dest = end - 1; // make sure dest in below end-1
1813 dest = end = text; // keep pointers valid
1814 file_modified++; // has the file been modified
1819 // copy text into register, then delete text.
1820 // if dist <= 0, do not include, or go past, a NewLine
1822 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1826 // make sure start <= stop
1828 // they are backwards, reverse them
1834 // we can not cross NL boundaries
1838 // dont go past a NewLine
1839 for (; p + 1 <= stop; p++) {
1841 stop = p; // "stop" just before NewLine
1847 #ifdef CONFIG_FEATURE_VI_YANKMARK
1848 text_yank(start, stop, YDreg);
1849 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1850 if (yf == YANKDEL) {
1851 p = text_hole_delete(start, stop);
1856 static void show_help(void)
1858 puts("These features are available:"
1859 #ifdef CONFIG_FEATURE_VI_SEARCH
1860 "\n\tPattern searches with / and ?"
1861 #endif /* CONFIG_FEATURE_VI_SEARCH */
1862 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1863 "\n\tLast command repeat with \'.\'"
1864 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1865 #ifdef CONFIG_FEATURE_VI_YANKMARK
1866 "\n\tLine marking with 'x"
1867 "\n\tNamed buffers with \"x"
1868 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1869 #ifdef CONFIG_FEATURE_VI_READONLY
1870 "\n\tReadonly if vi is called as \"view\""
1871 "\n\tReadonly with -R command line arg"
1872 #endif /* CONFIG_FEATURE_VI_READONLY */
1873 #ifdef CONFIG_FEATURE_VI_SET
1874 "\n\tSome colon mode commands with \':\'"
1875 #endif /* CONFIG_FEATURE_VI_SET */
1876 #ifdef CONFIG_FEATURE_VI_SETOPTS
1877 "\n\tSettable options with \":set\""
1878 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1879 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1880 "\n\tSignal catching- ^C"
1881 "\n\tJob suspend and resume with ^Z"
1882 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1883 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1884 "\n\tAdapt to window re-sizes"
1885 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1889 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1894 strcpy((char *) buf, ""); // init buf
1895 if (strlen((char *) s) <= 0)
1896 s = (Byte *) "(NULL)";
1897 for (; *s > '\0'; s++) {
1901 c_is_no_print = c > 127 && !Isprint(c);
1902 if (c_is_no_print) {
1903 strcat((char *) buf, SOn);
1906 if (c < ' ' || c == 127) {
1907 strcat((char *) buf, "^");
1914 strcat((char *) buf, (char *) b);
1916 strcat((char *) buf, SOs);
1918 strcat((char *) buf, "$");
1923 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1924 static void start_new_cmd_q(Byte c)
1927 free(last_modifying_cmd);
1928 // get buffer for new cmd
1929 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1930 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1931 // if there is a current cmd count put it in the buffer first
1933 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1934 else // just save char c onto queue
1935 last_modifying_cmd[0] = c;
1939 static void end_cmd_q(void)
1941 #ifdef CONFIG_FEATURE_VI_YANKMARK
1942 YDreg = 26; // go back to default Yank/Delete reg
1943 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1947 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1949 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1950 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1954 i = strlen((char *) s);
1955 p = text_hole_make(p, i);
1956 strncpy((char *) p, (char *) s, i);
1957 for (cnt = 0; *s != '\0'; s++) {
1961 #ifdef CONFIG_FEATURE_VI_YANKMARK
1962 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1963 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1966 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1968 #ifdef CONFIG_FEATURE_VI_YANKMARK
1969 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1974 if (q < p) { // they are backwards- reverse them
1981 free(t); // if already a yank register, free it
1982 t = (Byte *) xmalloc(cnt + 1); // get a new register
1983 memset(t, '\0', cnt + 1); // clear new text[]
1984 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1989 static Byte what_reg(void)
1995 c = 'D'; // default to D-reg
1996 if (0 <= YDreg && YDreg <= 25)
1997 c = 'a' + (Byte) YDreg;
2005 static void check_context(Byte cmd)
2007 // A context is defined to be "modifying text"
2008 // Any modifying command establishes a new context.
2010 if (dot < context_start || dot > context_end) {
2011 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2012 // we are trying to modify text[]- make this the current context
2013 mark[27] = mark[26]; // move cur to prev
2014 mark[26] = dot; // move local to cur
2015 context_start = prev_line(prev_line(dot));
2016 context_end = next_line(next_line(dot));
2017 //loiter= start_loiter= now;
2023 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2027 // the current context is in mark[26]
2028 // the previous context is in mark[27]
2029 // only swap context if other context is valid
2030 if (text <= mark[27] && mark[27] <= end - 1) {
2032 mark[27] = mark[26];
2034 p = mark[26]; // where we are going- previous context
2035 context_start = prev_line(prev_line(prev_line(p)));
2036 context_end = next_line(next_line(next_line(p)));
2040 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2042 static int isblnk(Byte c) // is the char a blank or tab
2044 return (c == ' ' || c == '\t');
2047 //----- Set terminal attributes --------------------------------
2048 static void rawmode(void)
2050 tcgetattr(0, &term_orig);
2051 term_vi = term_orig;
2052 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2053 term_vi.c_iflag &= (~IXON & ~ICRNL);
2054 term_vi.c_oflag &= (~ONLCR);
2055 term_vi.c_cc[VMIN] = 1;
2056 term_vi.c_cc[VTIME] = 0;
2057 erase_char = term_vi.c_cc[VERASE];
2058 tcsetattr(0, TCSANOW, &term_vi);
2061 static void cookmode(void)
2064 tcsetattr(0, TCSANOW, &term_orig);
2067 //----- Come here when we get a window resize signal ---------
2068 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2069 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2071 signal(SIGWINCH, winch_sig);
2072 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2074 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2075 new_screen(rows, columns); // get memory for virtual screen
2076 redraw(TRUE); // re-draw the screen
2079 //----- Come here when we get a continue signal -------------------
2080 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2082 rawmode(); // terminal to "raw"
2083 last_status_cksum = 0; // force status update
2084 redraw(TRUE); // re-draw the screen
2086 signal(SIGTSTP, suspend_sig);
2087 signal(SIGCONT, SIG_DFL);
2088 kill(my_pid, SIGCONT);
2091 //----- Come here when we get a Suspend signal -------------------
2092 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2094 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2095 clear_to_eol(); // Erase to end of line
2096 cookmode(); // terminal to "cooked"
2098 signal(SIGCONT, cont_sig);
2099 signal(SIGTSTP, SIG_DFL);
2100 kill(my_pid, SIGTSTP);
2103 //----- Come here when we get a signal ---------------------------
2104 static void catch_sig(int sig)
2106 signal(SIGINT, catch_sig);
2108 longjmp(restart, sig);
2110 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2112 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2114 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2119 tv.tv_usec = hund * 10000;
2120 select(1, &rfds, NULL, NULL, &tv);
2121 return (FD_ISSET(0, &rfds));
2124 #define readbuffer bb_common_bufsiz1
2126 static int readed_for_parse;
2128 //----- IO Routines --------------------------------------------
2129 static Byte readit(void) // read (maybe cursor) key from stdin
2138 static const struct esc_cmds esccmds[] = {
2139 {"OA", (Byte) VI_K_UP}, // cursor key Up
2140 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2141 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2142 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2143 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2144 {"OF", (Byte) VI_K_END}, // Cursor Key End
2145 {"[A", (Byte) VI_K_UP}, // cursor key Up
2146 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2147 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2148 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2149 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2150 {"[F", (Byte) VI_K_END}, // Cursor Key End
2151 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2152 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2153 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2154 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2155 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2156 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2157 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2158 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2159 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2160 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2161 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2162 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2163 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2164 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2165 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2166 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2167 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2168 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2169 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2170 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2171 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2174 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2176 (void) alarm(0); // turn alarm OFF while we wait for input
2178 n = readed_for_parse;
2179 // get input from User- are there already input chars in Q?
2182 // the Q is empty, wait for a typed char
2183 n = read(0, readbuffer, BUFSIZ - 1);
2186 goto ri0; // interrupted sys call
2189 if (errno == EFAULT)
2191 if (errno == EINVAL)
2199 if (readbuffer[0] == 27) {
2200 // This is an ESC char. Is this Esc sequence?
2201 // Could be bare Esc key. See if there are any
2202 // more chars to read after the ESC. This would
2203 // be a Function or Cursor Key sequence.
2207 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2209 // keep reading while there are input chars and room in buffer
2210 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2211 // read the rest of the ESC string
2212 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2218 readed_for_parse = n;
2221 if(c == 27 && n > 1) {
2222 // Maybe cursor or function key?
2223 const struct esc_cmds *eindex;
2225 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2226 int cnt = strlen(eindex->seq);
2230 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2232 // is a Cursor key- put derived value back into Q
2234 // for squeeze out the ESC sequence
2238 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2239 /* defined ESC sequence not found, set only one ESC */
2245 // remove key sequence from Q
2246 readed_for_parse -= n;
2247 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2248 (void) alarm(3); // we are done waiting for input, turn alarm ON
2252 //----- IO Routines --------------------------------------------
2253 static Byte get_one_char(void)
2257 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2258 // ! adding2q && ioq == 0 read()
2259 // ! adding2q && ioq != 0 *ioq
2260 // adding2q *last_modifying_cmd= read()
2262 // we are not adding to the q.
2263 // but, we may be reading from a q
2265 // there is no current q, read from STDIN
2266 c = readit(); // get the users input
2268 // there is a queue to get chars from first
2271 // the end of the q, read from STDIN
2273 ioq_start = ioq = 0;
2274 c = readit(); // get the users input
2278 // adding STDIN chars to q
2279 c = readit(); // get the users input
2280 if (last_modifying_cmd != 0) {
2281 int len = strlen((char *) last_modifying_cmd);
2282 if (len + 1 >= BUFSIZ) {
2283 psbs("last_modifying_cmd overrun");
2285 // add new char to q
2286 last_modifying_cmd[len] = c;
2290 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2291 c = readit(); // get the users input
2292 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2293 return (c); // return the char, where ever it came from
2296 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2301 static Byte *obufp = NULL;
2303 strcpy((char *) buf, (char *) prompt);
2304 last_status_cksum = 0; // force status update
2305 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2306 clear_to_eol(); // clear the line
2307 write1((char *) prompt); // write out the :, /, or ? prompt
2309 for (i = strlen((char *) buf); i < BUFSIZ;) {
2310 c = get_one_char(); // read user input
2311 if (c == '\n' || c == '\r' || c == 27)
2312 break; // is this end of input
2313 if (c == erase_char || c == 8 || c == 127) {
2314 // user wants to erase prev char
2315 i--; // backup to prev char
2316 buf[i] = '\0'; // erase the char
2317 buf[i + 1] = '\0'; // null terminate buffer
2318 write1("\b \b"); // erase char on screen
2319 if (i <= 0) { // user backs up before b-o-l, exit
2323 buf[i] = c; // save char in buffer
2324 buf[i + 1] = '\0'; // make sure buffer is null terminated
2325 putchar(c); // echo the char back to user
2331 obufp = (Byte *) bb_xstrdup((char *) buf);
2335 static int file_size(const Byte * fn) // what is the byte size of "fn"
2340 if (fn == 0 || strlen((char *)fn) <= 0)
2343 sr = stat((char *) fn, &st_buf); // see if file exists
2345 cnt = (int) st_buf.st_size;
2350 static int file_insert(Byte * fn, Byte * p, int size)
2355 #ifdef CONFIG_FEATURE_VI_READONLY
2357 #endif /* CONFIG_FEATURE_VI_READONLY */
2358 if (fn == 0 || strlen((char*) fn) <= 0) {
2359 psbs("No filename given");
2363 // OK- this is just a no-op
2368 psbs("Trying to insert a negative number (%d) of characters", size);
2371 if (p < text || p > end) {
2372 psbs("Trying to insert file outside of memory");
2376 // see if we can open the file
2377 #ifdef CONFIG_FEATURE_VI_READONLY
2378 if (vi_readonly) goto fi1; // do not try write-mode
2380 fd = open((char *) fn, O_RDWR); // assume read & write
2382 // could not open for writing- maybe file is read only
2383 #ifdef CONFIG_FEATURE_VI_READONLY
2386 fd = open((char *) fn, O_RDONLY); // try read-only
2388 psbs("\"%s\" %s", fn, "could not open file");
2391 #ifdef CONFIG_FEATURE_VI_READONLY
2392 // got the file- read-only
2394 #endif /* CONFIG_FEATURE_VI_READONLY */
2396 p = text_hole_make(p, size);
2397 cnt = read(fd, p, size);
2401 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2402 psbs("could not read file \"%s\"", fn);
2403 } else if (cnt < size) {
2404 // There was a partial read, shrink unused space text[]
2405 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2406 psbs("could not read all of file \"%s\"", fn);
2414 static int file_write(Byte * fn, Byte * first, Byte * last)
2416 int fd, cnt, charcnt;
2419 psbs("No current filename");
2423 // FIXIT- use the correct umask()
2424 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2427 cnt = last - first + 1;
2428 charcnt = write(fd, first, cnt);
2429 if (charcnt == cnt) {
2431 //file_modified= FALSE; // the file has not been modified
2439 //----- Terminal Drawing ---------------------------------------
2440 // The terminal is made up of 'rows' line of 'columns' columns.
2441 // classically this would be 24 x 80.
2442 // screen coordinates
2448 // 23,0 ... 23,79 status line
2451 //----- Move the cursor to row x col (count from 0, not 1) -------
2452 static void place_cursor(int row, int col, int opti)
2456 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2459 // char cm3[BUFSIZ];
2461 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2463 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2465 if (row < 0) row = 0;
2466 if (row >= rows) row = rows - 1;
2467 if (col < 0) col = 0;
2468 if (col >= columns) col = columns - 1;
2470 //----- 1. Try the standard terminal ESC sequence
2471 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2473 if (! opti) goto pc0;
2475 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2476 //----- find the minimum # of chars to move cursor -------------
2477 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2478 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2480 // move to the correct row
2481 while (row < Rrow) {
2482 // the cursor has to move up
2486 while (row > Rrow) {
2487 // the cursor has to move down
2488 strcat(cm2, CMdown);
2492 // now move to the correct column
2493 strcat(cm2, "\r"); // start at col 0
2494 // just send out orignal source char to get to correct place
2495 screenp = &screen[row * columns]; // start of screen line
2496 strncat(cm2, (char* )screenp, col);
2498 //----- 3. Try some other way of moving cursor
2499 //---------------------------------------------
2501 // pick the shortest cursor motion to send out
2503 if (strlen(cm2) < strlen(cm)) {
2505 } /* else if (strlen(cm3) < strlen(cm)) {
2508 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2510 write1(cm); // move the cursor
2513 //----- Erase from cursor to end of line -----------------------
2514 static void clear_to_eol(void)
2516 write1(Ceol); // Erase from cursor to end of line
2519 //----- Erase from cursor to end of screen -----------------------
2520 static void clear_to_eos(void)
2522 write1(Ceos); // Erase from cursor to end of screen
2525 //----- Start standout mode ------------------------------------
2526 static void standout_start(void) // send "start reverse video" sequence
2528 write1(SOs); // Start reverse video mode
2531 //----- End standout mode --------------------------------------
2532 static void standout_end(void) // send "end reverse video" sequence
2534 write1(SOn); // End reverse video mode
2537 //----- Flash the screen --------------------------------------
2538 static void flash(int h)
2540 standout_start(); // send "start reverse video" sequence
2543 standout_end(); // send "end reverse video" sequence
2547 static void Indicate_Error(void)
2549 #ifdef CONFIG_FEATURE_VI_CRASHME
2551 return; // generate a random command
2552 #endif /* CONFIG_FEATURE_VI_CRASHME */
2554 write1(bell); // send out a bell character
2560 //----- Screen[] Routines --------------------------------------
2561 //----- Erase the Screen[] memory ------------------------------
2562 static void screen_erase(void)
2564 memset(screen, ' ', screensize); // clear new screen
2567 static int bufsum(unsigned char *buf, int count)
2570 unsigned char *e = buf + count;
2576 //----- Draw the status line at bottom of the screen -------------
2577 static void show_status_line(void)
2579 int cnt = 0, cksum = 0;
2581 // either we already have an error or status message, or we
2583 if (!have_status_msg) {
2584 cnt = format_edit_status();
2585 cksum = bufsum(status_buffer, cnt);
2587 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2588 last_status_cksum= cksum; // remember if we have seen this line
2589 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2590 write1((char*)status_buffer);
2592 if (have_status_msg) {
2593 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2595 have_status_msg = 0;
2598 have_status_msg = 0;
2600 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2605 //----- format the status buffer, the bottom line of screen ------
2606 // format status buffer, with STANDOUT mode
2607 static void psbs(const char *format, ...)
2611 va_start(args, format);
2612 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2613 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2614 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2617 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2622 // format status buffer
2623 static void psb(const char *format, ...)
2627 va_start(args, format);
2628 vsprintf((char *) status_buffer, format, args);
2631 have_status_msg = 1;
2636 static void ni(Byte * s) // display messages
2640 print_literal(buf, s);
2641 psbs("\'%s\' is not implemented", buf);
2644 static int format_edit_status(void) // show file status on status line
2646 int cur, percent, ret, trunc_at;
2649 // file_modified is now a counter rather than a flag. this
2650 // helps reduce the amount of line counting we need to do.
2651 // (this will cause a mis-reporting of modified status
2652 // once every MAXINT editing operations.)
2654 // it would be nice to do a similar optimization here -- if
2655 // we haven't done a motion that could have changed which line
2656 // we're on, then we shouldn't have to do this count_lines()
2657 cur = count_lines(text, dot);
2659 // reduce counting -- the total lines can't have
2660 // changed if we haven't done any edits.
2661 if (file_modified != last_file_modified) {
2662 tot = cur + count_lines(dot, end - 1) - 1;
2663 last_file_modified = file_modified;
2666 // current line percent
2667 // ------------- ~~ ----------
2670 percent = (100 * cur) / tot;
2676 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2677 columns : STATUS_BUFFER_LEN-1;
2679 ret = snprintf((char *) status_buffer, trunc_at+1,
2680 #ifdef CONFIG_FEATURE_VI_READONLY
2681 "%c %s%s%s %d/%d %d%%",
2683 "%c %s%s %d/%d %d%%",
2685 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2686 (cfn != 0 ? (char *) cfn : "No file"),
2687 #ifdef CONFIG_FEATURE_VI_READONLY
2688 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2690 (file_modified ? " [modified]" : ""),
2693 if (ret >= 0 && ret < trunc_at)
2694 return ret; /* it all fit */
2696 return trunc_at; /* had to truncate */
2699 //----- Force refresh of all Lines -----------------------------
2700 static void redraw(int full_screen)
2702 place_cursor(0, 0, FALSE); // put cursor in correct place
2703 clear_to_eos(); // tel terminal to erase display
2704 screen_erase(); // erase the internal screen buffer
2705 last_status_cksum = 0; // force status update
2706 refresh(full_screen); // this will redraw the entire display
2710 //----- Format a text[] line into a buffer ---------------------
2711 static void format_line(Byte *dest, Byte *src, int li)
2716 for (co= 0; co < MAX_SCR_COLS; co++) {
2717 c= ' '; // assume blank
2718 if (li > 0 && co == 0) {
2719 c = '~'; // not first line, assume Tilde
2721 // are there chars in text[] and have we gone past the end
2722 if (text < end && src < end) {
2727 if (c > 127 && !Isprint(c)) {
2730 if (c < ' ' || c == 127) {
2734 for (; (co % tabstop) != (tabstop - 1); co++) {
2742 c += '@'; // make it visible
2745 // the co++ is done here so that the column will
2746 // not be overwritten when we blank-out the rest of line
2753 //----- Refresh the changed screen lines -----------------------
2754 // Copy the source line from text[] into the buffer and note
2755 // if the current screenline is different from the new buffer.
2756 // If they differ then that line needs redrawing on the terminal.
2758 static void refresh(int full_screen)
2760 static int old_offset;
2762 Byte buf[MAX_SCR_COLS];
2763 Byte *tp, *sp; // pointer into text[] and screen[]
2764 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2765 int last_li= -2; // last line that changed- for optimizing cursor movement
2766 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2768 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2770 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2771 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2772 tp = screenbegin; // index into text[] of top line
2774 // compare text[] to screen[] and mark screen[] lines that need updating
2775 for (li = 0; li < rows - 1; li++) {
2776 int cs, ce; // column start & end
2777 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2778 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2779 // format current text line into buf
2780 format_line(buf, tp, li);
2782 // skip to the end of the current text[] line
2783 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2785 // see if there are any changes between vitual screen and buf
2786 changed = FALSE; // assume no change
2789 sp = &screen[li * columns]; // start of screen line
2791 // force re-draw of every single column from 0 - columns-1
2794 // compare newly formatted buffer with virtual screen
2795 // look forward for first difference between buf and screen
2796 for ( ; cs <= ce; cs++) {
2797 if (buf[cs + offset] != sp[cs]) {
2798 changed = TRUE; // mark for redraw
2803 // look backward for last difference between buf and screen
2804 for ( ; ce >= cs; ce--) {
2805 if (buf[ce + offset] != sp[ce]) {
2806 changed = TRUE; // mark for redraw
2810 // now, cs is index of first diff, and ce is index of last diff
2812 // if horz offset has changed, force a redraw
2813 if (offset != old_offset) {
2818 // make a sanity check of columns indexes
2820 if (ce > columns-1) ce= columns-1;
2821 if (cs > ce) { cs= 0; ce= columns-1; }
2822 // is there a change between vitual screen and buf
2824 // copy changed part of buffer to virtual screen
2825 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2827 // move cursor to column of first change
2828 if (offset != old_offset) {
2829 // opti_cur_move is still too stupid
2830 // to handle offsets correctly
2831 place_cursor(li, cs, FALSE);
2833 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2834 // if this just the next line
2835 // try to optimize cursor movement
2836 // otherwise, use standard ESC sequence
2837 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2839 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2840 place_cursor(li, cs, FALSE); // use standard ESC sequence
2841 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2844 // write line out to terminal
2847 char *out = (char*)sp+cs;
2854 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2856 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2860 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2861 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2864 place_cursor(crow, ccol, FALSE);
2865 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2867 if (offset != old_offset)
2868 old_offset = offset;
2871 //---------------------------------------------------------------------
2872 //----- the Ascii Chart -----------------------------------------------
2874 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2875 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2876 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2877 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2878 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2879 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2880 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2881 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2882 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2883 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2884 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2885 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2886 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2887 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2888 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2889 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2890 //---------------------------------------------------------------------
2892 //----- Execute a Vi Command -----------------------------------
2893 static void do_cmd(Byte c)
2895 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2896 int cnt, i, j, dir, yf;
2898 c1 = c; // quiet the compiler
2899 cnt = yf = dir = 0; // quiet the compiler
2900 p = q = save_dot = msg = buf; // quiet the compiler
2901 memset(buf, '\0', 9); // clear buf
2905 /* if this is a cursor key, skip these checks */
2918 if (cmd_mode == 2) {
2919 // flip-flop Insert/Replace mode
2920 if (c == VI_K_INSERT) goto dc_i;
2921 // we are 'R'eplacing the current *dot with new char
2923 // don't Replace past E-o-l
2924 cmd_mode = 1; // convert to insert
2926 if (1 <= c || Isprint(c)) {
2928 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2929 dot = char_insert(dot, c); // insert new char
2934 if (cmd_mode == 1) {
2935 // hitting "Insert" twice means "R" replace mode
2936 if (c == VI_K_INSERT) goto dc5;
2937 // insert the char c at "dot"
2938 if (1 <= c || Isprint(c)) {
2939 dot = char_insert(dot, c);
2954 #ifdef CONFIG_FEATURE_VI_CRASHME
2955 case 0x14: // dc4 ctrl-T
2956 crashme = (crashme == 0) ? 1 : 0;
2958 #endif /* CONFIG_FEATURE_VI_CRASHME */
2987 //case 'u': // u- FIXME- there is no undo
2989 default: // unrecognised command
2998 end_cmd_q(); // stop adding to q
2999 case 0x00: // nul- ignore
3001 case 2: // ctrl-B scroll up full screen
3002 case VI_K_PAGEUP: // Cursor Key Page Up
3003 dot_scroll(rows - 2, -1);
3005 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3006 case 0x03: // ctrl-C interrupt
3007 longjmp(restart, 1);
3009 case 26: // ctrl-Z suspend
3010 suspend_sig(SIGTSTP);
3012 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3013 case 4: // ctrl-D scroll down half screen
3014 dot_scroll((rows - 2) / 2, 1);
3016 case 5: // ctrl-E scroll down one line
3019 case 6: // ctrl-F scroll down full screen
3020 case VI_K_PAGEDOWN: // Cursor Key Page Down
3021 dot_scroll(rows - 2, 1);
3023 case 7: // ctrl-G show current status
3024 last_status_cksum = 0; // force status update
3026 case 'h': // h- move left
3027 case VI_K_LEFT: // cursor key Left
3028 case 8: // ctrl-H- move left (This may be ERASE char)
3029 case 127: // DEL- move left (This may be ERASE char)
3035 case 10: // Newline ^J
3036 case 'j': // j- goto next line, same col
3037 case VI_K_DOWN: // cursor key Down
3041 dot_next(); // go to next B-o-l
3042 dot = move_to_col(dot, ccol + offset); // try stay in same col
3044 case 12: // ctrl-L force redraw whole screen
3045 case 18: // ctrl-R force redraw
3046 place_cursor(0, 0, FALSE); // put cursor in correct place
3047 clear_to_eos(); // tel terminal to erase display
3049 screen_erase(); // erase the internal screen buffer
3050 last_status_cksum = 0; // force status update
3051 refresh(TRUE); // this will redraw the entire display
3053 case 13: // Carriage Return ^M
3054 case '+': // +- goto next line
3061 case 21: // ctrl-U scroll up half screen
3062 dot_scroll((rows - 2) / 2, -1);
3064 case 25: // ctrl-Y scroll up one line
3070 cmd_mode = 0; // stop insrting
3072 last_status_cksum = 0; // force status update
3074 case ' ': // move right
3075 case 'l': // move right
3076 case VI_K_RIGHT: // Cursor Key Right
3082 #ifdef CONFIG_FEATURE_VI_YANKMARK
3083 case '"': // "- name a register to use for Delete/Yank
3084 c1 = get_one_char();
3092 case '\'': // '- goto a specific mark
3093 c1 = get_one_char();
3099 if (text <= q && q < end) {
3101 dot_begin(); // go to B-o-l
3104 } else if (c1 == '\'') { // goto previous context
3105 dot = swap_context(dot); // swap current and previous context
3106 dot_begin(); // go to B-o-l
3112 case 'm': // m- Mark a line
3113 // this is really stupid. If there are any inserts or deletes
3114 // between text[0] and dot then this mark will not point to the
3115 // correct location! It could be off by many lines!
3116 // Well..., at least its quick and dirty.
3117 c1 = get_one_char();
3121 // remember the line
3122 mark[(int) c1] = dot;
3127 case 'P': // P- Put register before
3128 case 'p': // p- put register after
3131 psbs("Nothing in register %c", what_reg());
3134 // are we putting whole lines or strings
3135 if (strchr((char *) p, '\n') != NULL) {
3137 dot_begin(); // putting lines- Put above
3140 // are we putting after very last line?
3141 if (end_line(dot) == (end - 1)) {
3142 dot = end; // force dot to end of text[]
3144 dot_next(); // next line, then put before
3149 dot_right(); // move to right, can move to NL
3151 dot = string_insert(dot, p); // insert the string
3152 end_cmd_q(); // stop adding to q
3154 case 'U': // U- Undo; replace current line with original version
3155 if (reg[Ureg] != 0) {
3156 p = begin_line(dot);
3158 p = text_hole_delete(p, q); // delete cur line
3159 p = string_insert(p, reg[Ureg]); // insert orig line
3164 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3165 case '$': // $- goto end of line
3166 case VI_K_END: // Cursor Key End
3170 dot = end_line(dot);
3172 case '%': // %- find matching char of pair () [] {}
3173 for (q = dot; q < end && *q != '\n'; q++) {
3174 if (strchr("()[]{}", *q) != NULL) {
3175 // we found half of a pair
3176 p = find_pair(q, *q);
3188 case 'f': // f- forward to a user specified char
3189 last_forward_char = get_one_char(); // get the search char
3191 // dont separate these two commands. 'f' depends on ';'
3193 //**** fall thru to ... ';'
3194 case ';': // ;- look at rest of line for last forward char
3198 if (last_forward_char == 0) break;
3200 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3203 if (*q == last_forward_char)
3206 case '-': // -- goto prev line
3213 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3214 case '.': // .- repeat the last modifying command
3215 // Stuff the last_modifying_cmd back into stdin
3216 // and let it be re-executed.
3217 if (last_modifying_cmd != 0) {
3218 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3221 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3222 #ifdef CONFIG_FEATURE_VI_SEARCH
3223 case '?': // /- search for a pattern
3224 case '/': // /- search for a pattern
3227 q = get_input_line(buf); // get input line- use "status line"
3228 if (strlen((char *) q) == 1)
3229 goto dc3; // if no pat re-use old pat
3230 if (strlen((char *) q) > 1) { // new pat- save it and find
3231 // there is a new pat
3232 free(last_search_pattern);
3233 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3234 goto dc3; // now find the pattern
3236 // user changed mind and erased the "/"- do nothing
3238 case 'N': // N- backward search for last pattern
3242 dir = BACK; // assume BACKWARD search
3244 if (last_search_pattern[0] == '?') {
3248 goto dc4; // now search for pattern
3250 case 'n': // n- repeat search for last pattern
3251 // search rest of text[] starting at next char
3252 // if search fails return orignal "p" not the "p+1" address
3257 if (last_search_pattern == 0) {
3258 msg = (Byte *) "No previous regular expression";
3261 if (last_search_pattern[0] == '/') {
3262 dir = FORWARD; // assume FORWARD search
3265 if (last_search_pattern[0] == '?') {
3270 q = char_search(p, last_search_pattern + 1, dir, FULL);
3272 dot = q; // good search, update "dot"
3276 // no pattern found between "dot" and "end"- continue at top
3281 q = char_search(p, last_search_pattern + 1, dir, FULL);
3282 if (q != NULL) { // found something
3283 dot = q; // found new pattern- goto it
3284 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3286 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3289 msg = (Byte *) "Pattern not found";
3292 if (*msg) psbs("%s", msg);
3294 case '{': // {- move backward paragraph
3295 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3296 if (q != NULL) { // found blank line
3297 dot = next_line(q); // move to next blank line
3300 case '}': // }- move forward paragraph
3301 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3302 if (q != NULL) { // found blank line
3303 dot = next_line(q); // move to next blank line
3306 #endif /* CONFIG_FEATURE_VI_SEARCH */
3307 case '0': // 0- goto begining of line
3317 if (c == '0' && cmdcnt < 1) {
3318 dot_begin(); // this was a standalone zero
3320 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3323 case ':': // :- the colon mode commands
3324 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3325 #ifdef CONFIG_FEATURE_VI_COLON
3326 colon(p); // execute the command
3327 #else /* CONFIG_FEATURE_VI_COLON */
3329 p++; // move past the ':'
3330 cnt = strlen((char *) p);
3333 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3334 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3335 if (file_modified && p[1] != '!') {
3336 psbs("No write since last change (:quit! overrides)");
3340 } else if (strncasecmp((char *) p, "write", cnt) == 0
3341 || strncasecmp((char *) p, "wq", cnt) == 0
3342 || strncasecmp((char *) p, "wn", cnt) == 0
3343 || strncasecmp((char *) p, "x", cnt) == 0) {
3344 cnt = file_write(cfn, text, end - 1);
3347 psbs("Write error: %s", strerror(errno));
3350 last_file_modified = -1;
3351 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3352 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3353 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3357 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3358 last_status_cksum = 0; // force status update
3359 } else if (sscanf((char *) p, "%d", &j) > 0) {
3360 dot = find_line(j); // go to line # j
3362 } else { // unrecognised cmd
3365 #endif /* CONFIG_FEATURE_VI_COLON */
3367 case '<': // <- Left shift something
3368 case '>': // >- Right shift something
3369 cnt = count_lines(text, dot); // remember what line we are on
3370 c1 = get_one_char(); // get the type of thing to delete
3371 find_range(&p, &q, c1);
3372 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3375 i = count_lines(p, q); // # of lines we are shifting
3376 for ( ; i > 0; i--, p = next_line(p)) {
3378 // shift left- remove tab or 8 spaces
3380 // shrink buffer 1 char
3381 (void) text_hole_delete(p, p);
3382 } else if (*p == ' ') {
3383 // we should be calculating columns, not just SPACE
3384 for (j = 0; *p == ' ' && j < tabstop; j++) {
3385 (void) text_hole_delete(p, p);
3388 } else if (c == '>') {
3389 // shift right -- add tab or 8 spaces
3390 (void) char_insert(p, '\t');
3393 dot = find_line(cnt); // what line were we on
3395 end_cmd_q(); // stop adding to q
3397 case 'A': // A- append at e-o-l
3398 dot_end(); // go to e-o-l
3399 //**** fall thru to ... 'a'
3400 case 'a': // a- append after current char
3405 case 'B': // B- back a blank-delimited Word
3406 case 'E': // E- end of a blank-delimited word
3407 case 'W': // W- forward a blank-delimited word
3414 if (c == 'W' || isspace(dot[dir])) {
3415 dot = skip_thing(dot, 1, dir, S_TO_WS);
3416 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3419 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3421 case 'C': // C- Change to e-o-l
3422 case 'D': // D- delete to e-o-l
3424 dot = dollar_line(dot); // move to before NL
3425 // copy text into a register and delete
3426 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3428 goto dc_i; // start inserting
3429 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3431 end_cmd_q(); // stop adding to q
3432 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3434 case 'G': // G- goto to a line number (default= E-O-F)
3435 dot = end - 1; // assume E-O-F
3437 dot = find_line(cmdcnt); // what line is #cmdcnt
3441 case 'H': // H- goto top line on screen
3443 if (cmdcnt > (rows - 1)) {
3444 cmdcnt = (rows - 1);
3451 case 'I': // I- insert before first non-blank
3454 //**** fall thru to ... 'i'
3455 case 'i': // i- insert before current char
3456 case VI_K_INSERT: // Cursor Key Insert
3458 cmd_mode = 1; // start insrting
3460 case 'J': // J- join current and next lines together
3464 dot_end(); // move to NL
3465 if (dot < end - 1) { // make sure not last char in text[]
3466 *dot++ = ' '; // replace NL with space
3468 while (isblnk(*dot)) { // delete leading WS
3472 end_cmd_q(); // stop adding to q
3474 case 'L': // L- goto bottom line on screen
3476 if (cmdcnt > (rows - 1)) {
3477 cmdcnt = (rows - 1);
3485 case 'M': // M- goto middle line on screen
3487 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3488 dot = next_line(dot);
3490 case 'O': // O- open a empty line above
3492 p = begin_line(dot);
3493 if (p[-1] == '\n') {
3495 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3497 dot = char_insert(dot, '\n');
3500 dot = char_insert(dot, '\n'); // i\n ESC
3505 case 'R': // R- continuous Replace char
3509 case 'X': // X- delete char before dot
3510 case 'x': // x- delete the current char
3511 case 's': // s- substitute the current char
3518 if (dot[dir] != '\n') {
3520 dot--; // delete prev char
3521 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3524 goto dc_i; // start insrting
3525 end_cmd_q(); // stop adding to q
3527 case 'Z': // Z- if modified, {write}; exit
3528 // ZZ means to save file (if necessary), then exit
3529 c1 = get_one_char();
3534 if (file_modified) {
3535 #ifdef CONFIG_FEATURE_VI_READONLY
3536 if (vi_readonly || readonly) {
3537 psbs("\"%s\" File is read only", cfn);
3540 #endif /* CONFIG_FEATURE_VI_READONLY */
3541 cnt = file_write(cfn, text, end - 1);
3544 psbs("Write error: %s", strerror(errno));
3545 } else if (cnt == (end - 1 - text + 1)) {
3552 case '^': // ^- move to first non-blank on line
3556 case 'b': // b- back a word
3557 case 'e': // e- end of word
3564 if ((dot + dir) < text || (dot + dir) > end - 1)
3567 if (isspace(*dot)) {
3568 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3570 if (isalnum(*dot) || *dot == '_') {
3571 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3572 } else if (ispunct(*dot)) {
3573 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3576 case 'c': // c- change something
3577 case 'd': // d- delete something
3578 #ifdef CONFIG_FEATURE_VI_YANKMARK
3579 case 'y': // y- yank something
3580 case 'Y': // Y- Yank a line
3581 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3582 yf = YANKDEL; // assume either "c" or "d"
3583 #ifdef CONFIG_FEATURE_VI_YANKMARK
3584 if (c == 'y' || c == 'Y')
3586 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3589 c1 = get_one_char(); // get the type of thing to delete
3590 find_range(&p, &q, c1);
3591 if (c1 == 27) { // ESC- user changed mind and wants out
3592 c = c1 = 27; // Escape- do nothing
3593 } else if (strchr("wW", c1)) {
3595 // don't include trailing WS as part of word
3596 while (isblnk(*q)) {
3597 if (q <= text || q[-1] == '\n')
3602 dot = yank_delete(p, q, 0, yf); // delete word
3603 } else if (strchr("^0bBeEft$", c1)) {
3604 // single line copy text into a register and delete
3605 dot = yank_delete(p, q, 0, yf); // delete word
3606 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3607 // multiple line copy text into a register and delete
3608 dot = yank_delete(p, q, 1, yf); // delete lines
3610 dot = char_insert(dot, '\n');
3611 // on the last line of file don't move to prev line
3612 if (dot != (end-1)) {
3615 } else if (c == 'd') {
3620 // could not recognize object
3621 c = c1 = 27; // error-
3625 // if CHANGING, not deleting, start inserting after the delete
3627 strcpy((char *) buf, "Change");
3628 goto dc_i; // start inserting
3631 strcpy((char *) buf, "Delete");
3633 #ifdef CONFIG_FEATURE_VI_YANKMARK
3634 if (c == 'y' || c == 'Y') {
3635 strcpy((char *) buf, "Yank");
3638 q = p + strlen((char *) p);
3639 for (cnt = 0; p <= q; p++) {
3643 psb("%s %d lines (%d chars) using [%c]",
3644 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3645 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3646 end_cmd_q(); // stop adding to q
3649 case 'k': // k- goto prev line, same col
3650 case VI_K_UP: // cursor key Up
3655 dot = move_to_col(dot, ccol + offset); // try stay in same col
3657 case 'r': // r- replace the current char with user input
3658 c1 = get_one_char(); // get the replacement char
3661 file_modified++; // has the file been modified
3663 end_cmd_q(); // stop adding to q
3665 case 't': // t- move to char prior to next x
3666 last_forward_char = get_one_char();
3668 if (*dot == last_forward_char)
3670 last_forward_char= 0;
3672 case 'w': // w- forward a word
3676 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3677 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3678 } else if (ispunct(*dot)) { // we are on PUNCT
3679 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3682 dot++; // move over word
3683 if (isspace(*dot)) {
3684 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3688 c1 = get_one_char(); // get the replacement char
3691 cnt = (rows - 2) / 2; // put dot at center
3693 cnt = rows - 2; // put dot at bottom
3694 screenbegin = begin_line(dot); // start dot at top
3695 dot_scroll(cnt, -1);
3697 case '|': // |- move to column "cmdcnt"
3698 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3700 case '~': // ~- flip the case of letters a-z -> A-Z
3704 if (islower(*dot)) {
3705 *dot = toupper(*dot);
3706 file_modified++; // has the file been modified
3707 } else if (isupper(*dot)) {
3708 *dot = tolower(*dot);
3709 file_modified++; // has the file been modified
3712 end_cmd_q(); // stop adding to q
3714 //----- The Cursor and Function Keys -----------------------------
3715 case VI_K_HOME: // Cursor Key Home
3718 // The Fn keys could point to do_macro which could translate them
3719 case VI_K_FUN1: // Function Key F1
3720 case VI_K_FUN2: // Function Key F2
3721 case VI_K_FUN3: // Function Key F3
3722 case VI_K_FUN4: // Function Key F4
3723 case VI_K_FUN5: // Function Key F5
3724 case VI_K_FUN6: // Function Key F6
3725 case VI_K_FUN7: // Function Key F7
3726 case VI_K_FUN8: // Function Key F8
3727 case VI_K_FUN9: // Function Key F9
3728 case VI_K_FUN10: // Function Key F10
3729 case VI_K_FUN11: // Function Key F11
3730 case VI_K_FUN12: // Function Key F12
3735 // if text[] just became empty, add back an empty line
3737 (void) char_insert(text, '\n'); // start empty buf with dummy line
3740 // it is OK for dot to exactly equal to end, otherwise check dot validity
3742 dot = bound_dot(dot); // make sure "dot" is valid
3744 #ifdef CONFIG_FEATURE_VI_YANKMARK
3745 check_context(c); // update the current context
3746 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3749 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3750 cnt = dot - begin_line(dot);
3751 // Try to stay off of the Newline
3752 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3756 #ifdef CONFIG_FEATURE_VI_CRASHME
3757 static int totalcmds = 0;
3758 static int Mp = 85; // Movement command Probability
3759 static int Np = 90; // Non-movement command Probability
3760 static int Dp = 96; // Delete command Probability
3761 static int Ip = 97; // Insert command Probability
3762 static int Yp = 98; // Yank command Probability
3763 static int Pp = 99; // Put command Probability
3764 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3765 char chars[20] = "\t012345 abcdABCD-=.$";
3766 char *words[20] = { "this", "is", "a", "test",
3767 "broadcast", "the", "emergency", "of",
3768 "system", "quick", "brown", "fox",
3769 "jumped", "over", "lazy", "dogs",
3770 "back", "January", "Febuary", "March"
3773 "You should have received a copy of the GNU General Public License\n",
3774 "char c, cm, *cmd, *cmd1;\n",
3775 "generate a command by percentages\n",
3776 "Numbers may be typed as a prefix to some commands.\n",
3777 "Quit, discarding changes!\n",
3778 "Forced write, if permission originally not valid.\n",
3779 "In general, any ex or ed command (such as substitute or delete).\n",
3780 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3781 "Please get w/ me and I will go over it with you.\n",
3782 "The following is a list of scheduled, committed changes.\n",
3783 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3784 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3785 "Any question about transactions please contact Sterling Huxley.\n",
3786 "I will try to get back to you by Friday, December 31.\n",
3787 "This Change will be implemented on Friday.\n",
3788 "Let me know if you have problems accessing this;\n",
3789 "Sterling Huxley recently added you to the access list.\n",
3790 "Would you like to go to lunch?\n",
3791 "The last command will be automatically run.\n",
3792 "This is too much english for a computer geek.\n",
3794 char *multilines[20] = {
3795 "You should have received a copy of the GNU General Public License\n",
3796 "char c, cm, *cmd, *cmd1;\n",
3797 "generate a command by percentages\n",
3798 "Numbers may be typed as a prefix to some commands.\n",
3799 "Quit, discarding changes!\n",
3800 "Forced write, if permission originally not valid.\n",
3801 "In general, any ex or ed command (such as substitute or delete).\n",
3802 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3803 "Please get w/ me and I will go over it with you.\n",
3804 "The following is a list of scheduled, committed changes.\n",
3805 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3806 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3807 "Any question about transactions please contact Sterling Huxley.\n",
3808 "I will try to get back to you by Friday, December 31.\n",
3809 "This Change will be implemented on Friday.\n",
3810 "Let me know if you have problems accessing this;\n",
3811 "Sterling Huxley recently added you to the access list.\n",
3812 "Would you like to go to lunch?\n",
3813 "The last command will be automatically run.\n",
3814 "This is too much english for a computer geek.\n",
3817 // create a random command to execute
3818 static void crash_dummy()
3820 static int sleeptime; // how long to pause between commands
3821 char c, cm, *cmd, *cmd1;
3822 int i, cnt, thing, rbi, startrbi, percent;
3824 // "dot" movement commands
3825 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3827 // is there already a command running?
3828 if (readed_for_parse > 0)
3832 sleeptime = 0; // how long to pause between commands
3833 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3834 // generate a command by percentages
3835 percent = (int) lrand48() % 100; // get a number from 0-99
3836 if (percent < Mp) { // Movement commands
3837 // available commands
3840 } else if (percent < Np) { // non-movement commands
3841 cmd = "mz<>\'\""; // available commands
3843 } else if (percent < Dp) { // Delete commands
3844 cmd = "dx"; // available commands
3846 } else if (percent < Ip) { // Inset commands
3847 cmd = "iIaAsrJ"; // available commands
3849 } else if (percent < Yp) { // Yank commands
3850 cmd = "yY"; // available commands
3852 } else if (percent < Pp) { // Put commands
3853 cmd = "pP"; // available commands
3856 // We do not know how to handle this command, try again
3860 // randomly pick one of the available cmds from "cmd[]"
3861 i = (int) lrand48() % strlen(cmd);
3863 if (strchr(":\024", cm))
3864 goto cd0; // dont allow colon or ctrl-T commands
3865 readbuffer[rbi++] = cm; // put cmd into input buffer
3867 // now we have the command-
3868 // there are 1, 2, and multi char commands
3869 // find out which and generate the rest of command as necessary
3870 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3871 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3872 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3873 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3875 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3877 readbuffer[rbi++] = c; // add movement to input buffer
3879 if (strchr("iIaAsc", cm)) { // multi-char commands
3881 // change some thing
3882 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3884 readbuffer[rbi++] = c; // add movement to input buffer
3886 thing = (int) lrand48() % 4; // what thing to insert
3887 cnt = (int) lrand48() % 10; // how many to insert
3888 for (i = 0; i < cnt; i++) {
3889 if (thing == 0) { // insert chars
3890 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3891 } else if (thing == 1) { // insert words
3892 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3893 strcat((char *) readbuffer, " ");
3894 sleeptime = 0; // how fast to type
3895 } else if (thing == 2) { // insert lines
3896 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3897 sleeptime = 0; // how fast to type
3898 } else { // insert multi-lines
3899 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3900 sleeptime = 0; // how fast to type
3903 strcat((char *) readbuffer, "\033");
3905 readed_for_parse = strlen(readbuffer);
3909 (void) mysleep(sleeptime); // sleep 1/100 sec
3912 // test to see if there are any errors
3913 static void crash_test()
3915 static time_t oldtim;
3917 char d[2], msg[BUFSIZ];
3921 strcat((char *) msg, "end<text ");
3923 if (end > textend) {
3924 strcat((char *) msg, "end>textend ");
3927 strcat((char *) msg, "dot<text ");
3930 strcat((char *) msg, "dot>end ");
3932 if (screenbegin < text) {
3933 strcat((char *) msg, "screenbegin<text ");
3935 if (screenbegin > end - 1) {
3936 strcat((char *) msg, "screenbegin>end-1 ");
3939 if (strlen(msg) > 0) {
3941 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3942 totalcmds, last_input_char, msg, SOs, SOn);
3944 while (read(0, d, 1) > 0) {
3945 if (d[0] == '\n' || d[0] == '\r')
3950 tim = (time_t) time((time_t *) 0);
3951 if (tim >= (oldtim + 3)) {
3952 sprintf((char *) status_buffer,
3953 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3954 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3959 #endif /* CONFIG_FEATURE_VI_CRASHME */