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.
10 * To compile for standalone use:
11 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
13 * gcc -Wall -Os -s -DSTANDALONE -DCONFIG_FEATURE_VI_CRASHME -o vi vi.c # include testing features
20 * $HOME/.exrc and ./.exrc
21 * add magic to search /foo.*bar
24 * how about mode lines: vi: set sw=8 ts=8:
25 * if mark[] values were line numbers rather than pointers
26 * it would be easier to change the mark when add/delete lines
27 * More intelligence in refresh()
28 * ":r !cmd" and "!cmd" to filter text through an external command
29 * A true "undo" facility
30 * An "ex" line oriented mode- maybe using "cmdedit"
33 //---- Feature -------------- Bytes to implement
36 #define CONFIG_FEATURE_VI_COLON // 4288
37 #define CONFIG_FEATURE_VI_YANKMARK // 1408
38 #define CONFIG_FEATURE_VI_SEARCH // 1088
39 #define CONFIG_FEATURE_VI_USE_SIGNALS // 1056
40 #define CONFIG_FEATURE_VI_DOT_CMD // 576
41 #define CONFIG_FEATURE_VI_READONLY // 128
42 #define CONFIG_FEATURE_VI_SETOPTS // 576
43 #define CONFIG_FEATURE_VI_SET // 224
44 #define CONFIG_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
45 // To test editor using CRASHME:
47 // To stop testing, wait until all to text[] is deleted, or
48 // Ctrl-Z and kill -9 %1
49 // while in the editor Ctrl-T will toggle the crashme function on and off.
50 //#define CONFIG_FEATURE_VI_CRASHME // randomly pick commands to execute
51 #endif /* STANDALONE */
58 #include <sys/ioctl.h>
60 #include <sys/types.h>
73 #define vi_Version BB_VER " " BB_BT
75 #define vi_Version "standalone"
76 #endif /* STANDALONE */
78 #ifdef CONFIG_LOCALE_SUPPORT
79 #define Isprint(c) isprint((c))
81 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
86 #define FALSE ((int)0)
88 #define MAX_SCR_COLS BUFSIZ
90 // Misc. non-Ascii keys that report an escape sequence
91 #define VI_K_UP 128 // cursor key Up
92 #define VI_K_DOWN 129 // cursor key Down
93 #define VI_K_RIGHT 130 // Cursor Key Right
94 #define VI_K_LEFT 131 // cursor key Left
95 #define VI_K_HOME 132 // Cursor Key Home
96 #define VI_K_END 133 // Cursor Key End
97 #define VI_K_INSERT 134 // Cursor Key Insert
98 #define VI_K_PAGEUP 135 // Cursor Key Page Up
99 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
100 #define VI_K_FUN1 137 // Function Key F1
101 #define VI_K_FUN2 138 // Function Key F2
102 #define VI_K_FUN3 139 // Function Key F3
103 #define VI_K_FUN4 140 // Function Key F4
104 #define VI_K_FUN5 141 // Function Key F5
105 #define VI_K_FUN6 142 // Function Key F6
106 #define VI_K_FUN7 143 // Function Key F7
107 #define VI_K_FUN8 144 // Function Key F8
108 #define VI_K_FUN9 145 // Function Key F9
109 #define VI_K_FUN10 146 // Function Key F10
110 #define VI_K_FUN11 147 // Function Key F11
111 #define VI_K_FUN12 148 // Function Key F12
113 /* vt102 typical ESC sequence */
114 /* terminal standout start/normal ESC sequence */
115 static const char SOs[] = "\033[7m";
116 static const char SOn[] = "\033[0m";
117 /* terminal bell sequence */
118 static const char bell[] = "\007";
119 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
120 static const char Ceol[] = "\033[0K";
121 static const char Ceos [] = "\033[0J";
122 /* Cursor motion arbitrary destination ESC sequence */
123 static const char CMrc[] = "\033[%d;%dH";
124 /* Cursor motion up and down ESC sequence */
125 static const char CMup[] = "\033[A";
126 static const char CMdown[] = "\n";
132 FORWARD = 1, // code depends on "1" for array index
133 BACK = -1, // code depends on "-1" for array index
134 LIMITED = 0, // how much of text[] in char_search
135 FULL = 1, // how much of text[] in char_search
137 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
138 S_TO_WS = 2, // used in skip_thing() for moving "dot"
139 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
140 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
141 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
144 typedef unsigned char Byte;
146 static int vi_setops;
147 #define VI_AUTOINDENT 1
148 #define VI_SHOWMATCH 2
149 #define VI_IGNORECASE 4
150 #define VI_ERR_METHOD 8
151 #define autoindent (vi_setops & VI_AUTOINDENT)
152 #define showmatch (vi_setops & VI_SHOWMATCH )
153 #define ignorecase (vi_setops & VI_IGNORECASE)
154 /* indicate error with beep or flash */
155 #define err_method (vi_setops & VI_ERR_METHOD)
158 static int editing; // >0 while we are editing a file
159 static int cmd_mode; // 0=command 1=insert 2=replace
160 static int file_modified; // buffer contents changed
161 static int last_file_modified = -1;
162 static int fn_start; // index of first cmd line file name
163 static int save_argc; // how many file names on cmd line
164 static int cmdcnt; // repetition count
165 static fd_set rfds; // use select() for small sleeps
166 static struct timeval tv; // use select() for small sleeps
167 static int rows, columns; // the terminal screen is this size
168 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
169 static Byte *status_buffer; // mesages to the user
170 #define STATUS_BUFFER_LEN 200
171 static int have_status_msg; // is default edit status needed?
172 static int last_status_cksum; // hash of current status line
173 static Byte *cfn; // previous, current, and next file name
174 static Byte *text, *end, *textend; // pointers to the user data in memory
175 static Byte *screen; // pointer to the virtual screen buffer
176 static int screensize; // and its size
177 static Byte *screenbegin; // index into text[], of top line on the screen
178 static Byte *dot; // where all the action takes place
180 static struct termios term_orig, term_vi; // remember what the cooked mode was
181 static Byte erase_char; // the users erase character
182 static Byte last_input_char; // last char read from user
183 static Byte last_forward_char; // last char searched for with 'f'
185 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
186 static int last_row; // where the cursor was last moved to
187 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
188 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
189 static jmp_buf restart; // catch_sig()
190 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
191 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
194 #ifdef CONFIG_FEATURE_VI_DOT_CMD
195 static int adding2q; // are we currently adding user input to q
196 static Byte *last_modifying_cmd; // last modifying cmd for "."
197 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
198 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
199 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
200 static Byte *modifying_cmds; // cmds that modify text[]
201 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
202 #ifdef CONFIG_FEATURE_VI_READONLY
203 static int vi_readonly, readonly;
204 #endif /* CONFIG_FEATURE_VI_READONLY */
205 #ifdef CONFIG_FEATURE_VI_YANKMARK
206 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
207 static int YDreg, Ureg; // default delete register and orig line for "U"
208 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
209 static Byte *context_start, *context_end;
210 #endif /* CONFIG_FEATURE_VI_YANKMARK */
211 #ifdef CONFIG_FEATURE_VI_SEARCH
212 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
213 #endif /* CONFIG_FEATURE_VI_SEARCH */
216 static void edit_file(Byte *); // edit one file
217 static void do_cmd(Byte); // execute a command
218 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
219 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
220 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
221 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
222 static Byte *next_line(Byte *); // return pointer to next line B-o-l
223 static Byte *end_screen(void); // get pointer to last char on screen
224 static int count_lines(Byte *, Byte *); // count line from start to stop
225 static Byte *find_line(int); // find begining of line #li
226 static Byte *move_to_col(Byte *, int); // move "p" to column l
227 static int isblnk(Byte); // is the char a blank or tab
228 static void dot_left(void); // move dot left- dont leave line
229 static void dot_right(void); // move dot right- dont leave line
230 static void dot_begin(void); // move dot to B-o-l
231 static void dot_end(void); // move dot to E-o-l
232 static void dot_next(void); // move dot to next line B-o-l
233 static void dot_prev(void); // move dot to prev line B-o-l
234 static void dot_scroll(int, int); // move the screen up or down
235 static void dot_skip_over_ws(void); // move dot pat WS
236 static void dot_delete(void); // delete the char at 'dot'
237 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
238 static Byte *new_screen(int, int); // malloc virtual screen memory
239 static Byte *new_text(int); // malloc memory for text[] buffer
240 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
241 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
242 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
243 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
244 static Byte *skip_thing(Byte *, int, int, int); // skip some object
245 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
246 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
247 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
248 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
249 static void show_help(void); // display some help info
250 static void rawmode(void); // set "raw" mode on tty
251 static void cookmode(void); // return to "cooked" mode on tty
252 static int mysleep(int); // sleep for 'h' 1/100 seconds
253 static Byte readit(void); // read (maybe cursor) key from stdin
254 static Byte get_one_char(void); // read 1 char from stdin
255 static int file_size(const Byte *); // what is the byte size of "fn"
256 static int file_insert(Byte *, Byte *, int);
257 static int file_write(Byte *, Byte *, Byte *);
258 static void place_cursor(int, int, int);
259 static void screen_erase(void);
260 static void clear_to_eol(void);
261 static void clear_to_eos(void);
262 static void standout_start(void); // send "start reverse video" sequence
263 static void standout_end(void); // send "end reverse video" sequence
264 static void flash(int); // flash the terminal screen
265 static void show_status_line(void); // put a message on the bottom line
266 static void psb(const char *, ...); // Print Status Buf
267 static void psbs(const char *, ...); // Print Status Buf in standout mode
268 static void ni(Byte *); // display messages
269 static int format_edit_status(void); // format file status on status line
270 static void redraw(int); // force a full screen refresh
271 static void format_line(Byte*, Byte*, int);
272 static void refresh(int); // update the terminal from screen[]
274 static void Indicate_Error(void); // use flash or beep to indicate error
275 #define indicate_error(c) Indicate_Error()
276 static void Hit_Return(void);
278 #ifdef CONFIG_FEATURE_VI_SEARCH
279 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
280 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
281 #endif /* CONFIG_FEATURE_VI_SEARCH */
282 #ifdef CONFIG_FEATURE_VI_COLON
283 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
284 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
285 static void colon(Byte *); // execute the "colon" mode cmds
286 #endif /* CONFIG_FEATURE_VI_COLON */
287 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
288 static void winch_sig(int); // catch window size changes
289 static void suspend_sig(int); // catch ctrl-Z
290 static void catch_sig(int); // catch ctrl-C and alarm time-outs
291 static void core_sig(int); // catch a core dump signal
292 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
293 #ifdef CONFIG_FEATURE_VI_DOT_CMD
294 static void start_new_cmd_q(Byte); // new queue for command
295 static void end_cmd_q(void); // stop saving input chars
296 #else /* CONFIG_FEATURE_VI_DOT_CMD */
298 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
299 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
300 static void window_size_get(int); // find out what size the window is
301 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
302 #ifdef CONFIG_FEATURE_VI_SETOPTS
303 static void showmatching(Byte *); // show the matching pair () [] {}
304 #endif /* CONFIG_FEATURE_VI_SETOPTS */
305 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_SEARCH) || defined(CONFIG_FEATURE_VI_CRASHME)
306 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
307 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
308 #ifdef CONFIG_FEATURE_VI_YANKMARK
309 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
310 static Byte what_reg(void); // what is letter of current YDreg
311 static void check_context(Byte); // remember context for '' command
312 #endif /* CONFIG_FEATURE_VI_YANKMARK */
313 #ifdef CONFIG_FEATURE_VI_CRASHME
314 static void crash_dummy();
315 static void crash_test();
316 static int crashme = 0;
317 #endif /* CONFIG_FEATURE_VI_CRASHME */
320 static void write1(const char *out)
325 int vi_main(int argc, char **argv)
328 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
330 #ifdef CONFIG_FEATURE_VI_YANKMARK
332 #endif /* CONFIG_FEATURE_VI_YANKMARK */
333 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
336 #ifdef CONFIG_FEATURE_VI_CRASHME
337 (void) srand((long) my_pid);
338 #endif /* CONFIG_FEATURE_VI_CRASHME */
340 status_buffer = (Byte *)STATUS_BUFFER;
341 last_status_cksum = 0;
343 #ifdef CONFIG_FEATURE_VI_READONLY
344 vi_readonly = readonly = FALSE;
345 if (strncmp(argv[0], "view", 4) == 0) {
349 #endif /* CONFIG_FEATURE_VI_READONLY */
350 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
351 #ifdef CONFIG_FEATURE_VI_YANKMARK
352 for (i = 0; i < 28; i++) {
354 } // init the yank regs
355 #endif /* CONFIG_FEATURE_VI_YANKMARK */
356 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
357 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
358 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
360 // 1- process $HOME/.exrc file
361 // 2- process EXINIT variable from environment
362 // 3- process command line args
363 while ((c = getopt(argc, argv, "hCR")) != -1) {
365 #ifdef CONFIG_FEATURE_VI_CRASHME
369 #endif /* CONFIG_FEATURE_VI_CRASHME */
370 #ifdef CONFIG_FEATURE_VI_READONLY
371 case 'R': // Read-only flag
375 #endif /* CONFIG_FEATURE_VI_READONLY */
376 //case 'r': // recover flag- ignore- we don't use tmp file
377 //case 'x': // encryption flag- ignore
378 //case 'c': // execute command first
379 //case 'h': // help -- just use default
386 // The argv array can be used by the ":next" and ":rewind" commands
388 fn_start = optind; // remember first file name for :next and :rew
391 //----- This is the main file handling loop --------------
392 if (optind >= argc) {
393 editing = 1; // 0= exit, 1= one file, 2= multiple files
396 for (; optind < argc; optind++) {
397 editing = 1; // 0=exit, 1=one file, 2+ =many files
399 cfn = (Byte *) bb_xstrdup(argv[optind]);
403 //-----------------------------------------------------------
408 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
409 //----- See what the window size currently is --------------------
410 static inline void window_size_get(int fd)
412 get_terminal_width_height(fd, &columns, &rows);
414 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
416 static void edit_file(Byte * fn)
421 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
423 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
424 #ifdef CONFIG_FEATURE_VI_YANKMARK
425 static Byte *cur_line;
426 #endif /* CONFIG_FEATURE_VI_YANKMARK */
432 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
434 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
435 new_screen(rows, columns); // get memory for virtual screen
437 cnt = file_size(fn); // file size
438 size = 2 * cnt; // 200% of file size
439 new_text(size); // get a text[] buffer
440 screenbegin = dot = end = text;
442 ch= file_insert(fn, text, cnt);
445 (void) char_insert(text, '\n'); // start empty buf with dummy line
448 last_file_modified = -1;
449 #ifdef CONFIG_FEATURE_VI_YANKMARK
450 YDreg = 26; // default Yank/Delete reg
451 Ureg = 27; // hold orig line for "U" cmd
452 for (cnt = 0; cnt < 28; cnt++) {
455 mark[26] = mark[27] = text; // init "previous context"
456 #endif /* CONFIG_FEATURE_VI_YANKMARK */
458 last_forward_char = last_input_char = '\0';
462 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
465 signal(SIGWINCH, winch_sig);
466 signal(SIGTSTP, suspend_sig);
467 sig = setjmp(restart);
469 const char *msg = "";
472 msg = "(window resize)";
482 msg = "(I tried to touch invalid memory)";
486 psbs("-- caught signal %d %s--", sig, msg);
487 screenbegin = dot = text;
489 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
492 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
495 offset = 0; // no horizontal offset
497 #ifdef CONFIG_FEATURE_VI_DOT_CMD
498 free(last_modifying_cmd);
500 ioq = ioq_start = last_modifying_cmd = 0;
502 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
503 redraw(FALSE); // dont force every col re-draw
506 //------This is the main Vi cmd handling loop -----------------------
507 while (editing > 0) {
508 #ifdef CONFIG_FEATURE_VI_CRASHME
510 if ((end - text) > 1) {
511 crash_dummy(); // generate a random command
515 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
519 #endif /* CONFIG_FEATURE_VI_CRASHME */
520 last_input_char = c = get_one_char(); // get a cmd from user
521 #ifdef CONFIG_FEATURE_VI_YANKMARK
522 // save a copy of the current line- for the 'U" command
523 if (begin_line(dot) != cur_line) {
524 cur_line = begin_line(dot);
525 text_yank(begin_line(dot), end_line(dot), Ureg);
527 #endif /* CONFIG_FEATURE_VI_YANKMARK */
528 #ifdef CONFIG_FEATURE_VI_DOT_CMD
529 // These are commands that change text[].
530 // Remember the input for the "." command
531 if (!adding2q && ioq_start == 0
532 && strchr((char *) modifying_cmds, c) != NULL) {
535 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
536 do_cmd(c); // execute the user command
538 // poll to see if there is input already waiting. if we are
539 // not able to display output fast enough to keep up, skip
540 // the display update until we catch up with input.
541 if (mysleep(0) == 0) {
542 // no input pending- so update output
546 #ifdef CONFIG_FEATURE_VI_CRASHME
548 crash_test(); // test editor variables
549 #endif /* CONFIG_FEATURE_VI_CRASHME */
551 //-------------------------------------------------------------------
553 place_cursor(rows, 0, FALSE); // go to bottom of screen
554 clear_to_eol(); // Erase to end of line
558 //----- The Colon commands -------------------------------------
559 #ifdef CONFIG_FEATURE_VI_COLON
560 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
565 #ifdef CONFIG_FEATURE_VI_YANKMARK
567 #endif /* CONFIG_FEATURE_VI_YANKMARK */
568 #ifdef CONFIG_FEATURE_VI_SEARCH
569 Byte *pat, buf[BUFSIZ];
570 #endif /* CONFIG_FEATURE_VI_SEARCH */
572 *addr = -1; // assume no addr
573 if (*p == '.') { // the current line
576 *addr = count_lines(text, q);
577 #ifdef CONFIG_FEATURE_VI_YANKMARK
578 } else if (*p == '\'') { // is this a mark addr
582 if (c >= 'a' && c <= 'z') {
586 if (q != NULL) { // is mark valid
587 *addr = count_lines(text, q); // count lines
590 #endif /* CONFIG_FEATURE_VI_YANKMARK */
591 #ifdef CONFIG_FEATURE_VI_SEARCH
592 } else if (*p == '/') { // a search pattern
600 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
603 q = char_search(dot, pat, FORWARD, FULL);
605 *addr = count_lines(text, q);
608 #endif /* CONFIG_FEATURE_VI_SEARCH */
609 } else if (*p == '$') { // the last line in file
611 q = begin_line(end - 1);
612 *addr = count_lines(text, q);
613 } else if (isdigit(*p)) { // specific line number
614 sscanf((char *) p, "%d%n", addr, &st);
616 } else { // I don't reconise this
617 // unrecognised address- assume -1
623 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
625 //----- get the address' i.e., 1,3 'a,'b -----
626 // get FIRST addr, if present
628 p++; // skip over leading spaces
629 if (*p == '%') { // alias for 1,$
632 *e = count_lines(text, end-1);
635 p = get_one_address(p, b);
638 if (*p == ',') { // is there a address separator
642 // get SECOND addr, if present
643 p = get_one_address(p, e);
647 p++; // skip over trailing spaces
651 #ifdef CONFIG_FEATURE_VI_SETOPTS
652 static void setops(const Byte *args, const char *opname, int flg_no,
653 const char *short_opname, int opt)
655 const char *a = (char *) args + flg_no;
656 int l = strlen(opname) - 1; /* opname have + ' ' */
658 if (strncasecmp(a, opname, l) == 0 ||
659 strncasecmp(a, short_opname, 2) == 0) {
668 static void colon(Byte * buf)
670 Byte c, *orig_buf, *buf1, *q, *r;
671 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
672 int i, l, li, ch, st, b, e;
673 int useforce = FALSE, forced = FALSE;
676 // :3154 // if (-e line 3154) goto it else stay put
677 // :4,33w! foo // write a portion of buffer to file "foo"
678 // :w // write all of buffer to current file
680 // :q! // quit- dont care about modified file
681 // :'a,'z!sort -u // filter block through sort
682 // :'f // goto mark "f"
683 // :'fl // list literal the mark "f" line
684 // :.r bar // read file "bar" into buffer before dot
685 // :/123/,/abc/d // delete lines from "123" line to "abc" line
686 // :/xyz/ // goto the "xyz" line
687 // :s/find/replace/ // substitute pattern "find" with "replace"
688 // :!<cmd> // run <cmd> then return
691 if (strlen((char *) buf) <= 0)
694 buf++; // move past the ':'
696 li = st = ch = i = 0;
698 q = text; // assume 1,$ for the range
700 li = count_lines(text, end - 1);
701 fn = cfn; // default to current file
702 memset(cmd, '\0', BUFSIZ); // clear cmd[]
703 memset(args, '\0', BUFSIZ); // clear args[]
705 // look for optional address(es) :. :1 :1,9 :'q,'a :%
706 buf = get_address(buf, &b, &e);
708 // remember orig command line
711 // get the COMMAND into cmd[]
713 while (*buf != '\0') {
721 strcpy((char *) args, (char *) buf);
722 buf1 = (Byte*)last_char_is((char *)cmd, '!');
725 *buf1 = '\0'; // get rid of !
728 // if there is only one addr, then the addr
729 // is the line number of the single line the
730 // user wants. So, reset the end
731 // pointer to point at end of the "b" line
732 q = find_line(b); // what line is #b
737 // we were given two addrs. change the
738 // end pointer to the addr given by user.
739 r = find_line(e); // what line is #e
743 // ------------ now look for the command ------------
744 i = strlen((char *) cmd);
745 if (i == 0) { // :123CR goto line #123
747 dot = find_line(b); // what line is #b
750 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
751 // :!ls run the <cmd>
752 (void) alarm(0); // wait for input- no alarms
753 place_cursor(rows - 1, 0, FALSE); // go to Status line
754 clear_to_eol(); // clear the line
756 system((char*)(orig_buf+1)); // run the cmd
758 Hit_Return(); // let user see results
759 (void) alarm(3); // done waiting for input
760 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
761 if (b < 0) { // no addr given- use defaults
762 b = e = count_lines(text, dot);
765 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
766 if (b < 0) { // no addr given- use defaults
767 q = begin_line(dot); // assume .,. for the range
770 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
772 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
775 // don't edit, if the current file has been modified
776 if (file_modified && ! useforce) {
777 psbs("No write since last change (:edit! overrides)");
780 if (strlen((char*)args) > 0) {
781 // the user supplied a file name
783 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
784 // no user supplied name- use the current filename
788 // no user file name, no current name- punt
789 psbs("No current filename");
793 // see if file exists- if not, its just a new file request
794 if ((sr=stat((char*)fn, &st_buf)) < 0) {
795 // This is just a request for a new file creation.
796 // The file_insert below will fail but we get
797 // an empty buffer with a file name. Then the "write"
798 // command can do the create.
800 if ((st_buf.st_mode & (S_IFREG)) == 0) {
801 // This is not a regular file
802 psbs("\"%s\" is not a regular file", fn);
805 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
806 // dont have any read permissions
807 psbs("\"%s\" is not readable", fn);
812 // There is a read-able regular file
813 // make this the current file
814 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
815 free(cfn); // free the old name
816 cfn = q; // remember new cfn
819 // delete all the contents of text[]
820 new_text(2 * file_size(fn));
821 screenbegin = dot = end = text;
824 ch = file_insert(fn, text, file_size(fn));
827 // start empty buf with dummy line
828 (void) char_insert(text, '\n');
832 last_file_modified = -1;
833 #ifdef CONFIG_FEATURE_VI_YANKMARK
834 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
835 free(reg[Ureg]); // free orig line reg- for 'U'
838 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
839 free(reg[YDreg]); // free default yank/delete register
842 for (li = 0; li < 28; li++) {
845 #endif /* CONFIG_FEATURE_VI_YANKMARK */
846 // how many lines in text[]?
847 li = count_lines(text, end - 1);
849 #ifdef CONFIG_FEATURE_VI_READONLY
851 #endif /* CONFIG_FEATURE_VI_READONLY */
853 (sr < 0 ? " [New file]" : ""),
854 #ifdef CONFIG_FEATURE_VI_READONLY
855 ((vi_readonly || readonly) ? " [Read only]" : ""),
856 #endif /* CONFIG_FEATURE_VI_READONLY */
858 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
859 if (b != -1 || e != -1) {
860 ni((Byte *) "No address allowed on this command");
863 if (strlen((char *) args) > 0) {
864 // user wants a new filename
866 cfn = (Byte *) bb_xstrdup((char *) args);
868 // user wants file status info
869 last_status_cksum = 0; // force status update
871 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
872 // print out values of all features
873 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
874 clear_to_eol(); // clear the line
879 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
880 if (b < 0) { // no addr given- use defaults
881 q = begin_line(dot); // assume .,. for the range
884 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
885 clear_to_eol(); // clear the line
887 for (; q <= r; q++) {
891 c_is_no_print = c > 127 && !Isprint(c);
898 } else if (c < ' ' || c == 127) {
909 #ifdef CONFIG_FEATURE_VI_SET
911 #endif /* CONFIG_FEATURE_VI_SET */
913 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
914 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
916 // force end of argv list
923 // don't exit if the file been modified
925 psbs("No write since last change (:%s! overrides)",
926 (*cmd == 'q' ? "quit" : "next"));
929 // are there other file to edit
930 if (*cmd == 'q' && optind < save_argc - 1) {
931 psbs("%d more file to edit", (save_argc - optind - 1));
934 if (*cmd == 'n' && optind >= save_argc - 1) {
935 psbs("No more files to edit");
939 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
941 if (strlen((char *) fn) <= 0) {
942 psbs("No filename given");
945 if (b < 0) { // no addr given- use defaults
946 q = begin_line(dot); // assume "dot"
948 // read after current line- unless user said ":0r foo"
951 #ifdef CONFIG_FEATURE_VI_READONLY
952 l= readonly; // remember current files' status
954 ch = file_insert(fn, q, file_size(fn));
955 #ifdef CONFIG_FEATURE_VI_READONLY
959 goto vc1; // nothing was inserted
960 // how many lines in text[]?
961 li = count_lines(q, q + ch - 1);
963 #ifdef CONFIG_FEATURE_VI_READONLY
965 #endif /* CONFIG_FEATURE_VI_READONLY */
967 #ifdef CONFIG_FEATURE_VI_READONLY
968 ((vi_readonly || readonly) ? " [Read only]" : ""),
969 #endif /* CONFIG_FEATURE_VI_READONLY */
972 // if the insert is before "dot" then we need to update
977 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
978 if (file_modified && ! useforce) {
979 psbs("No write since last change (:rewind! overrides)");
981 // reset the filenames to edit
982 optind = fn_start - 1;
985 #ifdef CONFIG_FEATURE_VI_SET
986 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
987 i = 0; // offset into args
988 if (strlen((char *) args) == 0) {
989 // print out values of all options
990 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
991 clear_to_eol(); // clear the line
992 printf("----------------------------------------\r\n");
993 #ifdef CONFIG_FEATURE_VI_SETOPTS
996 printf("autoindent ");
1002 printf("ignorecase ");
1005 printf("showmatch ");
1006 printf("tabstop=%d ", tabstop);
1007 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1011 if (strncasecmp((char *) args, "no", 2) == 0)
1012 i = 2; // ":set noautoindent"
1013 #ifdef CONFIG_FEATURE_VI_SETOPTS
1014 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1015 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1016 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1017 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1018 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1019 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1020 if (ch > 0 && ch < columns - 1)
1023 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1024 #endif /* CONFIG_FEATURE_VI_SET */
1025 #ifdef CONFIG_FEATURE_VI_SEARCH
1026 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1030 // F points to the "find" pattern
1031 // R points to the "replace" pattern
1032 // replace the cmd line delimiters "/" with NULLs
1033 gflag = 0; // global replace flag
1034 c = orig_buf[1]; // what is the delimiter
1035 F = orig_buf + 2; // start of "find"
1036 R = (Byte *) strchr((char *) F, c); // middle delimiter
1037 if (!R) goto colon_s_fail;
1038 *R++ = '\0'; // terminate "find"
1039 buf1 = (Byte *) strchr((char *) R, c);
1040 if (!buf1) goto colon_s_fail;
1041 *buf1++ = '\0'; // terminate "replace"
1042 if (*buf1 == 'g') { // :s/foo/bar/g
1044 gflag++; // turn on gflag
1047 if (b < 0) { // maybe :s/foo/bar/
1048 q = begin_line(dot); // start with cur line
1049 b = count_lines(text, q); // cur line number
1052 e = b; // maybe :.s/foo/bar/
1053 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1054 ls = q; // orig line start
1056 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1058 // we found the "find" pattern- delete it
1059 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1060 // inset the "replace" patern
1061 (void) string_insert(buf1, R); // insert the string
1062 // check for "global" :s/foo/bar/g
1064 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1065 q = buf1 + strlen((char *) R);
1066 goto vc4; // don't let q move past cur line
1072 #endif /* CONFIG_FEATURE_VI_SEARCH */
1073 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1074 psb("%s", vi_Version);
1075 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
1076 || strncasecmp((char *) cmd, "wq", i) == 0
1077 || strncasecmp((char *) cmd, "wn", i) == 0
1078 || strncasecmp((char *) cmd, "x", i) == 0) {
1079 // is there a file name to write to?
1080 if (strlen((char *) args) > 0) {
1083 #ifdef CONFIG_FEATURE_VI_READONLY
1084 if ((vi_readonly || readonly) && ! useforce) {
1085 psbs("\"%s\" File is read only", fn);
1088 #endif /* CONFIG_FEATURE_VI_READONLY */
1089 // how many lines in text[]?
1090 li = count_lines(q, r);
1092 // see if file exists- if not, its just a new file request
1094 // if "fn" is not write-able, chmod u+w
1095 // sprintf(syscmd, "chmod u+w %s", fn);
1099 l = file_write(fn, q, r);
1100 if (useforce && forced) {
1102 // sprintf(syscmd, "chmod u-w %s", fn);
1108 psbs("Write error: %s", strerror(errno));
1110 psb("\"%s\" %dL, %dC", fn, li, l);
1111 if (q == text && r == end - 1 && l == ch) {
1113 last_file_modified = -1;
1115 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1116 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1121 #ifdef CONFIG_FEATURE_VI_READONLY
1123 #endif /* CONFIG_FEATURE_VI_READONLY */
1124 #ifdef CONFIG_FEATURE_VI_YANKMARK
1125 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1126 if (b < 0) { // no addr given- use defaults
1127 q = begin_line(dot); // assume .,. for the range
1130 text_yank(q, r, YDreg);
1131 li = count_lines(q, r);
1132 psb("Yank %d lines (%d chars) into [%c]",
1133 li, strlen((char *) reg[YDreg]), what_reg());
1134 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1140 dot = bound_dot(dot); // make sure "dot" is valid
1142 #ifdef CONFIG_FEATURE_VI_SEARCH
1144 psb(":s expression missing delimiters");
1148 #endif /* CONFIG_FEATURE_VI_COLON */
1150 static void Hit_Return(void)
1154 standout_start(); // start reverse video
1155 write1("[Hit return to continue]");
1156 standout_end(); // end reverse video
1157 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1159 redraw(TRUE); // force redraw all
1162 //----- Synchronize the cursor to Dot --------------------------
1163 static void sync_cursor(Byte * d, int *row, int *col)
1165 Byte *beg_cur, *end_cur; // begin and end of "d" line
1166 Byte *beg_scr, *end_scr; // begin and end of screen
1170 beg_cur = begin_line(d); // first char of cur line
1171 end_cur = end_line(d); // last char of cur line
1173 beg_scr = end_scr = screenbegin; // first char of screen
1174 end_scr = end_screen(); // last char of screen
1176 if (beg_cur < screenbegin) {
1177 // "d" is before top line on screen
1178 // how many lines do we have to move
1179 cnt = count_lines(beg_cur, screenbegin);
1181 screenbegin = beg_cur;
1182 if (cnt > (rows - 1) / 2) {
1183 // we moved too many lines. put "dot" in middle of screen
1184 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1185 screenbegin = prev_line(screenbegin);
1188 } else if (beg_cur > end_scr) {
1189 // "d" is after bottom line on screen
1190 // how many lines do we have to move
1191 cnt = count_lines(end_scr, beg_cur);
1192 if (cnt > (rows - 1) / 2)
1193 goto sc1; // too many lines
1194 for (ro = 0; ro < cnt - 1; ro++) {
1195 // move screen begin the same amount
1196 screenbegin = next_line(screenbegin);
1197 // now, move the end of screen
1198 end_scr = next_line(end_scr);
1199 end_scr = end_line(end_scr);
1202 // "d" is on screen- find out which row
1204 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1210 // find out what col "d" is on
1212 do { // drive "co" to correct column
1213 if (*tp == '\n' || *tp == '\0')
1217 co += ((tabstop - 1) - (co % tabstop));
1218 } else if (*tp < ' ' || *tp == 127) {
1219 co++; // display as ^X, use 2 columns
1221 } while (tp++ < d && ++co);
1223 // "co" is the column where "dot" is.
1224 // The screen has "columns" columns.
1225 // The currently displayed columns are 0+offset -- columns+ofset
1226 // |-------------------------------------------------------------|
1228 // offset | |------- columns ----------------|
1230 // If "co" is already in this range then we do not have to adjust offset
1231 // but, we do have to subtract the "offset" bias from "co".
1232 // If "co" is outside this range then we have to change "offset".
1233 // If the first char of a line is a tab the cursor will try to stay
1234 // in column 7, but we have to set offset to 0.
1236 if (co < 0 + offset) {
1239 if (co >= columns + offset) {
1240 offset = co - columns + 1;
1242 // if the first char of the line is a tab, and "dot" is sitting on it
1243 // force offset to 0.
1244 if (d == beg_cur && *d == '\t') {
1253 //----- Text Movement Routines ---------------------------------
1254 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1256 while (p > text && p[-1] != '\n')
1257 p--; // go to cur line B-o-l
1261 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1263 while (p < end - 1 && *p != '\n')
1264 p++; // go to cur line E-o-l
1268 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1270 while (p < end - 1 && *p != '\n')
1271 p++; // go to cur line E-o-l
1272 // Try to stay off of the Newline
1273 if (*p == '\n' && (p - begin_line(p)) > 0)
1278 static Byte *prev_line(Byte * p) // return pointer first char prev line
1280 p = begin_line(p); // goto begining of cur line
1281 if (p[-1] == '\n' && p > text)
1282 p--; // step to prev line
1283 p = begin_line(p); // goto begining of prev line
1287 static Byte *next_line(Byte * p) // return pointer first char next line
1290 if (*p == '\n' && p < end - 1)
1291 p++; // step to next line
1295 //----- Text Information Routines ------------------------------
1296 static Byte *end_screen(void)
1301 // find new bottom line
1303 for (cnt = 0; cnt < rows - 2; cnt++)
1309 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1314 if (stop < start) { // start and stop are backwards- reverse them
1320 stop = end_line(stop); // get to end of this line
1321 for (q = start; q <= stop && q <= end - 1; q++) {
1328 static Byte *find_line(int li) // find begining of line #li
1332 for (q = text; li > 1; li--) {
1338 //----- Dot Movement Routines ----------------------------------
1339 static void dot_left(void)
1341 if (dot > text && dot[-1] != '\n')
1345 static void dot_right(void)
1347 if (dot < end - 1 && *dot != '\n')
1351 static void dot_begin(void)
1353 dot = begin_line(dot); // return pointer to first char cur line
1356 static void dot_end(void)
1358 dot = end_line(dot); // return pointer to last char cur line
1361 static Byte *move_to_col(Byte * p, int l)
1368 if (*p == '\n' || *p == '\0')
1372 co += ((tabstop - 1) - (co % tabstop));
1373 } else if (*p < ' ' || *p == 127) {
1374 co++; // display as ^X, use 2 columns
1376 } while (++co <= l && p++ < end);
1380 static void dot_next(void)
1382 dot = next_line(dot);
1385 static void dot_prev(void)
1387 dot = prev_line(dot);
1390 static void dot_scroll(int cnt, int dir)
1394 for (; cnt > 0; cnt--) {
1397 // ctrl-Y scroll up one line
1398 screenbegin = prev_line(screenbegin);
1401 // ctrl-E scroll down one line
1402 screenbegin = next_line(screenbegin);
1405 // make sure "dot" stays on the screen so we dont scroll off
1406 if (dot < screenbegin)
1408 q = end_screen(); // find new bottom line
1410 dot = begin_line(q); // is dot is below bottom line?
1414 static void dot_skip_over_ws(void)
1417 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1421 static void dot_delete(void) // delete the char at 'dot'
1423 (void) text_hole_delete(dot, dot);
1426 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1428 if (p >= end && end > text) {
1430 indicate_error('1');
1434 indicate_error('2');
1439 //----- Helper Utility Routines --------------------------------
1441 //----------------------------------------------------------------
1442 //----- Char Routines --------------------------------------------
1443 /* Chars that are part of a word-
1444 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1445 * Chars that are Not part of a word (stoppers)
1446 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1447 * Chars that are WhiteSpace
1448 * TAB NEWLINE VT FF RETURN SPACE
1449 * DO NOT COUNT NEWLINE AS WHITESPACE
1452 static Byte *new_screen(int ro, int co)
1457 screensize = ro * co + 8;
1458 screen = (Byte *) xmalloc(screensize);
1459 // initialize the new screen. assume this will be a empty file.
1461 // non-existent text[] lines start with a tilde (~).
1462 for (li = 1; li < ro - 1; li++) {
1463 screen[(li * co) + 0] = '~';
1468 static Byte *new_text(int size)
1471 size = 10240; // have a minimum size for new files
1473 text = (Byte *) xmalloc(size + 8);
1474 memset(text, '\0', size); // clear new text[]
1475 //text += 4; // leave some room for "oops"
1476 textend = text + size - 1;
1477 //textend -= 4; // leave some root for "oops"
1481 #ifdef CONFIG_FEATURE_VI_SEARCH
1482 static int mycmp(Byte * s1, Byte * s2, int len)
1486 i = strncmp((char *) s1, (char *) s2, len);
1487 #ifdef CONFIG_FEATURE_VI_SETOPTS
1489 i = strncasecmp((char *) s1, (char *) s2, len);
1491 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1495 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1497 #ifndef REGEX_SEARCH
1501 len = strlen((char *) pat);
1502 if (dir == FORWARD) {
1503 stop = end - 1; // assume range is p - end-1
1504 if (range == LIMITED)
1505 stop = next_line(p); // range is to next line
1506 for (start = p; start < stop; start++) {
1507 if (mycmp(start, pat, len) == 0) {
1511 } else if (dir == BACK) {
1512 stop = text; // assume range is text - p
1513 if (range == LIMITED)
1514 stop = prev_line(p); // range is to prev line
1515 for (start = p - len; start >= stop; start--) {
1516 if (mycmp(start, pat, len) == 0) {
1521 // pattern not found
1523 #else /*REGEX_SEARCH */
1525 struct re_pattern_buffer preg;
1529 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1535 // assume a LIMITED forward search
1543 // count the number of chars to search over, forward or backward
1547 // RANGE could be negative if we are searching backwards
1550 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1552 // The pattern was not compiled
1553 psbs("bad search pattern: \"%s\": %s", pat, q);
1554 i = 0; // return p if pattern not compiled
1564 // search for the compiled pattern, preg, in p[]
1565 // range < 0- search backward
1566 // range > 0- search forward
1568 // re_search() < 0 not found or error
1569 // re_search() > 0 index of found pattern
1570 // struct pattern char int int int struct reg
1571 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1572 i = re_search(&preg, q, size, 0, range, 0);
1575 i = 0; // return NULL if pattern not found
1578 if (dir == FORWARD) {
1584 #endif /*REGEX_SEARCH */
1586 #endif /* CONFIG_FEATURE_VI_SEARCH */
1588 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1590 if (c == 22) { // Is this an ctrl-V?
1591 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1592 p--; // backup onto ^
1593 refresh(FALSE); // show the ^
1597 file_modified++; // has the file been modified
1598 } else if (c == 27) { // Is this an ESC?
1601 end_cmd_q(); // stop adding to q
1602 last_status_cksum = 0; // force status update
1603 if ((p[-1] != '\n') && (dot>text)) {
1606 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1608 if ((p[-1] != '\n') && (dot>text)) {
1610 p = text_hole_delete(p, p); // shrink buffer 1 char
1613 // insert a char into text[]
1614 Byte *sp; // "save p"
1617 c = '\n'; // translate \r to \n
1618 sp = p; // remember addr of insert
1619 p = stupid_insert(p, c); // insert the char
1620 #ifdef CONFIG_FEATURE_VI_SETOPTS
1621 if (showmatch && strchr(")]}", *sp) != NULL) {
1624 if (autoindent && c == '\n') { // auto indent the new line
1627 q = prev_line(p); // use prev line as templet
1628 for (; isblnk(*q); q++) {
1629 p = stupid_insert(p, *q); // insert the char
1632 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1637 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1639 p = text_hole_make(p, 1);
1642 file_modified++; // has the file been modified
1648 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1650 Byte *save_dot, *p, *q;
1656 if (strchr("cdy><", c)) {
1657 // these cmds operate on whole lines
1658 p = q = begin_line(p);
1659 for (cnt = 1; cnt < cmdcnt; cnt++) {
1663 } else if (strchr("^%$0bBeEft", c)) {
1664 // These cmds operate on char positions
1665 do_cmd(c); // execute movement cmd
1667 } else if (strchr("wW", c)) {
1668 do_cmd(c); // execute movement cmd
1669 // if we are at the next word's first char
1670 // step back one char
1671 // but check the possibilities when it is true
1672 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1673 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1674 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1675 dot--; // move back off of next word
1676 if (dot > text && *dot == '\n')
1677 dot--; // stay off NL
1679 } else if (strchr("H-k{", c)) {
1680 // these operate on multi-lines backwards
1681 q = end_line(dot); // find NL
1682 do_cmd(c); // execute movement cmd
1685 } else if (strchr("L+j}\r\n", c)) {
1686 // these operate on multi-lines forwards
1687 p = begin_line(dot);
1688 do_cmd(c); // execute movement cmd
1689 dot_end(); // find NL
1692 c = 27; // error- return an ESC char
1705 static int st_test(Byte * p, int type, int dir, Byte * tested)
1715 if (type == S_BEFORE_WS) {
1717 test = ((!isspace(c)) || c == '\n');
1719 if (type == S_TO_WS) {
1721 test = ((!isspace(c)) || c == '\n');
1723 if (type == S_OVER_WS) {
1725 test = ((isspace(c)));
1727 if (type == S_END_PUNCT) {
1729 test = ((ispunct(c)));
1731 if (type == S_END_ALNUM) {
1733 test = ((isalnum(c)) || c == '_');
1739 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1743 while (st_test(p, type, dir, &c)) {
1744 // make sure we limit search to correct number of lines
1745 if (c == '\n' && --linecnt < 1)
1747 if (dir >= 0 && p >= end - 1)
1749 if (dir < 0 && p <= text)
1751 p += dir; // move to next char
1756 // find matching char of pair () [] {}
1757 static Byte *find_pair(Byte * p, Byte c)
1764 dir = 1; // assume forward
1788 for (q = p + dir; text <= q && q < end; q += dir) {
1789 // look for match, count levels of pairs (( ))
1791 level++; // increase pair levels
1793 level--; // reduce pair level
1795 break; // found matching pair
1798 q = NULL; // indicate no match
1802 #ifdef CONFIG_FEATURE_VI_SETOPTS
1803 // show the matching char of a pair, () [] {}
1804 static void showmatching(Byte * p)
1808 // we found half of a pair
1809 q = find_pair(p, *p); // get loc of matching char
1811 indicate_error('3'); // no matching char
1813 // "q" now points to matching pair
1814 save_dot = dot; // remember where we are
1815 dot = q; // go to new loc
1816 refresh(FALSE); // let the user see it
1817 (void) mysleep(40); // give user some time
1818 dot = save_dot; // go back to old loc
1822 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1824 // open a hole in text[]
1825 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1834 cnt = end - src; // the rest of buffer
1835 if (memmove(dest, src, cnt) != dest) {
1836 psbs("can't create room for new characters");
1838 memset(p, ' ', size); // clear new hole
1839 end = end + size; // adjust the new END
1840 file_modified++; // has the file been modified
1845 // close a hole in text[]
1846 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1851 // move forwards, from beginning
1855 if (q < p) { // they are backward- swap them
1859 hole_size = q - p + 1;
1861 if (src < text || src > end)
1863 if (dest < text || dest >= end)
1866 goto thd_atend; // just delete the end of the buffer
1867 if (memmove(dest, src, cnt) != dest) {
1868 psbs("can't delete the character");
1871 end = end - hole_size; // adjust the new END
1873 dest = end - 1; // make sure dest in below end-1
1875 dest = end = text; // keep pointers valid
1876 file_modified++; // has the file been modified
1881 // copy text into register, then delete text.
1882 // if dist <= 0, do not include, or go past, a NewLine
1884 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1888 // make sure start <= stop
1890 // they are backwards, reverse them
1896 // we can not cross NL boundaries
1900 // dont go past a NewLine
1901 for (; p + 1 <= stop; p++) {
1903 stop = p; // "stop" just before NewLine
1909 #ifdef CONFIG_FEATURE_VI_YANKMARK
1910 text_yank(start, stop, YDreg);
1911 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1912 if (yf == YANKDEL) {
1913 p = text_hole_delete(start, stop);
1918 static void show_help(void)
1920 puts("These features are available:"
1921 #ifdef CONFIG_FEATURE_VI_SEARCH
1922 "\n\tPattern searches with / and ?"
1923 #endif /* CONFIG_FEATURE_VI_SEARCH */
1924 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1925 "\n\tLast command repeat with \'.\'"
1926 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1927 #ifdef CONFIG_FEATURE_VI_YANKMARK
1928 "\n\tLine marking with 'x"
1929 "\n\tNamed buffers with \"x"
1930 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1931 #ifdef CONFIG_FEATURE_VI_READONLY
1932 "\n\tReadonly if vi is called as \"view\""
1933 "\n\tReadonly with -R command line arg"
1934 #endif /* CONFIG_FEATURE_VI_READONLY */
1935 #ifdef CONFIG_FEATURE_VI_SET
1936 "\n\tSome colon mode commands with \':\'"
1937 #endif /* CONFIG_FEATURE_VI_SET */
1938 #ifdef CONFIG_FEATURE_VI_SETOPTS
1939 "\n\tSettable options with \":set\""
1940 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1941 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1942 "\n\tSignal catching- ^C"
1943 "\n\tJob suspend and resume with ^Z"
1944 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1945 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1946 "\n\tAdapt to window re-sizes"
1947 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1951 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1956 strcpy((char *) buf, ""); // init buf
1957 if (strlen((char *) s) <= 0)
1958 s = (Byte *) "(NULL)";
1959 for (; *s > '\0'; s++) {
1963 c_is_no_print = c > 127 && !Isprint(c);
1964 if (c_is_no_print) {
1965 strcat((char *) buf, SOn);
1968 if (c < ' ' || c == 127) {
1969 strcat((char *) buf, "^");
1976 strcat((char *) buf, (char *) b);
1978 strcat((char *) buf, SOs);
1980 strcat((char *) buf, "$");
1985 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1986 static void start_new_cmd_q(Byte c)
1989 free(last_modifying_cmd);
1990 // get buffer for new cmd
1991 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1992 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1993 // if there is a current cmd count put it in the buffer first
1995 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1996 else // just save char c onto queue
1997 last_modifying_cmd[0] = c;
2001 static void end_cmd_q(void)
2003 #ifdef CONFIG_FEATURE_VI_YANKMARK
2004 YDreg = 26; // go back to default Yank/Delete reg
2005 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2009 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2011 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_SEARCH) || defined(CONFIG_FEATURE_VI_CRASHME)
2012 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2016 i = strlen((char *) s);
2017 p = text_hole_make(p, i);
2018 strncpy((char *) p, (char *) s, i);
2019 for (cnt = 0; *s != '\0'; s++) {
2023 #ifdef CONFIG_FEATURE_VI_YANKMARK
2024 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2025 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2028 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2030 #ifdef CONFIG_FEATURE_VI_YANKMARK
2031 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2036 if (q < p) { // they are backwards- reverse them
2043 free(t); // if already a yank register, free it
2044 t = (Byte *) xmalloc(cnt + 1); // get a new register
2045 memset(t, '\0', cnt + 1); // clear new text[]
2046 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2051 static Byte what_reg(void)
2057 c = 'D'; // default to D-reg
2058 if (0 <= YDreg && YDreg <= 25)
2059 c = 'a' + (Byte) YDreg;
2067 static void check_context(Byte cmd)
2069 // A context is defined to be "modifying text"
2070 // Any modifying command establishes a new context.
2072 if (dot < context_start || dot > context_end) {
2073 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2074 // we are trying to modify text[]- make this the current context
2075 mark[27] = mark[26]; // move cur to prev
2076 mark[26] = dot; // move local to cur
2077 context_start = prev_line(prev_line(dot));
2078 context_end = next_line(next_line(dot));
2079 //loiter= start_loiter= now;
2085 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2089 // the current context is in mark[26]
2090 // the previous context is in mark[27]
2091 // only swap context if other context is valid
2092 if (text <= mark[27] && mark[27] <= end - 1) {
2094 mark[27] = mark[26];
2096 p = mark[26]; // where we are going- previous context
2097 context_start = prev_line(prev_line(prev_line(p)));
2098 context_end = next_line(next_line(next_line(p)));
2102 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2104 static int isblnk(Byte c) // is the char a blank or tab
2106 return (c == ' ' || c == '\t');
2109 //----- Set terminal attributes --------------------------------
2110 static void rawmode(void)
2112 tcgetattr(0, &term_orig);
2113 term_vi = term_orig;
2114 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2115 term_vi.c_iflag &= (~IXON & ~ICRNL);
2116 term_vi.c_oflag &= (~ONLCR);
2117 term_vi.c_cc[VMIN] = 1;
2118 term_vi.c_cc[VTIME] = 0;
2119 erase_char = term_vi.c_cc[VERASE];
2120 tcsetattr(0, TCSANOW, &term_vi);
2123 static void cookmode(void)
2126 tcsetattr(0, TCSANOW, &term_orig);
2129 //----- Come here when we get a window resize signal ---------
2130 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2131 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2133 signal(SIGWINCH, winch_sig);
2134 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2136 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2137 new_screen(rows, columns); // get memory for virtual screen
2138 redraw(TRUE); // re-draw the screen
2141 //----- Come here when we get a continue signal -------------------
2142 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2144 rawmode(); // terminal to "raw"
2145 last_status_cksum = 0; // force status update
2146 redraw(TRUE); // re-draw the screen
2148 signal(SIGTSTP, suspend_sig);
2149 signal(SIGCONT, SIG_DFL);
2150 kill(my_pid, SIGCONT);
2153 //----- Come here when we get a Suspend signal -------------------
2154 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2156 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2157 clear_to_eol(); // Erase to end of line
2158 cookmode(); // terminal to "cooked"
2160 signal(SIGCONT, cont_sig);
2161 signal(SIGTSTP, SIG_DFL);
2162 kill(my_pid, SIGTSTP);
2165 //----- Come here when we get a signal ---------------------------
2166 static void catch_sig(int sig)
2168 signal(SIGHUP, catch_sig);
2169 signal(SIGINT, catch_sig);
2170 signal(SIGTERM, catch_sig);
2171 signal(SIGALRM, catch_sig);
2173 longjmp(restart, sig);
2176 //----- Come here when we get a core dump signal -----------------
2177 static void core_sig(int sig)
2179 signal(SIGQUIT, core_sig);
2180 signal(SIGILL, core_sig);
2181 signal(SIGTRAP, core_sig);
2182 signal(SIGIOT, core_sig);
2183 signal(SIGABRT, core_sig);
2184 signal(SIGFPE, core_sig);
2185 signal(SIGBUS, core_sig);
2186 signal(SIGSEGV, core_sig);
2188 signal(SIGSYS, core_sig);
2191 if(sig) { // signaled
2192 dot = bound_dot(dot); // make sure "dot" is valid
2193 longjmp(restart, sig);
2196 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2198 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2200 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2205 tv.tv_usec = hund * 10000;
2206 select(1, &rfds, NULL, NULL, &tv);
2207 return (FD_ISSET(0, &rfds));
2210 #define readbuffer bb_common_bufsiz1
2212 static int readed_for_parse;
2214 //----- IO Routines --------------------------------------------
2215 static Byte readit(void) // read (maybe cursor) key from stdin
2224 static const struct esc_cmds esccmds[] = {
2225 {"OA", (Byte) VI_K_UP}, // cursor key Up
2226 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2227 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2228 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2229 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2230 {"OF", (Byte) VI_K_END}, // Cursor Key End
2231 {"[A", (Byte) VI_K_UP}, // cursor key Up
2232 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2233 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2234 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2235 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2236 {"[F", (Byte) VI_K_END}, // Cursor Key End
2237 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2238 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2239 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2240 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2241 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2242 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2243 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2244 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2245 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2246 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2247 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2248 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2249 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2250 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2251 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2252 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2253 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2254 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2255 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2256 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2257 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2260 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2262 (void) alarm(0); // turn alarm OFF while we wait for input
2264 n = readed_for_parse;
2265 // get input from User- are there already input chars in Q?
2268 // the Q is empty, wait for a typed char
2269 n = read(0, readbuffer, BUFSIZ - 1);
2272 goto ri0; // interrupted sys call
2275 if (errno == EFAULT)
2277 if (errno == EINVAL)
2285 if (readbuffer[0] == 27) {
2286 // This is an ESC char. Is this Esc sequence?
2287 // Could be bare Esc key. See if there are any
2288 // more chars to read after the ESC. This would
2289 // be a Function or Cursor Key sequence.
2293 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2295 // keep reading while there are input chars and room in buffer
2296 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2297 // read the rest of the ESC string
2298 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2304 readed_for_parse = n;
2307 if(c == 27 && n > 1) {
2308 // Maybe cursor or function key?
2309 const struct esc_cmds *eindex;
2311 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2312 int cnt = strlen(eindex->seq);
2316 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2318 // is a Cursor key- put derived value back into Q
2320 // for squeeze out the ESC sequence
2324 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2325 /* defined ESC sequence not found, set only one ESC */
2331 // remove key sequence from Q
2332 readed_for_parse -= n;
2333 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2334 (void) alarm(3); // we are done waiting for input, turn alarm ON
2338 //----- IO Routines --------------------------------------------
2339 static Byte get_one_char(void)
2343 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2344 // ! adding2q && ioq == 0 read()
2345 // ! adding2q && ioq != 0 *ioq
2346 // adding2q *last_modifying_cmd= read()
2348 // we are not adding to the q.
2349 // but, we may be reading from a q
2351 // there is no current q, read from STDIN
2352 c = readit(); // get the users input
2354 // there is a queue to get chars from first
2357 // the end of the q, read from STDIN
2359 ioq_start = ioq = 0;
2360 c = readit(); // get the users input
2364 // adding STDIN chars to q
2365 c = readit(); // get the users input
2366 if (last_modifying_cmd != 0) {
2367 int len = strlen((char *) last_modifying_cmd);
2368 if (len + 1 >= BUFSIZ) {
2369 psbs("last_modifying_cmd overrun");
2371 // add new char to q
2372 last_modifying_cmd[len] = c;
2376 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2377 c = readit(); // get the users input
2378 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2379 return (c); // return the char, where ever it came from
2382 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2387 static Byte *obufp = NULL;
2389 strcpy((char *) buf, (char *) prompt);
2390 last_status_cksum = 0; // force status update
2391 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2392 clear_to_eol(); // clear the line
2393 write1((char *) prompt); // write out the :, /, or ? prompt
2395 for (i = strlen((char *) buf); i < BUFSIZ;) {
2396 c = get_one_char(); // read user input
2397 if (c == '\n' || c == '\r' || c == 27)
2398 break; // is this end of input
2399 if (c == erase_char || c == 8 || c == 127) {
2400 // user wants to erase prev char
2401 i--; // backup to prev char
2402 buf[i] = '\0'; // erase the char
2403 buf[i + 1] = '\0'; // null terminate buffer
2404 write1("\b \b"); // erase char on screen
2405 if (i <= 0) { // user backs up before b-o-l, exit
2409 buf[i] = c; // save char in buffer
2410 buf[i + 1] = '\0'; // make sure buffer is null terminated
2411 putchar(c); // echo the char back to user
2417 obufp = (Byte *) bb_xstrdup((char *) buf);
2421 static int file_size(const Byte * fn) // what is the byte size of "fn"
2426 if (fn == 0 || strlen((char *)fn) <= 0)
2429 sr = stat((char *) fn, &st_buf); // see if file exists
2431 cnt = (int) st_buf.st_size;
2436 static int file_insert(Byte * fn, Byte * p, int size)
2441 #ifdef CONFIG_FEATURE_VI_READONLY
2443 #endif /* CONFIG_FEATURE_VI_READONLY */
2444 if (fn == 0 || strlen((char*) fn) <= 0) {
2445 psbs("No filename given");
2449 // OK- this is just a no-op
2454 psbs("Trying to insert a negative number (%d) of characters", size);
2457 if (p < text || p > end) {
2458 psbs("Trying to insert file outside of memory");
2462 // see if we can open the file
2463 #ifdef CONFIG_FEATURE_VI_READONLY
2464 if (vi_readonly) goto fi1; // do not try write-mode
2466 fd = open((char *) fn, O_RDWR); // assume read & write
2468 // could not open for writing- maybe file is read only
2469 #ifdef CONFIG_FEATURE_VI_READONLY
2472 fd = open((char *) fn, O_RDONLY); // try read-only
2474 psbs("\"%s\" %s", fn, "could not open file");
2477 #ifdef CONFIG_FEATURE_VI_READONLY
2478 // got the file- read-only
2480 #endif /* CONFIG_FEATURE_VI_READONLY */
2482 p = text_hole_make(p, size);
2483 cnt = read(fd, p, size);
2487 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2488 psbs("could not read file \"%s\"", fn);
2489 } else if (cnt < size) {
2490 // There was a partial read, shrink unused space text[]
2491 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2492 psbs("could not read all of file \"%s\"", fn);
2500 static int file_write(Byte * fn, Byte * first, Byte * last)
2502 int fd, cnt, charcnt;
2505 psbs("No current filename");
2509 // FIXIT- use the correct umask()
2510 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2513 cnt = last - first + 1;
2514 charcnt = write(fd, first, cnt);
2515 if (charcnt == cnt) {
2517 //file_modified= FALSE; // the file has not been modified
2525 //----- Terminal Drawing ---------------------------------------
2526 // The terminal is made up of 'rows' line of 'columns' columns.
2527 // classically this would be 24 x 80.
2528 // screen coordinates
2534 // 23,0 ... 23,79 status line
2537 //----- Move the cursor to row x col (count from 0, not 1) -------
2538 static void place_cursor(int row, int col, int opti)
2542 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2545 // char cm3[BUFSIZ];
2547 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2549 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2551 if (row < 0) row = 0;
2552 if (row >= rows) row = rows - 1;
2553 if (col < 0) col = 0;
2554 if (col >= columns) col = columns - 1;
2556 //----- 1. Try the standard terminal ESC sequence
2557 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2559 if (! opti) goto pc0;
2561 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2562 //----- find the minimum # of chars to move cursor -------------
2563 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2564 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2566 // move to the correct row
2567 while (row < Rrow) {
2568 // the cursor has to move up
2572 while (row > Rrow) {
2573 // the cursor has to move down
2574 strcat(cm2, CMdown);
2578 // now move to the correct column
2579 strcat(cm2, "\r"); // start at col 0
2580 // just send out orignal source char to get to correct place
2581 screenp = &screen[row * columns]; // start of screen line
2582 strncat(cm2, (char* )screenp, col);
2584 //----- 3. Try some other way of moving cursor
2585 //---------------------------------------------
2587 // pick the shortest cursor motion to send out
2589 if (strlen(cm2) < strlen(cm)) {
2591 } /* else if (strlen(cm3) < strlen(cm)) {
2594 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2596 write1(cm); // move the cursor
2599 //----- Erase from cursor to end of line -----------------------
2600 static void clear_to_eol(void)
2602 write1(Ceol); // Erase from cursor to end of line
2605 //----- Erase from cursor to end of screen -----------------------
2606 static void clear_to_eos(void)
2608 write1(Ceos); // Erase from cursor to end of screen
2611 //----- Start standout mode ------------------------------------
2612 static void standout_start(void) // send "start reverse video" sequence
2614 write1(SOs); // Start reverse video mode
2617 //----- End standout mode --------------------------------------
2618 static void standout_end(void) // send "end reverse video" sequence
2620 write1(SOn); // End reverse video mode
2623 //----- Flash the screen --------------------------------------
2624 static void flash(int h)
2626 standout_start(); // send "start reverse video" sequence
2629 standout_end(); // send "end reverse video" sequence
2633 static void Indicate_Error(void)
2635 #ifdef CONFIG_FEATURE_VI_CRASHME
2637 return; // generate a random command
2638 #endif /* CONFIG_FEATURE_VI_CRASHME */
2640 write1(bell); // send out a bell character
2646 //----- Screen[] Routines --------------------------------------
2647 //----- Erase the Screen[] memory ------------------------------
2648 static void screen_erase(void)
2650 memset(screen, ' ', screensize); // clear new screen
2653 static int bufsum(unsigned char *buf, int count)
2656 unsigned char *e = buf + count;
2662 //----- Draw the status line at bottom of the screen -------------
2663 static void show_status_line(void)
2665 int cnt = 0, cksum = 0;
2667 // either we already have an error or status message, or we
2669 if (!have_status_msg) {
2670 cnt = format_edit_status();
2671 cksum = bufsum(status_buffer, cnt);
2673 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2674 last_status_cksum= cksum; // remember if we have seen this line
2675 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2676 write1((char*)status_buffer);
2678 if (have_status_msg) {
2679 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2681 have_status_msg = 0;
2684 have_status_msg = 0;
2686 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2691 //----- format the status buffer, the bottom line of screen ------
2692 // format status buffer, with STANDOUT mode
2693 static void psbs(const char *format, ...)
2697 va_start(args, format);
2698 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2699 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2700 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2703 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2708 // format status buffer
2709 static void psb(const char *format, ...)
2713 va_start(args, format);
2714 vsprintf((char *) status_buffer, format, args);
2717 have_status_msg = 1;
2722 static void ni(Byte * s) // display messages
2726 print_literal(buf, s);
2727 psbs("\'%s\' is not implemented", buf);
2730 static int format_edit_status(void) // show file status on status line
2732 int cur, percent, ret, trunc_at;
2735 // file_modified is now a counter rather than a flag. this
2736 // helps reduce the amount of line counting we need to do.
2737 // (this will cause a mis-reporting of modified status
2738 // once every MAXINT editing operations.)
2740 // it would be nice to do a similar optimization here -- if
2741 // we haven't done a motion that could have changed which line
2742 // we're on, then we shouldn't have to do this count_lines()
2743 cur = count_lines(text, dot);
2745 // reduce counting -- the total lines can't have
2746 // changed if we haven't done any edits.
2747 if (file_modified != last_file_modified) {
2748 tot = cur + count_lines(dot, end - 1) - 1;
2749 last_file_modified = file_modified;
2752 // current line percent
2753 // ------------- ~~ ----------
2756 percent = (100 * cur) / tot;
2762 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2763 columns : STATUS_BUFFER_LEN-1;
2765 ret = snprintf((char *) status_buffer, trunc_at+1,
2766 #ifdef CONFIG_FEATURE_VI_READONLY
2767 "%c %s%s%s %d/%d %d%%",
2769 "%c %s%s %d/%d %d%%",
2771 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2772 (cfn != 0 ? (char *) cfn : "No file"),
2773 #ifdef CONFIG_FEATURE_VI_READONLY
2774 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2776 (file_modified ? " [modified]" : ""),
2779 if (ret >= 0 && ret < trunc_at)
2780 return ret; /* it all fit */
2782 return trunc_at; /* had to truncate */
2785 //----- Force refresh of all Lines -----------------------------
2786 static void redraw(int full_screen)
2788 place_cursor(0, 0, FALSE); // put cursor in correct place
2789 clear_to_eos(); // tel terminal to erase display
2790 screen_erase(); // erase the internal screen buffer
2791 last_status_cksum = 0; // force status update
2792 refresh(full_screen); // this will redraw the entire display
2796 //----- Format a text[] line into a buffer ---------------------
2797 static void format_line(Byte *dest, Byte *src, int li)
2802 for (co= 0; co < MAX_SCR_COLS; co++) {
2803 c= ' '; // assume blank
2804 if (li > 0 && co == 0) {
2805 c = '~'; // not first line, assume Tilde
2807 // are there chars in text[] and have we gone past the end
2808 if (text < end && src < end) {
2813 if (c > 127 && !Isprint(c)) {
2816 if (c < ' ' || c == 127) {
2820 for (; (co % tabstop) != (tabstop - 1); co++) {
2828 c += '@'; // make it visible
2831 // the co++ is done here so that the column will
2832 // not be overwritten when we blank-out the rest of line
2839 //----- Refresh the changed screen lines -----------------------
2840 // Copy the source line from text[] into the buffer and note
2841 // if the current screenline is different from the new buffer.
2842 // If they differ then that line needs redrawing on the terminal.
2844 static void refresh(int full_screen)
2846 static int old_offset;
2848 Byte buf[MAX_SCR_COLS];
2849 Byte *tp, *sp; // pointer into text[] and screen[]
2850 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2851 int last_li= -2; // last line that changed- for optimizing cursor movement
2852 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2854 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2856 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2857 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2858 tp = screenbegin; // index into text[] of top line
2860 // compare text[] to screen[] and mark screen[] lines that need updating
2861 for (li = 0; li < rows - 1; li++) {
2862 int cs, ce; // column start & end
2863 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2864 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2865 // format current text line into buf
2866 format_line(buf, tp, li);
2868 // skip to the end of the current text[] line
2869 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2871 // see if there are any changes between vitual screen and buf
2872 changed = FALSE; // assume no change
2875 sp = &screen[li * columns]; // start of screen line
2877 // force re-draw of every single column from 0 - columns-1
2880 // compare newly formatted buffer with virtual screen
2881 // look forward for first difference between buf and screen
2882 for ( ; cs <= ce; cs++) {
2883 if (buf[cs + offset] != sp[cs]) {
2884 changed = TRUE; // mark for redraw
2889 // look backward for last difference between buf and screen
2890 for ( ; ce >= cs; ce--) {
2891 if (buf[ce + offset] != sp[ce]) {
2892 changed = TRUE; // mark for redraw
2896 // now, cs is index of first diff, and ce is index of last diff
2898 // if horz offset has changed, force a redraw
2899 if (offset != old_offset) {
2904 // make a sanity check of columns indexes
2906 if (ce > columns-1) ce= columns-1;
2907 if (cs > ce) { cs= 0; ce= columns-1; }
2908 // is there a change between vitual screen and buf
2910 // copy changed part of buffer to virtual screen
2911 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2913 // move cursor to column of first change
2914 if (offset != old_offset) {
2915 // opti_cur_move is still too stupid
2916 // to handle offsets correctly
2917 place_cursor(li, cs, FALSE);
2919 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2920 // if this just the next line
2921 // try to optimize cursor movement
2922 // otherwise, use standard ESC sequence
2923 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2925 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2926 place_cursor(li, cs, FALSE); // use standard ESC sequence
2927 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2930 // write line out to terminal
2933 char *out = (char*)sp+cs;
2940 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2942 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2946 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2947 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2950 place_cursor(crow, ccol, FALSE);
2951 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2953 if (offset != old_offset)
2954 old_offset = offset;
2957 //---------------------------------------------------------------------
2958 //----- the Ascii Chart -----------------------------------------------
2960 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2961 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2962 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2963 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2964 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2965 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2966 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2967 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2968 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2969 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2970 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2971 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2972 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2973 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2974 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2975 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2976 //---------------------------------------------------------------------
2978 //----- Execute a Vi Command -----------------------------------
2979 static void do_cmd(Byte c)
2981 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2982 int cnt, i, j, dir, yf;
2984 c1 = c; // quiet the compiler
2985 cnt = yf = dir = 0; // quiet the compiler
2986 p = q = save_dot = msg = buf; // quiet the compiler
2987 memset(buf, '\0', 9); // clear buf
2991 /* if this is a cursor key, skip these checks */
3004 if (cmd_mode == 2) {
3005 // flip-flop Insert/Replace mode
3006 if (c == VI_K_INSERT) goto dc_i;
3007 // we are 'R'eplacing the current *dot with new char
3009 // don't Replace past E-o-l
3010 cmd_mode = 1; // convert to insert
3012 if (1 <= c || Isprint(c)) {
3014 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3015 dot = char_insert(dot, c); // insert new char
3020 if (cmd_mode == 1) {
3021 // hitting "Insert" twice means "R" replace mode
3022 if (c == VI_K_INSERT) goto dc5;
3023 // insert the char c at "dot"
3024 if (1 <= c || Isprint(c)) {
3025 dot = char_insert(dot, c);
3040 #ifdef CONFIG_FEATURE_VI_CRASHME
3041 case 0x14: // dc4 ctrl-T
3042 crashme = (crashme == 0) ? 1 : 0;
3044 #endif /* CONFIG_FEATURE_VI_CRASHME */
3073 //case 'u': // u- FIXME- there is no undo
3075 default: // unrecognised command
3084 end_cmd_q(); // stop adding to q
3085 case 0x00: // nul- ignore
3087 case 2: // ctrl-B scroll up full screen
3088 case VI_K_PAGEUP: // Cursor Key Page Up
3089 dot_scroll(rows - 2, -1);
3091 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3092 case 0x03: // ctrl-C interrupt
3093 longjmp(restart, 1);
3095 case 26: // ctrl-Z suspend
3096 suspend_sig(SIGTSTP);
3098 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3099 case 4: // ctrl-D scroll down half screen
3100 dot_scroll((rows - 2) / 2, 1);
3102 case 5: // ctrl-E scroll down one line
3105 case 6: // ctrl-F scroll down full screen
3106 case VI_K_PAGEDOWN: // Cursor Key Page Down
3107 dot_scroll(rows - 2, 1);
3109 case 7: // ctrl-G show current status
3110 last_status_cksum = 0; // force status update
3112 case 'h': // h- move left
3113 case VI_K_LEFT: // cursor key Left
3114 case 8: // ctrl-H- move left (This may be ERASE char)
3115 case 127: // DEL- move left (This may be ERASE char)
3121 case 10: // Newline ^J
3122 case 'j': // j- goto next line, same col
3123 case VI_K_DOWN: // cursor key Down
3127 dot_next(); // go to next B-o-l
3128 dot = move_to_col(dot, ccol + offset); // try stay in same col
3130 case 12: // ctrl-L force redraw whole screen
3131 case 18: // ctrl-R force redraw
3132 place_cursor(0, 0, FALSE); // put cursor in correct place
3133 clear_to_eos(); // tel terminal to erase display
3135 screen_erase(); // erase the internal screen buffer
3136 last_status_cksum = 0; // force status update
3137 refresh(TRUE); // this will redraw the entire display
3139 case 13: // Carriage Return ^M
3140 case '+': // +- goto next line
3147 case 21: // ctrl-U scroll up half screen
3148 dot_scroll((rows - 2) / 2, -1);
3150 case 25: // ctrl-Y scroll up one line
3156 cmd_mode = 0; // stop insrting
3158 last_status_cksum = 0; // force status update
3160 case ' ': // move right
3161 case 'l': // move right
3162 case VI_K_RIGHT: // Cursor Key Right
3168 #ifdef CONFIG_FEATURE_VI_YANKMARK
3169 case '"': // "- name a register to use for Delete/Yank
3170 c1 = get_one_char();
3178 case '\'': // '- goto a specific mark
3179 c1 = get_one_char();
3185 if (text <= q && q < end) {
3187 dot_begin(); // go to B-o-l
3190 } else if (c1 == '\'') { // goto previous context
3191 dot = swap_context(dot); // swap current and previous context
3192 dot_begin(); // go to B-o-l
3198 case 'm': // m- Mark a line
3199 // this is really stupid. If there are any inserts or deletes
3200 // between text[0] and dot then this mark will not point to the
3201 // correct location! It could be off by many lines!
3202 // Well..., at least its quick and dirty.
3203 c1 = get_one_char();
3207 // remember the line
3208 mark[(int) c1] = dot;
3213 case 'P': // P- Put register before
3214 case 'p': // p- put register after
3217 psbs("Nothing in register %c", what_reg());
3220 // are we putting whole lines or strings
3221 if (strchr((char *) p, '\n') != NULL) {
3223 dot_begin(); // putting lines- Put above
3226 // are we putting after very last line?
3227 if (end_line(dot) == (end - 1)) {
3228 dot = end; // force dot to end of text[]
3230 dot_next(); // next line, then put before
3235 dot_right(); // move to right, can move to NL
3237 dot = string_insert(dot, p); // insert the string
3238 end_cmd_q(); // stop adding to q
3240 case 'U': // U- Undo; replace current line with original version
3241 if (reg[Ureg] != 0) {
3242 p = begin_line(dot);
3244 p = text_hole_delete(p, q); // delete cur line
3245 p = string_insert(p, reg[Ureg]); // insert orig line
3250 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3251 case '$': // $- goto end of line
3252 case VI_K_END: // Cursor Key End
3256 dot = end_line(dot);
3258 case '%': // %- find matching char of pair () [] {}
3259 for (q = dot; q < end && *q != '\n'; q++) {
3260 if (strchr("()[]{}", *q) != NULL) {
3261 // we found half of a pair
3262 p = find_pair(q, *q);
3274 case 'f': // f- forward to a user specified char
3275 last_forward_char = get_one_char(); // get the search char
3277 // dont separate these two commands. 'f' depends on ';'
3279 //**** fall thru to ... ';'
3280 case ';': // ;- look at rest of line for last forward char
3284 if (last_forward_char == 0) break;
3286 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3289 if (*q == last_forward_char)
3292 case '-': // -- goto prev line
3299 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3300 case '.': // .- repeat the last modifying command
3301 // Stuff the last_modifying_cmd back into stdin
3302 // and let it be re-executed.
3303 if (last_modifying_cmd != 0) {
3304 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3307 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3308 #ifdef CONFIG_FEATURE_VI_SEARCH
3309 case '?': // /- search for a pattern
3310 case '/': // /- search for a pattern
3313 q = get_input_line(buf); // get input line- use "status line"
3314 if (strlen((char *) q) == 1)
3315 goto dc3; // if no pat re-use old pat
3316 if (strlen((char *) q) > 1) { // new pat- save it and find
3317 // there is a new pat
3318 free(last_search_pattern);
3319 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3320 goto dc3; // now find the pattern
3322 // user changed mind and erased the "/"- do nothing
3324 case 'N': // N- backward search for last pattern
3328 dir = BACK; // assume BACKWARD search
3330 if (last_search_pattern[0] == '?') {
3334 goto dc4; // now search for pattern
3336 case 'n': // n- repeat search for last pattern
3337 // search rest of text[] starting at next char
3338 // if search fails return orignal "p" not the "p+1" address
3343 if (last_search_pattern == 0) {
3344 msg = (Byte *) "No previous regular expression";
3347 if (last_search_pattern[0] == '/') {
3348 dir = FORWARD; // assume FORWARD search
3351 if (last_search_pattern[0] == '?') {
3356 q = char_search(p, last_search_pattern + 1, dir, FULL);
3358 dot = q; // good search, update "dot"
3362 // no pattern found between "dot" and "end"- continue at top
3367 q = char_search(p, last_search_pattern + 1, dir, FULL);
3368 if (q != NULL) { // found something
3369 dot = q; // found new pattern- goto it
3370 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3372 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3375 msg = (Byte *) "Pattern not found";
3378 if (*msg) psbs("%s", msg);
3380 case '{': // {- move backward paragraph
3381 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3382 if (q != NULL) { // found blank line
3383 dot = next_line(q); // move to next blank line
3386 case '}': // }- move forward paragraph
3387 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3388 if (q != NULL) { // found blank line
3389 dot = next_line(q); // move to next blank line
3392 #endif /* CONFIG_FEATURE_VI_SEARCH */
3393 case '0': // 0- goto begining of line
3403 if (c == '0' && cmdcnt < 1) {
3404 dot_begin(); // this was a standalone zero
3406 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3409 case ':': // :- the colon mode commands
3410 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3411 #ifdef CONFIG_FEATURE_VI_COLON
3412 colon(p); // execute the command
3413 #else /* CONFIG_FEATURE_VI_COLON */
3415 p++; // move past the ':'
3416 cnt = strlen((char *) p);
3419 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3420 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3421 if (file_modified && p[1] != '!') {
3422 psbs("No write since last change (:quit! overrides)");
3426 } else if (strncasecmp((char *) p, "write", cnt) == 0
3427 || strncasecmp((char *) p, "wq", cnt) == 0
3428 || strncasecmp((char *) p, "wn", cnt) == 0
3429 || strncasecmp((char *) p, "x", cnt) == 0) {
3430 cnt = file_write(cfn, text, end - 1);
3433 psbs("Write error: %s", strerror(errno));
3436 last_file_modified = -1;
3437 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3438 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3439 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3443 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3444 last_status_cksum = 0; // force status update
3445 } else if (sscanf((char *) p, "%d", &j) > 0) {
3446 dot = find_line(j); // go to line # j
3448 } else { // unrecognised cmd
3451 #endif /* CONFIG_FEATURE_VI_COLON */
3453 case '<': // <- Left shift something
3454 case '>': // >- Right shift something
3455 cnt = count_lines(text, dot); // remember what line we are on
3456 c1 = get_one_char(); // get the type of thing to delete
3457 find_range(&p, &q, c1);
3458 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3461 i = count_lines(p, q); // # of lines we are shifting
3462 for ( ; i > 0; i--, p = next_line(p)) {
3464 // shift left- remove tab or 8 spaces
3466 // shrink buffer 1 char
3467 (void) text_hole_delete(p, p);
3468 } else if (*p == ' ') {
3469 // we should be calculating columns, not just SPACE
3470 for (j = 0; *p == ' ' && j < tabstop; j++) {
3471 (void) text_hole_delete(p, p);
3474 } else if (c == '>') {
3475 // shift right -- add tab or 8 spaces
3476 (void) char_insert(p, '\t');
3479 dot = find_line(cnt); // what line were we on
3481 end_cmd_q(); // stop adding to q
3483 case 'A': // A- append at e-o-l
3484 dot_end(); // go to e-o-l
3485 //**** fall thru to ... 'a'
3486 case 'a': // a- append after current char
3491 case 'B': // B- back a blank-delimited Word
3492 case 'E': // E- end of a blank-delimited word
3493 case 'W': // W- forward a blank-delimited word
3500 if (c == 'W' || isspace(dot[dir])) {
3501 dot = skip_thing(dot, 1, dir, S_TO_WS);
3502 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3505 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3507 case 'C': // C- Change to e-o-l
3508 case 'D': // D- delete to e-o-l
3510 dot = dollar_line(dot); // move to before NL
3511 // copy text into a register and delete
3512 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3514 goto dc_i; // start inserting
3515 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3517 end_cmd_q(); // stop adding to q
3518 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3520 case 'G': // G- goto to a line number (default= E-O-F)
3521 dot = end - 1; // assume E-O-F
3523 dot = find_line(cmdcnt); // what line is #cmdcnt
3527 case 'H': // H- goto top line on screen
3529 if (cmdcnt > (rows - 1)) {
3530 cmdcnt = (rows - 1);
3537 case 'I': // I- insert before first non-blank
3540 //**** fall thru to ... 'i'
3541 case 'i': // i- insert before current char
3542 case VI_K_INSERT: // Cursor Key Insert
3544 cmd_mode = 1; // start insrting
3546 case 'J': // J- join current and next lines together
3550 dot_end(); // move to NL
3551 if (dot < end - 1) { // make sure not last char in text[]
3552 *dot++ = ' '; // replace NL with space
3554 while (isblnk(*dot)) { // delete leading WS
3558 end_cmd_q(); // stop adding to q
3560 case 'L': // L- goto bottom line on screen
3562 if (cmdcnt > (rows - 1)) {
3563 cmdcnt = (rows - 1);
3571 case 'M': // M- goto middle line on screen
3573 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3574 dot = next_line(dot);
3576 case 'O': // O- open a empty line above
3578 p = begin_line(dot);
3579 if (p[-1] == '\n') {
3581 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3583 dot = char_insert(dot, '\n');
3586 dot = char_insert(dot, '\n'); // i\n ESC
3591 case 'R': // R- continuous Replace char
3595 case 'X': // X- delete char before dot
3596 case 'x': // x- delete the current char
3597 case 's': // s- substitute the current char
3604 if (dot[dir] != '\n') {
3606 dot--; // delete prev char
3607 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3610 goto dc_i; // start insrting
3611 end_cmd_q(); // stop adding to q
3613 case 'Z': // Z- if modified, {write}; exit
3614 // ZZ means to save file (if necessary), then exit
3615 c1 = get_one_char();
3620 if (file_modified) {
3621 #ifdef CONFIG_FEATURE_VI_READONLY
3622 if (vi_readonly || readonly) {
3623 psbs("\"%s\" File is read only", cfn);
3626 #endif /* CONFIG_FEATURE_VI_READONLY */
3627 cnt = file_write(cfn, text, end - 1);
3630 psbs("Write error: %s", strerror(errno));
3631 } else if (cnt == (end - 1 - text + 1)) {
3638 case '^': // ^- move to first non-blank on line
3642 case 'b': // b- back a word
3643 case 'e': // e- end of word
3650 if ((dot + dir) < text || (dot + dir) > end - 1)
3653 if (isspace(*dot)) {
3654 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3656 if (isalnum(*dot) || *dot == '_') {
3657 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3658 } else if (ispunct(*dot)) {
3659 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3662 case 'c': // c- change something
3663 case 'd': // d- delete something
3664 #ifdef CONFIG_FEATURE_VI_YANKMARK
3665 case 'y': // y- yank something
3666 case 'Y': // Y- Yank a line
3667 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3668 yf = YANKDEL; // assume either "c" or "d"
3669 #ifdef CONFIG_FEATURE_VI_YANKMARK
3670 if (c == 'y' || c == 'Y')
3672 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3675 c1 = get_one_char(); // get the type of thing to delete
3676 find_range(&p, &q, c1);
3677 if (c1 == 27) { // ESC- user changed mind and wants out
3678 c = c1 = 27; // Escape- do nothing
3679 } else if (strchr("wW", c1)) {
3681 // don't include trailing WS as part of word
3682 while (isblnk(*q)) {
3683 if (q <= text || q[-1] == '\n')
3688 dot = yank_delete(p, q, 0, yf); // delete word
3689 } else if (strchr("^0bBeEft$", c1)) {
3690 // single line copy text into a register and delete
3691 dot = yank_delete(p, q, 0, yf); // delete word
3692 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3693 // multiple line copy text into a register and delete
3694 dot = yank_delete(p, q, 1, yf); // delete lines
3696 dot = char_insert(dot, '\n');
3697 // on the last line of file don't move to prev line
3698 if (dot != (end-1)) {
3701 } else if (c == 'd') {
3706 // could not recognize object
3707 c = c1 = 27; // error-
3711 // if CHANGING, not deleting, start inserting after the delete
3713 strcpy((char *) buf, "Change");
3714 goto dc_i; // start inserting
3717 strcpy((char *) buf, "Delete");
3719 #ifdef CONFIG_FEATURE_VI_YANKMARK
3720 if (c == 'y' || c == 'Y') {
3721 strcpy((char *) buf, "Yank");
3724 q = p + strlen((char *) p);
3725 for (cnt = 0; p <= q; p++) {
3729 psb("%s %d lines (%d chars) using [%c]",
3730 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3731 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3732 end_cmd_q(); // stop adding to q
3735 case 'k': // k- goto prev line, same col
3736 case VI_K_UP: // cursor key Up
3741 dot = move_to_col(dot, ccol + offset); // try stay in same col
3743 case 'r': // r- replace the current char with user input
3744 c1 = get_one_char(); // get the replacement char
3747 file_modified++; // has the file been modified
3749 end_cmd_q(); // stop adding to q
3751 case 't': // t- move to char prior to next x
3752 last_forward_char = get_one_char();
3754 if (*dot == last_forward_char)
3756 last_forward_char= 0;
3758 case 'w': // w- forward a word
3762 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3763 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3764 } else if (ispunct(*dot)) { // we are on PUNCT
3765 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3768 dot++; // move over word
3769 if (isspace(*dot)) {
3770 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3774 c1 = get_one_char(); // get the replacement char
3777 cnt = (rows - 2) / 2; // put dot at center
3779 cnt = rows - 2; // put dot at bottom
3780 screenbegin = begin_line(dot); // start dot at top
3781 dot_scroll(cnt, -1);
3783 case '|': // |- move to column "cmdcnt"
3784 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3786 case '~': // ~- flip the case of letters a-z -> A-Z
3790 if (islower(*dot)) {
3791 *dot = toupper(*dot);
3792 file_modified++; // has the file been modified
3793 } else if (isupper(*dot)) {
3794 *dot = tolower(*dot);
3795 file_modified++; // has the file been modified
3798 end_cmd_q(); // stop adding to q
3800 //----- The Cursor and Function Keys -----------------------------
3801 case VI_K_HOME: // Cursor Key Home
3804 // The Fn keys could point to do_macro which could translate them
3805 case VI_K_FUN1: // Function Key F1
3806 case VI_K_FUN2: // Function Key F2
3807 case VI_K_FUN3: // Function Key F3
3808 case VI_K_FUN4: // Function Key F4
3809 case VI_K_FUN5: // Function Key F5
3810 case VI_K_FUN6: // Function Key F6
3811 case VI_K_FUN7: // Function Key F7
3812 case VI_K_FUN8: // Function Key F8
3813 case VI_K_FUN9: // Function Key F9
3814 case VI_K_FUN10: // Function Key F10
3815 case VI_K_FUN11: // Function Key F11
3816 case VI_K_FUN12: // Function Key F12
3821 // if text[] just became empty, add back an empty line
3823 (void) char_insert(text, '\n'); // start empty buf with dummy line
3826 // it is OK for dot to exactly equal to end, otherwise check dot validity
3828 dot = bound_dot(dot); // make sure "dot" is valid
3830 #ifdef CONFIG_FEATURE_VI_YANKMARK
3831 check_context(c); // update the current context
3832 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3835 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3836 cnt = dot - begin_line(dot);
3837 // Try to stay off of the Newline
3838 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3842 #ifdef CONFIG_FEATURE_VI_CRASHME
3843 static int totalcmds = 0;
3844 static int Mp = 85; // Movement command Probability
3845 static int Np = 90; // Non-movement command Probability
3846 static int Dp = 96; // Delete command Probability
3847 static int Ip = 97; // Insert command Probability
3848 static int Yp = 98; // Yank command Probability
3849 static int Pp = 99; // Put command Probability
3850 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3851 char chars[20] = "\t012345 abcdABCD-=.$";
3852 char *words[20] = { "this", "is", "a", "test",
3853 "broadcast", "the", "emergency", "of",
3854 "system", "quick", "brown", "fox",
3855 "jumped", "over", "lazy", "dogs",
3856 "back", "January", "Febuary", "March"
3859 "You should have received a copy of the GNU General Public License\n",
3860 "char c, cm, *cmd, *cmd1;\n",
3861 "generate a command by percentages\n",
3862 "Numbers may be typed as a prefix to some commands.\n",
3863 "Quit, discarding changes!\n",
3864 "Forced write, if permission originally not valid.\n",
3865 "In general, any ex or ed command (such as substitute or delete).\n",
3866 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3867 "Please get w/ me and I will go over it with you.\n",
3868 "The following is a list of scheduled, committed changes.\n",
3869 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3870 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3871 "Any question about transactions please contact Sterling Huxley.\n",
3872 "I will try to get back to you by Friday, December 31.\n",
3873 "This Change will be implemented on Friday.\n",
3874 "Let me know if you have problems accessing this;\n",
3875 "Sterling Huxley recently added you to the access list.\n",
3876 "Would you like to go to lunch?\n",
3877 "The last command will be automatically run.\n",
3878 "This is too much english for a computer geek.\n",
3880 char *multilines[20] = {
3881 "You should have received a copy of the GNU General Public License\n",
3882 "char c, cm, *cmd, *cmd1;\n",
3883 "generate a command by percentages\n",
3884 "Numbers may be typed as a prefix to some commands.\n",
3885 "Quit, discarding changes!\n",
3886 "Forced write, if permission originally not valid.\n",
3887 "In general, any ex or ed command (such as substitute or delete).\n",
3888 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3889 "Please get w/ me and I will go over it with you.\n",
3890 "The following is a list of scheduled, committed changes.\n",
3891 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3892 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3893 "Any question about transactions please contact Sterling Huxley.\n",
3894 "I will try to get back to you by Friday, December 31.\n",
3895 "This Change will be implemented on Friday.\n",
3896 "Let me know if you have problems accessing this;\n",
3897 "Sterling Huxley recently added you to the access list.\n",
3898 "Would you like to go to lunch?\n",
3899 "The last command will be automatically run.\n",
3900 "This is too much english for a computer geek.\n",
3903 // create a random command to execute
3904 static void crash_dummy()
3906 static int sleeptime; // how long to pause between commands
3907 char c, cm, *cmd, *cmd1;
3908 int i, cnt, thing, rbi, startrbi, percent;
3910 // "dot" movement commands
3911 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3913 // is there already a command running?
3914 if (readed_for_parse > 0)
3918 sleeptime = 0; // how long to pause between commands
3919 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3920 // generate a command by percentages
3921 percent = (int) lrand48() % 100; // get a number from 0-99
3922 if (percent < Mp) { // Movement commands
3923 // available commands
3926 } else if (percent < Np) { // non-movement commands
3927 cmd = "mz<>\'\""; // available commands
3929 } else if (percent < Dp) { // Delete commands
3930 cmd = "dx"; // available commands
3932 } else if (percent < Ip) { // Inset commands
3933 cmd = "iIaAsrJ"; // available commands
3935 } else if (percent < Yp) { // Yank commands
3936 cmd = "yY"; // available commands
3938 } else if (percent < Pp) { // Put commands
3939 cmd = "pP"; // available commands
3942 // We do not know how to handle this command, try again
3946 // randomly pick one of the available cmds from "cmd[]"
3947 i = (int) lrand48() % strlen(cmd);
3949 if (strchr(":\024", cm))
3950 goto cd0; // dont allow colon or ctrl-T commands
3951 readbuffer[rbi++] = cm; // put cmd into input buffer
3953 // now we have the command-
3954 // there are 1, 2, and multi char commands
3955 // find out which and generate the rest of command as necessary
3956 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3957 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3958 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3959 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3961 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3963 readbuffer[rbi++] = c; // add movement to input buffer
3965 if (strchr("iIaAsc", cm)) { // multi-char commands
3967 // change some thing
3968 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3970 readbuffer[rbi++] = c; // add movement to input buffer
3972 thing = (int) lrand48() % 4; // what thing to insert
3973 cnt = (int) lrand48() % 10; // how many to insert
3974 for (i = 0; i < cnt; i++) {
3975 if (thing == 0) { // insert chars
3976 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3977 } else if (thing == 1) { // insert words
3978 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3979 strcat((char *) readbuffer, " ");
3980 sleeptime = 0; // how fast to type
3981 } else if (thing == 2) { // insert lines
3982 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3983 sleeptime = 0; // how fast to type
3984 } else { // insert multi-lines
3985 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3986 sleeptime = 0; // how fast to type
3989 strcat((char *) readbuffer, "\033");
3991 readed_for_parse = strlen(readbuffer);
3995 (void) mysleep(sleeptime); // sleep 1/100 sec
3998 // test to see if there are any errors
3999 static void crash_test()
4001 static time_t oldtim;
4003 char d[2], msg[BUFSIZ];
4007 strcat((char *) msg, "end<text ");
4009 if (end > textend) {
4010 strcat((char *) msg, "end>textend ");
4013 strcat((char *) msg, "dot<text ");
4016 strcat((char *) msg, "dot>end ");
4018 if (screenbegin < text) {
4019 strcat((char *) msg, "screenbegin<text ");
4021 if (screenbegin > end - 1) {
4022 strcat((char *) msg, "screenbegin>end-1 ");
4025 if (strlen(msg) > 0) {
4027 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4028 totalcmds, last_input_char, msg, SOs, SOn);
4030 while (read(0, d, 1) > 0) {
4031 if (d[0] == '\n' || d[0] == '\r')
4036 tim = (time_t) time((time_t *) 0);
4037 if (tim >= (oldtim + 3)) {
4038 sprintf((char *) status_buffer,
4039 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4040 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4045 #endif /* CONFIG_FEATURE_VI_CRASHME */