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 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 static const char vi_Version[] =
22 "$Id: vi.c,v 1.38 2004/08/19 19:15:06 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -s -DSTANDALONE -DCONFIG_FEATURE_VI_CRASHME -o vi vi.c # include testing features
35 * $HOME/.exrc and ./.exrc
36 * add magic to search /foo.*bar
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 * More intelligence in refresh()
43 * ":r !cmd" and "!cmd" to filter text through an external command
44 * A true "undo" facility
45 * An "ex" line oriented mode- maybe using "cmdedit"
48 //---- Feature -------------- Bytes to implement
51 #define CONFIG_FEATURE_VI_COLON // 4288
52 #define CONFIG_FEATURE_VI_YANKMARK // 1408
53 #define CONFIG_FEATURE_VI_SEARCH // 1088
54 #define CONFIG_FEATURE_VI_USE_SIGNALS // 1056
55 #define CONFIG_FEATURE_VI_DOT_CMD // 576
56 #define CONFIG_FEATURE_VI_READONLY // 128
57 #define CONFIG_FEATURE_VI_SETOPTS // 576
58 #define CONFIG_FEATURE_VI_SET // 224
59 #define CONFIG_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
60 // To test editor using CRASHME:
62 // To stop testing, wait until all to text[] is deleted, or
63 // Ctrl-Z and kill -9 %1
64 // while in the editor Ctrl-T will toggle the crashme function on and off.
65 //#define CONFIG_FEATURE_VI_CRASHME // randomly pick commands to execute
66 #endif /* STANDALONE */
73 #include <sys/ioctl.h>
75 #include <sys/types.h>
88 #endif /* STANDALONE */
90 #ifdef CONFIG_LOCALE_SUPPORT
91 #define Isprint(c) isprint((c))
93 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
98 #define FALSE ((int)0)
100 #define MAX_SCR_COLS BUFSIZ
102 // Misc. non-Ascii keys that report an escape sequence
103 #define VI_K_UP 128 // cursor key Up
104 #define VI_K_DOWN 129 // cursor key Down
105 #define VI_K_RIGHT 130 // Cursor Key Right
106 #define VI_K_LEFT 131 // cursor key Left
107 #define VI_K_HOME 132 // Cursor Key Home
108 #define VI_K_END 133 // Cursor Key End
109 #define VI_K_INSERT 134 // Cursor Key Insert
110 #define VI_K_PAGEUP 135 // Cursor Key Page Up
111 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
112 #define VI_K_FUN1 137 // Function Key F1
113 #define VI_K_FUN2 138 // Function Key F2
114 #define VI_K_FUN3 139 // Function Key F3
115 #define VI_K_FUN4 140 // Function Key F4
116 #define VI_K_FUN5 141 // Function Key F5
117 #define VI_K_FUN6 142 // Function Key F6
118 #define VI_K_FUN7 143 // Function Key F7
119 #define VI_K_FUN8 144 // Function Key F8
120 #define VI_K_FUN9 145 // Function Key F9
121 #define VI_K_FUN10 146 // Function Key F10
122 #define VI_K_FUN11 147 // Function Key F11
123 #define VI_K_FUN12 148 // Function Key F12
125 /* vt102 typical ESC sequence */
126 /* terminal standout start/normal ESC sequence */
127 static const char SOs[] = "\033[7m";
128 static const char SOn[] = "\033[0m";
129 /* terminal bell sequence */
130 static const char bell[] = "\007";
131 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
132 static const char Ceol[] = "\033[0K";
133 static const char Ceos [] = "\033[0J";
134 /* Cursor motion arbitrary destination ESC sequence */
135 static const char CMrc[] = "\033[%d;%dH";
136 /* Cursor motion up and down ESC sequence */
137 static const char CMup[] = "\033[A";
138 static const char CMdown[] = "\n";
144 FORWARD = 1, // code depends on "1" for array index
145 BACK = -1, // code depends on "-1" for array index
146 LIMITED = 0, // how much of text[] in char_search
147 FULL = 1, // how much of text[] in char_search
149 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
150 S_TO_WS = 2, // used in skip_thing() for moving "dot"
151 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
152 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
153 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
156 typedef unsigned char Byte;
158 static int vi_setops;
159 #define VI_AUTOINDENT 1
160 #define VI_SHOWMATCH 2
161 #define VI_IGNORECASE 4
162 #define VI_ERR_METHOD 8
163 #define autoindent (vi_setops & VI_AUTOINDENT)
164 #define showmatch (vi_setops & VI_SHOWMATCH )
165 #define ignorecase (vi_setops & VI_IGNORECASE)
166 /* indicate error with beep or flash */
167 #define err_method (vi_setops & VI_ERR_METHOD)
170 static int editing; // >0 while we are editing a file
171 static int cmd_mode; // 0=command 1=insert 2=replace
172 static int file_modified; // buffer contents changed
173 static int last_file_modified = -1;
174 static int fn_start; // index of first cmd line file name
175 static int save_argc; // how many file names on cmd line
176 static int cmdcnt; // repetition count
177 static fd_set rfds; // use select() for small sleeps
178 static struct timeval tv; // use select() for small sleeps
179 static int rows, columns; // the terminal screen is this size
180 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
181 static Byte *status_buffer; // mesages to the user
182 #define STATUS_BUFFER_LEN 200
183 static int have_status_msg; // is default edit status needed?
184 static int last_status_cksum; // hash of current status line
185 static Byte *cfn; // previous, current, and next file name
186 static Byte *text, *end, *textend; // pointers to the user data in memory
187 static Byte *screen; // pointer to the virtual screen buffer
188 static int screensize; // and its size
189 static Byte *screenbegin; // index into text[], of top line on the screen
190 static Byte *dot; // where all the action takes place
192 static struct termios term_orig, term_vi; // remember what the cooked mode was
193 static Byte erase_char; // the users erase character
194 static Byte last_input_char; // last char read from user
195 static Byte last_forward_char; // last char searched for with 'f'
197 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
198 static int last_row; // where the cursor was last moved to
199 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
200 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
201 static jmp_buf restart; // catch_sig()
202 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
203 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
206 #ifdef CONFIG_FEATURE_VI_DOT_CMD
207 static int adding2q; // are we currently adding user input to q
208 static Byte *last_modifying_cmd; // last modifying cmd for "."
209 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
210 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
211 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
212 static Byte *modifying_cmds; // cmds that modify text[]
213 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
214 #ifdef CONFIG_FEATURE_VI_READONLY
215 static int vi_readonly, readonly;
216 #endif /* CONFIG_FEATURE_VI_READONLY */
217 #ifdef CONFIG_FEATURE_VI_YANKMARK
218 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
219 static int YDreg, Ureg; // default delete register and orig line for "U"
220 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
221 static Byte *context_start, *context_end;
222 #endif /* CONFIG_FEATURE_VI_YANKMARK */
223 #ifdef CONFIG_FEATURE_VI_SEARCH
224 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
225 #endif /* CONFIG_FEATURE_VI_SEARCH */
228 static void edit_file(Byte *); // edit one file
229 static void do_cmd(Byte); // execute a command
230 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
231 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
232 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
233 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
234 static Byte *next_line(Byte *); // return pointer to next line B-o-l
235 static Byte *end_screen(void); // get pointer to last char on screen
236 static int count_lines(Byte *, Byte *); // count line from start to stop
237 static Byte *find_line(int); // find begining of line #li
238 static Byte *move_to_col(Byte *, int); // move "p" to column l
239 static int isblnk(Byte); // is the char a blank or tab
240 static void dot_left(void); // move dot left- dont leave line
241 static void dot_right(void); // move dot right- dont leave line
242 static void dot_begin(void); // move dot to B-o-l
243 static void dot_end(void); // move dot to E-o-l
244 static void dot_next(void); // move dot to next line B-o-l
245 static void dot_prev(void); // move dot to prev line B-o-l
246 static void dot_scroll(int, int); // move the screen up or down
247 static void dot_skip_over_ws(void); // move dot pat WS
248 static void dot_delete(void); // delete the char at 'dot'
249 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
250 static Byte *new_screen(int, int); // malloc virtual screen memory
251 static Byte *new_text(int); // malloc memory for text[] buffer
252 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
253 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
254 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
255 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
256 static Byte *skip_thing(Byte *, int, int, int); // skip some object
257 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
258 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
259 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
260 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
261 static void show_help(void); // display some help info
262 static void rawmode(void); // set "raw" mode on tty
263 static void cookmode(void); // return to "cooked" mode on tty
264 static int mysleep(int); // sleep for 'h' 1/100 seconds
265 static Byte readit(void); // read (maybe cursor) key from stdin
266 static Byte get_one_char(void); // read 1 char from stdin
267 static int file_size(const Byte *); // what is the byte size of "fn"
268 static int file_insert(Byte *, Byte *, int);
269 static int file_write(Byte *, Byte *, Byte *);
270 static void place_cursor(int, int, int);
271 static void screen_erase(void);
272 static void clear_to_eol(void);
273 static void clear_to_eos(void);
274 static void standout_start(void); // send "start reverse video" sequence
275 static void standout_end(void); // send "end reverse video" sequence
276 static void flash(int); // flash the terminal screen
277 static void show_status_line(void); // put a message on the bottom line
278 static void psb(const char *, ...); // Print Status Buf
279 static void psbs(const char *, ...); // Print Status Buf in standout mode
280 static void ni(Byte *); // display messages
281 static int format_edit_status(void); // format file status on status line
282 static void redraw(int); // force a full screen refresh
283 static void format_line(Byte*, Byte*, int);
284 static void refresh(int); // update the terminal from screen[]
286 static void Indicate_Error(void); // use flash or beep to indicate error
287 #define indicate_error(c) Indicate_Error()
288 static void Hit_Return(void);
290 #ifdef CONFIG_FEATURE_VI_SEARCH
291 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
292 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
293 #endif /* CONFIG_FEATURE_VI_SEARCH */
294 #ifdef CONFIG_FEATURE_VI_COLON
295 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
296 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
297 static void colon(Byte *); // execute the "colon" mode cmds
298 #endif /* CONFIG_FEATURE_VI_COLON */
299 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
300 static void winch_sig(int); // catch window size changes
301 static void suspend_sig(int); // catch ctrl-Z
302 static void catch_sig(int); // catch ctrl-C and alarm time-outs
303 static void core_sig(int); // catch a core dump signal
304 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
305 #ifdef CONFIG_FEATURE_VI_DOT_CMD
306 static void start_new_cmd_q(Byte); // new queue for command
307 static void end_cmd_q(void); // stop saving input chars
308 #else /* CONFIG_FEATURE_VI_DOT_CMD */
310 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
311 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
312 static void window_size_get(int); // find out what size the window is
313 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
314 #ifdef CONFIG_FEATURE_VI_SETOPTS
315 static void showmatching(Byte *); // show the matching pair () [] {}
316 #endif /* CONFIG_FEATURE_VI_SETOPTS */
317 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
318 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
319 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
320 #ifdef CONFIG_FEATURE_VI_YANKMARK
321 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
322 static Byte what_reg(void); // what is letter of current YDreg
323 static void check_context(Byte); // remember context for '' command
324 #endif /* CONFIG_FEATURE_VI_YANKMARK */
325 #ifdef CONFIG_FEATURE_VI_CRASHME
326 static void crash_dummy();
327 static void crash_test();
328 static int crashme = 0;
329 #endif /* CONFIG_FEATURE_VI_CRASHME */
332 static void write1(const char *out)
337 int vi_main(int argc, char **argv)
340 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
342 #ifdef CONFIG_FEATURE_VI_YANKMARK
344 #endif /* CONFIG_FEATURE_VI_YANKMARK */
345 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
348 #ifdef CONFIG_FEATURE_VI_CRASHME
349 (void) srand((long) my_pid);
350 #endif /* CONFIG_FEATURE_VI_CRASHME */
352 status_buffer = (Byte *)STATUS_BUFFER;
353 last_status_cksum = 0;
355 #ifdef CONFIG_FEATURE_VI_READONLY
356 vi_readonly = readonly = FALSE;
357 if (strncmp(argv[0], "view", 4) == 0) {
361 #endif /* CONFIG_FEATURE_VI_READONLY */
362 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
363 #ifdef CONFIG_FEATURE_VI_YANKMARK
364 for (i = 0; i < 28; i++) {
366 } // init the yank regs
367 #endif /* CONFIG_FEATURE_VI_YANKMARK */
368 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
369 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
370 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
372 // 1- process $HOME/.exrc file
373 // 2- process EXINIT variable from environment
374 // 3- process command line args
375 while ((c = getopt(argc, argv, "hCR")) != -1) {
377 #ifdef CONFIG_FEATURE_VI_CRASHME
381 #endif /* CONFIG_FEATURE_VI_CRASHME */
382 #ifdef CONFIG_FEATURE_VI_READONLY
383 case 'R': // Read-only flag
387 #endif /* CONFIG_FEATURE_VI_READONLY */
388 //case 'r': // recover flag- ignore- we don't use tmp file
389 //case 'x': // encryption flag- ignore
390 //case 'c': // execute command first
391 //case 'h': // help -- just use default
398 // The argv array can be used by the ":next" and ":rewind" commands
400 fn_start = optind; // remember first file name for :next and :rew
403 //----- This is the main file handling loop --------------
404 if (optind >= argc) {
405 editing = 1; // 0= exit, 1= one file, 2= multiple files
408 for (; optind < argc; optind++) {
409 editing = 1; // 0=exit, 1=one file, 2+ =many files
411 cfn = (Byte *) bb_xstrdup(argv[optind]);
415 //-----------------------------------------------------------
420 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
421 //----- See what the window size currently is --------------------
422 static inline void window_size_get(int fd)
424 get_terminal_width_height(fd, &columns, &rows);
426 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
428 static void edit_file(Byte * fn)
433 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
435 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
436 #ifdef CONFIG_FEATURE_VI_YANKMARK
437 static Byte *cur_line;
438 #endif /* CONFIG_FEATURE_VI_YANKMARK */
444 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
446 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
447 new_screen(rows, columns); // get memory for virtual screen
449 cnt = file_size(fn); // file size
450 size = 2 * cnt; // 200% of file size
451 new_text(size); // get a text[] buffer
452 screenbegin = dot = end = text;
454 ch= file_insert(fn, text, cnt);
457 (void) char_insert(text, '\n'); // start empty buf with dummy line
460 last_file_modified = -1;
461 #ifdef CONFIG_FEATURE_VI_YANKMARK
462 YDreg = 26; // default Yank/Delete reg
463 Ureg = 27; // hold orig line for "U" cmd
464 for (cnt = 0; cnt < 28; cnt++) {
467 mark[26] = mark[27] = text; // init "previous context"
468 #endif /* CONFIG_FEATURE_VI_YANKMARK */
470 last_forward_char = last_input_char = '\0';
474 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
477 signal(SIGWINCH, winch_sig);
478 signal(SIGTSTP, suspend_sig);
479 sig = setjmp(restart);
481 const char *msg = "";
484 msg = "(window resize)";
494 msg = "(I tried to touch invalid memory)";
498 psbs("-- caught signal %d %s--", sig, msg);
499 screenbegin = dot = text;
501 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
504 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
507 offset = 0; // no horizontal offset
509 #ifdef CONFIG_FEATURE_VI_DOT_CMD
510 free(last_modifying_cmd);
512 ioq = ioq_start = last_modifying_cmd = 0;
514 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
515 redraw(FALSE); // dont force every col re-draw
518 //------This is the main Vi cmd handling loop -----------------------
519 while (editing > 0) {
520 #ifdef CONFIG_FEATURE_VI_CRASHME
522 if ((end - text) > 1) {
523 crash_dummy(); // generate a random command
527 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
531 #endif /* CONFIG_FEATURE_VI_CRASHME */
532 last_input_char = c = get_one_char(); // get a cmd from user
533 #ifdef CONFIG_FEATURE_VI_YANKMARK
534 // save a copy of the current line- for the 'U" command
535 if (begin_line(dot) != cur_line) {
536 cur_line = begin_line(dot);
537 text_yank(begin_line(dot), end_line(dot), Ureg);
539 #endif /* CONFIG_FEATURE_VI_YANKMARK */
540 #ifdef CONFIG_FEATURE_VI_DOT_CMD
541 // These are commands that change text[].
542 // Remember the input for the "." command
543 if (!adding2q && ioq_start == 0
544 && strchr((char *) modifying_cmds, c) != NULL) {
547 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
548 do_cmd(c); // execute the user command
550 // poll to see if there is input already waiting. if we are
551 // not able to display output fast enough to keep up, skip
552 // the display update until we catch up with input.
553 if (mysleep(0) == 0) {
554 // no input pending- so update output
558 #ifdef CONFIG_FEATURE_VI_CRASHME
560 crash_test(); // test editor variables
561 #endif /* CONFIG_FEATURE_VI_CRASHME */
563 //-------------------------------------------------------------------
565 place_cursor(rows, 0, FALSE); // go to bottom of screen
566 clear_to_eol(); // Erase to end of line
570 //----- The Colon commands -------------------------------------
571 #ifdef CONFIG_FEATURE_VI_COLON
572 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
577 #ifdef CONFIG_FEATURE_VI_YANKMARK
579 #endif /* CONFIG_FEATURE_VI_YANKMARK */
580 #ifdef CONFIG_FEATURE_VI_SEARCH
581 Byte *pat, buf[BUFSIZ];
582 #endif /* CONFIG_FEATURE_VI_SEARCH */
584 *addr = -1; // assume no addr
585 if (*p == '.') { // the current line
588 *addr = count_lines(text, q);
589 #ifdef CONFIG_FEATURE_VI_YANKMARK
590 } else if (*p == '\'') { // is this a mark addr
594 if (c >= 'a' && c <= 'z') {
598 if (q != NULL) { // is mark valid
599 *addr = count_lines(text, q); // count lines
602 #endif /* CONFIG_FEATURE_VI_YANKMARK */
603 #ifdef CONFIG_FEATURE_VI_SEARCH
604 } else if (*p == '/') { // a search pattern
612 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
615 q = char_search(dot, pat, FORWARD, FULL);
617 *addr = count_lines(text, q);
620 #endif /* CONFIG_FEATURE_VI_SEARCH */
621 } else if (*p == '$') { // the last line in file
623 q = begin_line(end - 1);
624 *addr = count_lines(text, q);
625 } else if (isdigit(*p)) { // specific line number
626 sscanf((char *) p, "%d%n", addr, &st);
628 } else { // I don't reconise this
629 // unrecognised address- assume -1
635 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
637 //----- get the address' i.e., 1,3 'a,'b -----
638 // get FIRST addr, if present
640 p++; // skip over leading spaces
641 if (*p == '%') { // alias for 1,$
644 *e = count_lines(text, end-1);
647 p = get_one_address(p, b);
650 if (*p == ',') { // is there a address separator
654 // get SECOND addr, if present
655 p = get_one_address(p, e);
659 p++; // skip over trailing spaces
663 #ifdef CONFIG_FEATURE_VI_SETOPTS
664 static void setops(const Byte *args, const char *opname, int flg_no,
665 const char *short_opname, int opt)
667 const char *a = (char *) args + flg_no;
668 int l = strlen(opname) - 1; /* opname have + ' ' */
670 if (strncasecmp(a, opname, l) == 0 ||
671 strncasecmp(a, short_opname, 2) == 0) {
680 static void colon(Byte * buf)
682 Byte c, *orig_buf, *buf1, *q, *r;
683 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
684 int i, l, li, ch, st, b, e;
685 int useforce = FALSE, forced = FALSE;
688 // :3154 // if (-e line 3154) goto it else stay put
689 // :4,33w! foo // write a portion of buffer to file "foo"
690 // :w // write all of buffer to current file
692 // :q! // quit- dont care about modified file
693 // :'a,'z!sort -u // filter block through sort
694 // :'f // goto mark "f"
695 // :'fl // list literal the mark "f" line
696 // :.r bar // read file "bar" into buffer before dot
697 // :/123/,/abc/d // delete lines from "123" line to "abc" line
698 // :/xyz/ // goto the "xyz" line
699 // :s/find/replace/ // substitute pattern "find" with "replace"
700 // :!<cmd> // run <cmd> then return
703 if (strlen((char *) buf) <= 0)
706 buf++; // move past the ':'
708 li = st = ch = i = 0;
710 q = text; // assume 1,$ for the range
712 li = count_lines(text, end - 1);
713 fn = cfn; // default to current file
714 memset(cmd, '\0', BUFSIZ); // clear cmd[]
715 memset(args, '\0', BUFSIZ); // clear args[]
717 // look for optional address(es) :. :1 :1,9 :'q,'a :%
718 buf = get_address(buf, &b, &e);
720 // remember orig command line
723 // get the COMMAND into cmd[]
725 while (*buf != '\0') {
733 strcpy((char *) args, (char *) buf);
734 buf1 = (Byte*)last_char_is((char *)cmd, '!');
737 *buf1 = '\0'; // get rid of !
740 // if there is only one addr, then the addr
741 // is the line number of the single line the
742 // user wants. So, reset the end
743 // pointer to point at end of the "b" line
744 q = find_line(b); // what line is #b
749 // we were given two addrs. change the
750 // end pointer to the addr given by user.
751 r = find_line(e); // what line is #e
755 // ------------ now look for the command ------------
756 i = strlen((char *) cmd);
757 if (i == 0) { // :123CR goto line #123
759 dot = find_line(b); // what line is #b
762 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
763 // :!ls run the <cmd>
764 (void) alarm(0); // wait for input- no alarms
765 place_cursor(rows - 1, 0, FALSE); // go to Status line
766 clear_to_eol(); // clear the line
768 system((char*)(orig_buf+1)); // run the cmd
770 Hit_Return(); // let user see results
771 (void) alarm(3); // done waiting for input
772 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
773 if (b < 0) { // no addr given- use defaults
774 b = e = count_lines(text, dot);
777 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
778 if (b < 0) { // no addr given- use defaults
779 q = begin_line(dot); // assume .,. for the range
782 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
784 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
787 // don't edit, if the current file has been modified
788 if (file_modified && ! useforce) {
789 psbs("No write since last change (:edit! overrides)");
792 if (strlen((char*)args) > 0) {
793 // the user supplied a file name
795 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
796 // no user supplied name- use the current filename
800 // no user file name, no current name- punt
801 psbs("No current filename");
805 // see if file exists- if not, its just a new file request
806 if ((sr=stat((char*)fn, &st_buf)) < 0) {
807 // This is just a request for a new file creation.
808 // The file_insert below will fail but we get
809 // an empty buffer with a file name. Then the "write"
810 // command can do the create.
812 if ((st_buf.st_mode & (S_IFREG)) == 0) {
813 // This is not a regular file
814 psbs("\"%s\" is not a regular file", fn);
817 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
818 // dont have any read permissions
819 psbs("\"%s\" is not readable", fn);
824 // There is a read-able regular file
825 // make this the current file
826 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
827 free(cfn); // free the old name
828 cfn = q; // remember new cfn
831 // delete all the contents of text[]
832 new_text(2 * file_size(fn));
833 screenbegin = dot = end = text;
836 ch = file_insert(fn, text, file_size(fn));
839 // start empty buf with dummy line
840 (void) char_insert(text, '\n');
844 last_file_modified = -1;
845 #ifdef CONFIG_FEATURE_VI_YANKMARK
846 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
847 free(reg[Ureg]); // free orig line reg- for 'U'
850 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
851 free(reg[YDreg]); // free default yank/delete register
854 for (li = 0; li < 28; li++) {
857 #endif /* CONFIG_FEATURE_VI_YANKMARK */
858 // how many lines in text[]?
859 li = count_lines(text, end - 1);
861 #ifdef CONFIG_FEATURE_VI_READONLY
863 #endif /* CONFIG_FEATURE_VI_READONLY */
865 (sr < 0 ? " [New file]" : ""),
866 #ifdef CONFIG_FEATURE_VI_READONLY
867 ((vi_readonly || readonly) ? " [Read only]" : ""),
868 #endif /* CONFIG_FEATURE_VI_READONLY */
870 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
871 if (b != -1 || e != -1) {
872 ni((Byte *) "No address allowed on this command");
875 if (strlen((char *) args) > 0) {
876 // user wants a new filename
878 cfn = (Byte *) bb_xstrdup((char *) args);
880 // user wants file status info
881 last_status_cksum = 0; // force status update
883 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
884 // print out values of all features
885 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
886 clear_to_eol(); // clear the line
891 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
892 if (b < 0) { // no addr given- use defaults
893 q = begin_line(dot); // assume .,. for the range
896 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
897 clear_to_eol(); // clear the line
899 for (; q <= r; q++) {
903 c_is_no_print = c > 127 && !Isprint(c);
910 } else if (c < ' ' || c == 127) {
921 #ifdef CONFIG_FEATURE_VI_SET
923 #endif /* CONFIG_FEATURE_VI_SET */
925 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
926 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
928 // force end of argv list
935 // don't exit if the file been modified
937 psbs("No write since last change (:%s! overrides)",
938 (*cmd == 'q' ? "quit" : "next"));
941 // are there other file to edit
942 if (*cmd == 'q' && optind < save_argc - 1) {
943 psbs("%d more file to edit", (save_argc - optind - 1));
946 if (*cmd == 'n' && optind >= save_argc - 1) {
947 psbs("No more files to edit");
951 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
953 if (strlen((char *) fn) <= 0) {
954 psbs("No filename given");
957 if (b < 0) { // no addr given- use defaults
958 q = begin_line(dot); // assume "dot"
960 // read after current line- unless user said ":0r foo"
963 #ifdef CONFIG_FEATURE_VI_READONLY
964 l= readonly; // remember current files' status
966 ch = file_insert(fn, q, file_size(fn));
967 #ifdef CONFIG_FEATURE_VI_READONLY
971 goto vc1; // nothing was inserted
972 // how many lines in text[]?
973 li = count_lines(q, q + ch - 1);
975 #ifdef CONFIG_FEATURE_VI_READONLY
977 #endif /* CONFIG_FEATURE_VI_READONLY */
979 #ifdef CONFIG_FEATURE_VI_READONLY
980 ((vi_readonly || readonly) ? " [Read only]" : ""),
981 #endif /* CONFIG_FEATURE_VI_READONLY */
984 // if the insert is before "dot" then we need to update
989 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
990 if (file_modified && ! useforce) {
991 psbs("No write since last change (:rewind! overrides)");
993 // reset the filenames to edit
994 optind = fn_start - 1;
997 #ifdef CONFIG_FEATURE_VI_SET
998 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
999 i = 0; // offset into args
1000 if (strlen((char *) args) == 0) {
1001 // print out values of all options
1002 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1003 clear_to_eol(); // clear the line
1004 printf("----------------------------------------\r\n");
1005 #ifdef CONFIG_FEATURE_VI_SETOPTS
1008 printf("autoindent ");
1014 printf("ignorecase ");
1017 printf("showmatch ");
1018 printf("tabstop=%d ", tabstop);
1019 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1023 if (strncasecmp((char *) args, "no", 2) == 0)
1024 i = 2; // ":set noautoindent"
1025 #ifdef CONFIG_FEATURE_VI_SETOPTS
1026 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1027 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1028 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1029 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1030 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1031 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1032 if (ch > 0 && ch < columns - 1)
1035 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1036 #endif /* CONFIG_FEATURE_VI_SET */
1037 #ifdef CONFIG_FEATURE_VI_SEARCH
1038 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1042 // F points to the "find" pattern
1043 // R points to the "replace" pattern
1044 // replace the cmd line delimiters "/" with NULLs
1045 gflag = 0; // global replace flag
1046 c = orig_buf[1]; // what is the delimiter
1047 F = orig_buf + 2; // start of "find"
1048 R = (Byte *) strchr((char *) F, c); // middle delimiter
1049 if (!R) goto colon_s_fail;
1050 *R++ = '\0'; // terminate "find"
1051 buf1 = (Byte *) strchr((char *) R, c);
1052 if (!buf1) goto colon_s_fail;
1053 *buf1++ = '\0'; // terminate "replace"
1054 if (*buf1 == 'g') { // :s/foo/bar/g
1056 gflag++; // turn on gflag
1059 if (b < 0) { // maybe :s/foo/bar/
1060 q = begin_line(dot); // start with cur line
1061 b = count_lines(text, q); // cur line number
1064 e = b; // maybe :.s/foo/bar/
1065 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1066 ls = q; // orig line start
1068 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1070 // we found the "find" pattern- delete it
1071 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1072 // inset the "replace" patern
1073 (void) string_insert(buf1, R); // insert the string
1074 // check for "global" :s/foo/bar/g
1076 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1077 q = buf1 + strlen((char *) R);
1078 goto vc4; // don't let q move past cur line
1084 #endif /* CONFIG_FEATURE_VI_SEARCH */
1085 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1086 psb("%s", vi_Version);
1087 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1088 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1089 (strncasecmp((char *) cmd, "x", i) == 0)) {
1090 // is there a file name to write to?
1091 if (strlen((char *) args) > 0) {
1094 #ifdef CONFIG_FEATURE_VI_READONLY
1095 if ((vi_readonly || readonly) && ! useforce) {
1096 psbs("\"%s\" File is read only", fn);
1099 #endif /* CONFIG_FEATURE_VI_READONLY */
1100 // how many lines in text[]?
1101 li = count_lines(q, r);
1103 // see if file exists- if not, its just a new file request
1105 // if "fn" is not write-able, chmod u+w
1106 // sprintf(syscmd, "chmod u+w %s", fn);
1110 l = file_write(fn, q, r);
1111 if (useforce && forced) {
1113 // sprintf(syscmd, "chmod u-w %s", fn);
1119 psbs("Write error: %s", strerror(errno));
1121 psb("\"%s\" %dL, %dC", fn, li, l);
1122 if (q == text && r == end - 1 && l == ch) {
1124 last_file_modified = -1;
1126 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1130 #ifdef CONFIG_FEATURE_VI_READONLY
1132 #endif /* CONFIG_FEATURE_VI_READONLY */
1133 #ifdef CONFIG_FEATURE_VI_YANKMARK
1134 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1135 if (b < 0) { // no addr given- use defaults
1136 q = begin_line(dot); // assume .,. for the range
1139 text_yank(q, r, YDreg);
1140 li = count_lines(q, r);
1141 psb("Yank %d lines (%d chars) into [%c]",
1142 li, strlen((char *) reg[YDreg]), what_reg());
1143 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1149 dot = bound_dot(dot); // make sure "dot" is valid
1151 #ifdef CONFIG_FEATURE_VI_SEARCH
1153 psb(":s expression missing delimiters");
1157 #endif /* CONFIG_FEATURE_VI_COLON */
1159 static void Hit_Return(void)
1163 standout_start(); // start reverse video
1164 write1("[Hit return to continue]");
1165 standout_end(); // end reverse video
1166 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1168 redraw(TRUE); // force redraw all
1171 //----- Synchronize the cursor to Dot --------------------------
1172 static void sync_cursor(Byte * d, int *row, int *col)
1174 Byte *beg_cur, *end_cur; // begin and end of "d" line
1175 Byte *beg_scr, *end_scr; // begin and end of screen
1179 beg_cur = begin_line(d); // first char of cur line
1180 end_cur = end_line(d); // last char of cur line
1182 beg_scr = end_scr = screenbegin; // first char of screen
1183 end_scr = end_screen(); // last char of screen
1185 if (beg_cur < screenbegin) {
1186 // "d" is before top line on screen
1187 // how many lines do we have to move
1188 cnt = count_lines(beg_cur, screenbegin);
1190 screenbegin = beg_cur;
1191 if (cnt > (rows - 1) / 2) {
1192 // we moved too many lines. put "dot" in middle of screen
1193 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1194 screenbegin = prev_line(screenbegin);
1197 } else if (beg_cur > end_scr) {
1198 // "d" is after bottom line on screen
1199 // how many lines do we have to move
1200 cnt = count_lines(end_scr, beg_cur);
1201 if (cnt > (rows - 1) / 2)
1202 goto sc1; // too many lines
1203 for (ro = 0; ro < cnt - 1; ro++) {
1204 // move screen begin the same amount
1205 screenbegin = next_line(screenbegin);
1206 // now, move the end of screen
1207 end_scr = next_line(end_scr);
1208 end_scr = end_line(end_scr);
1211 // "d" is on screen- find out which row
1213 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1219 // find out what col "d" is on
1221 do { // drive "co" to correct column
1222 if (*tp == '\n' || *tp == '\0')
1226 co += ((tabstop - 1) - (co % tabstop));
1227 } else if (*tp < ' ' || *tp == 127) {
1228 co++; // display as ^X, use 2 columns
1230 } while (tp++ < d && ++co);
1232 // "co" is the column where "dot" is.
1233 // The screen has "columns" columns.
1234 // The currently displayed columns are 0+offset -- columns+ofset
1235 // |-------------------------------------------------------------|
1237 // offset | |------- columns ----------------|
1239 // If "co" is already in this range then we do not have to adjust offset
1240 // but, we do have to subtract the "offset" bias from "co".
1241 // If "co" is outside this range then we have to change "offset".
1242 // If the first char of a line is a tab the cursor will try to stay
1243 // in column 7, but we have to set offset to 0.
1245 if (co < 0 + offset) {
1248 if (co >= columns + offset) {
1249 offset = co - columns + 1;
1251 // if the first char of the line is a tab, and "dot" is sitting on it
1252 // force offset to 0.
1253 if (d == beg_cur && *d == '\t') {
1262 //----- Text Movement Routines ---------------------------------
1263 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1265 while (p > text && p[-1] != '\n')
1266 p--; // go to cur line B-o-l
1270 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1272 while (p < end - 1 && *p != '\n')
1273 p++; // go to cur line E-o-l
1277 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1279 while (p < end - 1 && *p != '\n')
1280 p++; // go to cur line E-o-l
1281 // Try to stay off of the Newline
1282 if (*p == '\n' && (p - begin_line(p)) > 0)
1287 static Byte *prev_line(Byte * p) // return pointer first char prev line
1289 p = begin_line(p); // goto begining of cur line
1290 if (p[-1] == '\n' && p > text)
1291 p--; // step to prev line
1292 p = begin_line(p); // goto begining of prev line
1296 static Byte *next_line(Byte * p) // return pointer first char next line
1299 if (*p == '\n' && p < end - 1)
1300 p++; // step to next line
1304 //----- Text Information Routines ------------------------------
1305 static Byte *end_screen(void)
1310 // find new bottom line
1312 for (cnt = 0; cnt < rows - 2; cnt++)
1318 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1323 if (stop < start) { // start and stop are backwards- reverse them
1329 stop = end_line(stop); // get to end of this line
1330 for (q = start; q <= stop && q <= end - 1; q++) {
1337 static Byte *find_line(int li) // find begining of line #li
1341 for (q = text; li > 1; li--) {
1347 //----- Dot Movement Routines ----------------------------------
1348 static void dot_left(void)
1350 if (dot > text && dot[-1] != '\n')
1354 static void dot_right(void)
1356 if (dot < end - 1 && *dot != '\n')
1360 static void dot_begin(void)
1362 dot = begin_line(dot); // return pointer to first char cur line
1365 static void dot_end(void)
1367 dot = end_line(dot); // return pointer to last char cur line
1370 static Byte *move_to_col(Byte * p, int l)
1377 if (*p == '\n' || *p == '\0')
1381 co += ((tabstop - 1) - (co % tabstop));
1382 } else if (*p < ' ' || *p == 127) {
1383 co++; // display as ^X, use 2 columns
1385 } while (++co <= l && p++ < end);
1389 static void dot_next(void)
1391 dot = next_line(dot);
1394 static void dot_prev(void)
1396 dot = prev_line(dot);
1399 static void dot_scroll(int cnt, int dir)
1403 for (; cnt > 0; cnt--) {
1406 // ctrl-Y scroll up one line
1407 screenbegin = prev_line(screenbegin);
1410 // ctrl-E scroll down one line
1411 screenbegin = next_line(screenbegin);
1414 // make sure "dot" stays on the screen so we dont scroll off
1415 if (dot < screenbegin)
1417 q = end_screen(); // find new bottom line
1419 dot = begin_line(q); // is dot is below bottom line?
1423 static void dot_skip_over_ws(void)
1426 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1430 static void dot_delete(void) // delete the char at 'dot'
1432 (void) text_hole_delete(dot, dot);
1435 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1437 if (p >= end && end > text) {
1439 indicate_error('1');
1443 indicate_error('2');
1448 //----- Helper Utility Routines --------------------------------
1450 //----------------------------------------------------------------
1451 //----- Char Routines --------------------------------------------
1452 /* Chars that are part of a word-
1453 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1454 * Chars that are Not part of a word (stoppers)
1455 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1456 * Chars that are WhiteSpace
1457 * TAB NEWLINE VT FF RETURN SPACE
1458 * DO NOT COUNT NEWLINE AS WHITESPACE
1461 static Byte *new_screen(int ro, int co)
1466 screensize = ro * co + 8;
1467 screen = (Byte *) xmalloc(screensize);
1468 // initialize the new screen. assume this will be a empty file.
1470 // non-existent text[] lines start with a tilde (~).
1471 for (li = 1; li < ro - 1; li++) {
1472 screen[(li * co) + 0] = '~';
1477 static Byte *new_text(int size)
1480 size = 10240; // have a minimum size for new files
1482 text = (Byte *) xmalloc(size + 8);
1483 memset(text, '\0', size); // clear new text[]
1484 //text += 4; // leave some room for "oops"
1485 textend = text + size - 1;
1486 //textend -= 4; // leave some root for "oops"
1490 #ifdef CONFIG_FEATURE_VI_SEARCH
1491 static int mycmp(Byte * s1, Byte * s2, int len)
1495 i = strncmp((char *) s1, (char *) s2, len);
1496 #ifdef CONFIG_FEATURE_VI_SETOPTS
1498 i = strncasecmp((char *) s1, (char *) s2, len);
1500 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1504 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1506 #ifndef REGEX_SEARCH
1510 len = strlen((char *) pat);
1511 if (dir == FORWARD) {
1512 stop = end - 1; // assume range is p - end-1
1513 if (range == LIMITED)
1514 stop = next_line(p); // range is to next line
1515 for (start = p; start < stop; start++) {
1516 if (mycmp(start, pat, len) == 0) {
1520 } else if (dir == BACK) {
1521 stop = text; // assume range is text - p
1522 if (range == LIMITED)
1523 stop = prev_line(p); // range is to prev line
1524 for (start = p - len; start >= stop; start--) {
1525 if (mycmp(start, pat, len) == 0) {
1530 // pattern not found
1532 #else /*REGEX_SEARCH */
1534 struct re_pattern_buffer preg;
1538 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1544 // assume a LIMITED forward search
1552 // count the number of chars to search over, forward or backward
1556 // RANGE could be negative if we are searching backwards
1559 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1561 // The pattern was not compiled
1562 psbs("bad search pattern: \"%s\": %s", pat, q);
1563 i = 0; // return p if pattern not compiled
1573 // search for the compiled pattern, preg, in p[]
1574 // range < 0- search backward
1575 // range > 0- search forward
1577 // re_search() < 0 not found or error
1578 // re_search() > 0 index of found pattern
1579 // struct pattern char int int int struct reg
1580 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1581 i = re_search(&preg, q, size, 0, range, 0);
1584 i = 0; // return NULL if pattern not found
1587 if (dir == FORWARD) {
1593 #endif /*REGEX_SEARCH */
1595 #endif /* CONFIG_FEATURE_VI_SEARCH */
1597 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1599 if (c == 22) { // Is this an ctrl-V?
1600 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1601 p--; // backup onto ^
1602 refresh(FALSE); // show the ^
1606 file_modified++; // has the file been modified
1607 } else if (c == 27) { // Is this an ESC?
1610 end_cmd_q(); // stop adding to q
1611 last_status_cksum = 0; // force status update
1612 if ((p[-1] != '\n') && (dot>text)) {
1615 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1617 if ((p[-1] != '\n') && (dot>text)) {
1619 p = text_hole_delete(p, p); // shrink buffer 1 char
1622 // insert a char into text[]
1623 Byte *sp; // "save p"
1626 c = '\n'; // translate \r to \n
1627 sp = p; // remember addr of insert
1628 p = stupid_insert(p, c); // insert the char
1629 #ifdef CONFIG_FEATURE_VI_SETOPTS
1630 if (showmatch && strchr(")]}", *sp) != NULL) {
1633 if (autoindent && c == '\n') { // auto indent the new line
1636 q = prev_line(p); // use prev line as templet
1637 for (; isblnk(*q); q++) {
1638 p = stupid_insert(p, *q); // insert the char
1641 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1646 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1648 p = text_hole_make(p, 1);
1651 file_modified++; // has the file been modified
1657 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1659 Byte *save_dot, *p, *q;
1665 if (strchr("cdy><", c)) {
1666 // these cmds operate on whole lines
1667 p = q = begin_line(p);
1668 for (cnt = 1; cnt < cmdcnt; cnt++) {
1672 } else if (strchr("^%$0bBeEft", c)) {
1673 // These cmds operate on char positions
1674 do_cmd(c); // execute movement cmd
1676 } else if (strchr("wW", c)) {
1677 do_cmd(c); // execute movement cmd
1678 // if we are at the next word's first char
1679 // step back one char
1680 // but check the possibilities when it is true
1681 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1682 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1683 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1684 dot--; // move back off of next word
1685 if (dot > text && *dot == '\n')
1686 dot--; // stay off NL
1688 } else if (strchr("H-k{", c)) {
1689 // these operate on multi-lines backwards
1690 q = end_line(dot); // find NL
1691 do_cmd(c); // execute movement cmd
1694 } else if (strchr("L+j}\r\n", c)) {
1695 // these operate on multi-lines forwards
1696 p = begin_line(dot);
1697 do_cmd(c); // execute movement cmd
1698 dot_end(); // find NL
1701 c = 27; // error- return an ESC char
1714 static int st_test(Byte * p, int type, int dir, Byte * tested)
1724 if (type == S_BEFORE_WS) {
1726 test = ((!isspace(c)) || c == '\n');
1728 if (type == S_TO_WS) {
1730 test = ((!isspace(c)) || c == '\n');
1732 if (type == S_OVER_WS) {
1734 test = ((isspace(c)));
1736 if (type == S_END_PUNCT) {
1738 test = ((ispunct(c)));
1740 if (type == S_END_ALNUM) {
1742 test = ((isalnum(c)) || c == '_');
1748 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1752 while (st_test(p, type, dir, &c)) {
1753 // make sure we limit search to correct number of lines
1754 if (c == '\n' && --linecnt < 1)
1756 if (dir >= 0 && p >= end - 1)
1758 if (dir < 0 && p <= text)
1760 p += dir; // move to next char
1765 // find matching char of pair () [] {}
1766 static Byte *find_pair(Byte * p, Byte c)
1773 dir = 1; // assume forward
1797 for (q = p + dir; text <= q && q < end; q += dir) {
1798 // look for match, count levels of pairs (( ))
1800 level++; // increase pair levels
1802 level--; // reduce pair level
1804 break; // found matching pair
1807 q = NULL; // indicate no match
1811 #ifdef CONFIG_FEATURE_VI_SETOPTS
1812 // show the matching char of a pair, () [] {}
1813 static void showmatching(Byte * p)
1817 // we found half of a pair
1818 q = find_pair(p, *p); // get loc of matching char
1820 indicate_error('3'); // no matching char
1822 // "q" now points to matching pair
1823 save_dot = dot; // remember where we are
1824 dot = q; // go to new loc
1825 refresh(FALSE); // let the user see it
1826 (void) mysleep(40); // give user some time
1827 dot = save_dot; // go back to old loc
1831 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1833 // open a hole in text[]
1834 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1843 cnt = end - src; // the rest of buffer
1844 if (memmove(dest, src, cnt) != dest) {
1845 psbs("can't create room for new characters");
1847 memset(p, ' ', size); // clear new hole
1848 end = end + size; // adjust the new END
1849 file_modified++; // has the file been modified
1854 // close a hole in text[]
1855 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1860 // move forwards, from beginning
1864 if (q < p) { // they are backward- swap them
1868 hole_size = q - p + 1;
1870 if (src < text || src > end)
1872 if (dest < text || dest >= end)
1875 goto thd_atend; // just delete the end of the buffer
1876 if (memmove(dest, src, cnt) != dest) {
1877 psbs("can't delete the character");
1880 end = end - hole_size; // adjust the new END
1882 dest = end - 1; // make sure dest in below end-1
1884 dest = end = text; // keep pointers valid
1885 file_modified++; // has the file been modified
1890 // copy text into register, then delete text.
1891 // if dist <= 0, do not include, or go past, a NewLine
1893 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1897 // make sure start <= stop
1899 // they are backwards, reverse them
1905 // we can not cross NL boundaries
1909 // dont go past a NewLine
1910 for (; p + 1 <= stop; p++) {
1912 stop = p; // "stop" just before NewLine
1918 #ifdef CONFIG_FEATURE_VI_YANKMARK
1919 text_yank(start, stop, YDreg);
1920 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1921 if (yf == YANKDEL) {
1922 p = text_hole_delete(start, stop);
1927 static void show_help(void)
1929 puts("These features are available:"
1930 #ifdef CONFIG_FEATURE_VI_SEARCH
1931 "\n\tPattern searches with / and ?"
1932 #endif /* CONFIG_FEATURE_VI_SEARCH */
1933 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1934 "\n\tLast command repeat with \'.\'"
1935 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1936 #ifdef CONFIG_FEATURE_VI_YANKMARK
1937 "\n\tLine marking with 'x"
1938 "\n\tNamed buffers with \"x"
1939 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1940 #ifdef CONFIG_FEATURE_VI_READONLY
1941 "\n\tReadonly if vi is called as \"view\""
1942 "\n\tReadonly with -R command line arg"
1943 #endif /* CONFIG_FEATURE_VI_READONLY */
1944 #ifdef CONFIG_FEATURE_VI_SET
1945 "\n\tSome colon mode commands with \':\'"
1946 #endif /* CONFIG_FEATURE_VI_SET */
1947 #ifdef CONFIG_FEATURE_VI_SETOPTS
1948 "\n\tSettable options with \":set\""
1949 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1950 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1951 "\n\tSignal catching- ^C"
1952 "\n\tJob suspend and resume with ^Z"
1953 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1954 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1955 "\n\tAdapt to window re-sizes"
1956 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1960 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1965 strcpy((char *) buf, ""); // init buf
1966 if (strlen((char *) s) <= 0)
1967 s = (Byte *) "(NULL)";
1968 for (; *s > '\0'; s++) {
1972 c_is_no_print = c > 127 && !Isprint(c);
1973 if (c_is_no_print) {
1974 strcat((char *) buf, SOn);
1977 if (c < ' ' || c == 127) {
1978 strcat((char *) buf, "^");
1985 strcat((char *) buf, (char *) b);
1987 strcat((char *) buf, SOs);
1989 strcat((char *) buf, "$");
1994 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1995 static void start_new_cmd_q(Byte c)
1998 free(last_modifying_cmd);
1999 // get buffer for new cmd
2000 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
2001 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2002 // if there is a current cmd count put it in the buffer first
2004 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
2005 else // just save char c onto queue
2006 last_modifying_cmd[0] = c;
2010 static void end_cmd_q(void)
2012 #ifdef CONFIG_FEATURE_VI_YANKMARK
2013 YDreg = 26; // go back to default Yank/Delete reg
2014 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2018 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2020 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2021 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2025 i = strlen((char *) s);
2026 p = text_hole_make(p, i);
2027 strncpy((char *) p, (char *) s, i);
2028 for (cnt = 0; *s != '\0'; s++) {
2032 #ifdef CONFIG_FEATURE_VI_YANKMARK
2033 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2034 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2037 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2039 #ifdef CONFIG_FEATURE_VI_YANKMARK
2040 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2045 if (q < p) { // they are backwards- reverse them
2052 free(t); // if already a yank register, free it
2053 t = (Byte *) xmalloc(cnt + 1); // get a new register
2054 memset(t, '\0', cnt + 1); // clear new text[]
2055 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2060 static Byte what_reg(void)
2066 c = 'D'; // default to D-reg
2067 if (0 <= YDreg && YDreg <= 25)
2068 c = 'a' + (Byte) YDreg;
2076 static void check_context(Byte cmd)
2078 // A context is defined to be "modifying text"
2079 // Any modifying command establishes a new context.
2081 if (dot < context_start || dot > context_end) {
2082 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2083 // we are trying to modify text[]- make this the current context
2084 mark[27] = mark[26]; // move cur to prev
2085 mark[26] = dot; // move local to cur
2086 context_start = prev_line(prev_line(dot));
2087 context_end = next_line(next_line(dot));
2088 //loiter= start_loiter= now;
2094 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2098 // the current context is in mark[26]
2099 // the previous context is in mark[27]
2100 // only swap context if other context is valid
2101 if (text <= mark[27] && mark[27] <= end - 1) {
2103 mark[27] = mark[26];
2105 p = mark[26]; // where we are going- previous context
2106 context_start = prev_line(prev_line(prev_line(p)));
2107 context_end = next_line(next_line(next_line(p)));
2111 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2113 static int isblnk(Byte c) // is the char a blank or tab
2115 return (c == ' ' || c == '\t');
2118 //----- Set terminal attributes --------------------------------
2119 static void rawmode(void)
2121 tcgetattr(0, &term_orig);
2122 term_vi = term_orig;
2123 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2124 term_vi.c_iflag &= (~IXON & ~ICRNL);
2125 term_vi.c_oflag &= (~ONLCR);
2126 term_vi.c_cc[VMIN] = 1;
2127 term_vi.c_cc[VTIME] = 0;
2128 erase_char = term_vi.c_cc[VERASE];
2129 tcsetattr(0, TCSANOW, &term_vi);
2132 static void cookmode(void)
2135 tcsetattr(0, TCSANOW, &term_orig);
2138 //----- Come here when we get a window resize signal ---------
2139 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2140 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2142 signal(SIGWINCH, winch_sig);
2143 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2145 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2146 new_screen(rows, columns); // get memory for virtual screen
2147 redraw(TRUE); // re-draw the screen
2150 //----- Come here when we get a continue signal -------------------
2151 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2153 rawmode(); // terminal to "raw"
2154 last_status_cksum = 0; // force status update
2155 redraw(TRUE); // re-draw the screen
2157 signal(SIGTSTP, suspend_sig);
2158 signal(SIGCONT, SIG_DFL);
2159 kill(my_pid, SIGCONT);
2162 //----- Come here when we get a Suspend signal -------------------
2163 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2165 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2166 clear_to_eol(); // Erase to end of line
2167 cookmode(); // terminal to "cooked"
2169 signal(SIGCONT, cont_sig);
2170 signal(SIGTSTP, SIG_DFL);
2171 kill(my_pid, SIGTSTP);
2174 //----- Come here when we get a signal ---------------------------
2175 static void catch_sig(int sig)
2177 signal(SIGHUP, catch_sig);
2178 signal(SIGINT, catch_sig);
2179 signal(SIGTERM, catch_sig);
2180 signal(SIGALRM, catch_sig);
2182 longjmp(restart, sig);
2185 //----- Come here when we get a core dump signal -----------------
2186 static void core_sig(int sig)
2188 signal(SIGQUIT, core_sig);
2189 signal(SIGILL, core_sig);
2190 signal(SIGTRAP, core_sig);
2191 signal(SIGIOT, core_sig);
2192 signal(SIGABRT, core_sig);
2193 signal(SIGFPE, core_sig);
2194 signal(SIGBUS, core_sig);
2195 signal(SIGSEGV, core_sig);
2197 signal(SIGSYS, core_sig);
2200 if(sig) { // signaled
2201 dot = bound_dot(dot); // make sure "dot" is valid
2202 longjmp(restart, sig);
2205 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2207 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2209 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2214 tv.tv_usec = hund * 10000;
2215 select(1, &rfds, NULL, NULL, &tv);
2216 return (FD_ISSET(0, &rfds));
2219 #define readbuffer bb_common_bufsiz1
2221 static int readed_for_parse;
2223 //----- IO Routines --------------------------------------------
2224 static Byte readit(void) // read (maybe cursor) key from stdin
2233 static const struct esc_cmds esccmds[] = {
2234 {"OA", (Byte) VI_K_UP}, // cursor key Up
2235 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2236 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2237 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2238 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2239 {"OF", (Byte) VI_K_END}, // Cursor Key End
2240 {"[A", (Byte) VI_K_UP}, // cursor key Up
2241 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2242 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2243 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2244 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2245 {"[F", (Byte) VI_K_END}, // Cursor Key End
2246 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2247 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2248 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2249 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2250 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2251 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2252 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2253 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2254 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2255 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2256 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2257 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2258 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2259 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2260 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2261 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2262 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2263 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2264 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2265 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2266 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2269 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2271 (void) alarm(0); // turn alarm OFF while we wait for input
2273 n = readed_for_parse;
2274 // get input from User- are there already input chars in Q?
2277 // the Q is empty, wait for a typed char
2278 n = read(0, readbuffer, BUFSIZ - 1);
2281 goto ri0; // interrupted sys call
2284 if (errno == EFAULT)
2286 if (errno == EINVAL)
2294 if (readbuffer[0] == 27) {
2295 // This is an ESC char. Is this Esc sequence?
2296 // Could be bare Esc key. See if there are any
2297 // more chars to read after the ESC. This would
2298 // be a Function or Cursor Key sequence.
2302 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2304 // keep reading while there are input chars and room in buffer
2305 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2306 // read the rest of the ESC string
2307 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2313 readed_for_parse = n;
2316 if(c == 27 && n > 1) {
2317 // Maybe cursor or function key?
2318 const struct esc_cmds *eindex;
2320 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2321 int cnt = strlen(eindex->seq);
2325 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2327 // is a Cursor key- put derived value back into Q
2329 // for squeeze out the ESC sequence
2333 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2334 /* defined ESC sequence not found, set only one ESC */
2340 // remove key sequence from Q
2341 readed_for_parse -= n;
2342 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2343 (void) alarm(3); // we are done waiting for input, turn alarm ON
2347 //----- IO Routines --------------------------------------------
2348 static Byte get_one_char(void)
2352 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2353 // ! adding2q && ioq == 0 read()
2354 // ! adding2q && ioq != 0 *ioq
2355 // adding2q *last_modifying_cmd= read()
2357 // we are not adding to the q.
2358 // but, we may be reading from a q
2360 // there is no current q, read from STDIN
2361 c = readit(); // get the users input
2363 // there is a queue to get chars from first
2366 // the end of the q, read from STDIN
2368 ioq_start = ioq = 0;
2369 c = readit(); // get the users input
2373 // adding STDIN chars to q
2374 c = readit(); // get the users input
2375 if (last_modifying_cmd != 0) {
2376 int len = strlen((char *) last_modifying_cmd);
2377 if (len + 1 >= BUFSIZ) {
2378 psbs("last_modifying_cmd overrun");
2380 // add new char to q
2381 last_modifying_cmd[len] = c;
2385 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2386 c = readit(); // get the users input
2387 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2388 return (c); // return the char, where ever it came from
2391 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2396 static Byte *obufp = NULL;
2398 strcpy((char *) buf, (char *) prompt);
2399 last_status_cksum = 0; // force status update
2400 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2401 clear_to_eol(); // clear the line
2402 write1((char *) prompt); // write out the :, /, or ? prompt
2404 for (i = strlen((char *) buf); i < BUFSIZ;) {
2405 c = get_one_char(); // read user input
2406 if (c == '\n' || c == '\r' || c == 27)
2407 break; // is this end of input
2408 if (c == erase_char || c == 8 || c == 127) {
2409 // user wants to erase prev char
2410 i--; // backup to prev char
2411 buf[i] = '\0'; // erase the char
2412 buf[i + 1] = '\0'; // null terminate buffer
2413 write1("\b \b"); // erase char on screen
2414 if (i <= 0) { // user backs up before b-o-l, exit
2418 buf[i] = c; // save char in buffer
2419 buf[i + 1] = '\0'; // make sure buffer is null terminated
2420 putchar(c); // echo the char back to user
2426 obufp = (Byte *) bb_xstrdup((char *) buf);
2430 static int file_size(const Byte * fn) // what is the byte size of "fn"
2435 if (fn == 0 || strlen((char *)fn) <= 0)
2438 sr = stat((char *) fn, &st_buf); // see if file exists
2440 cnt = (int) st_buf.st_size;
2445 static int file_insert(Byte * fn, Byte * p, int size)
2450 #ifdef CONFIG_FEATURE_VI_READONLY
2452 #endif /* CONFIG_FEATURE_VI_READONLY */
2453 if (fn == 0 || strlen((char*) fn) <= 0) {
2454 psbs("No filename given");
2458 // OK- this is just a no-op
2463 psbs("Trying to insert a negative number (%d) of characters", size);
2466 if (p < text || p > end) {
2467 psbs("Trying to insert file outside of memory");
2471 // see if we can open the file
2472 #ifdef CONFIG_FEATURE_VI_READONLY
2473 if (vi_readonly) goto fi1; // do not try write-mode
2475 fd = open((char *) fn, O_RDWR); // assume read & write
2477 // could not open for writing- maybe file is read only
2478 #ifdef CONFIG_FEATURE_VI_READONLY
2481 fd = open((char *) fn, O_RDONLY); // try read-only
2483 psbs("\"%s\" %s", fn, "could not open file");
2486 #ifdef CONFIG_FEATURE_VI_READONLY
2487 // got the file- read-only
2489 #endif /* CONFIG_FEATURE_VI_READONLY */
2491 p = text_hole_make(p, size);
2492 cnt = read(fd, p, size);
2496 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2497 psbs("could not read file \"%s\"", fn);
2498 } else if (cnt < size) {
2499 // There was a partial read, shrink unused space text[]
2500 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2501 psbs("could not read all of file \"%s\"", fn);
2509 static int file_write(Byte * fn, Byte * first, Byte * last)
2511 int fd, cnt, charcnt;
2514 psbs("No current filename");
2518 // FIXIT- use the correct umask()
2519 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2522 cnt = last - first + 1;
2523 charcnt = write(fd, first, cnt);
2524 if (charcnt == cnt) {
2526 //file_modified= FALSE; // the file has not been modified
2534 //----- Terminal Drawing ---------------------------------------
2535 // The terminal is made up of 'rows' line of 'columns' columns.
2536 // classically this would be 24 x 80.
2537 // screen coordinates
2543 // 23,0 ... 23,79 status line
2546 //----- Move the cursor to row x col (count from 0, not 1) -------
2547 static void place_cursor(int row, int col, int opti)
2551 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2554 // char cm3[BUFSIZ];
2556 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2558 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2560 if (row < 0) row = 0;
2561 if (row >= rows) row = rows - 1;
2562 if (col < 0) col = 0;
2563 if (col >= columns) col = columns - 1;
2565 //----- 1. Try the standard terminal ESC sequence
2566 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2568 if (! opti) goto pc0;
2570 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2571 //----- find the minimum # of chars to move cursor -------------
2572 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2573 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2575 // move to the correct row
2576 while (row < Rrow) {
2577 // the cursor has to move up
2581 while (row > Rrow) {
2582 // the cursor has to move down
2583 strcat(cm2, CMdown);
2587 // now move to the correct column
2588 strcat(cm2, "\r"); // start at col 0
2589 // just send out orignal source char to get to correct place
2590 screenp = &screen[row * columns]; // start of screen line
2591 strncat(cm2, (char* )screenp, col);
2593 //----- 3. Try some other way of moving cursor
2594 //---------------------------------------------
2596 // pick the shortest cursor motion to send out
2598 if (strlen(cm2) < strlen(cm)) {
2600 } /* else if (strlen(cm3) < strlen(cm)) {
2603 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2605 write1(cm); // move the cursor
2608 //----- Erase from cursor to end of line -----------------------
2609 static void clear_to_eol(void)
2611 write1(Ceol); // Erase from cursor to end of line
2614 //----- Erase from cursor to end of screen -----------------------
2615 static void clear_to_eos(void)
2617 write1(Ceos); // Erase from cursor to end of screen
2620 //----- Start standout mode ------------------------------------
2621 static void standout_start(void) // send "start reverse video" sequence
2623 write1(SOs); // Start reverse video mode
2626 //----- End standout mode --------------------------------------
2627 static void standout_end(void) // send "end reverse video" sequence
2629 write1(SOn); // End reverse video mode
2632 //----- Flash the screen --------------------------------------
2633 static void flash(int h)
2635 standout_start(); // send "start reverse video" sequence
2638 standout_end(); // send "end reverse video" sequence
2642 static void Indicate_Error(void)
2644 #ifdef CONFIG_FEATURE_VI_CRASHME
2646 return; // generate a random command
2647 #endif /* CONFIG_FEATURE_VI_CRASHME */
2649 write1(bell); // send out a bell character
2655 //----- Screen[] Routines --------------------------------------
2656 //----- Erase the Screen[] memory ------------------------------
2657 static void screen_erase(void)
2659 memset(screen, ' ', screensize); // clear new screen
2662 static int bufsum(unsigned char *buf, int count)
2665 unsigned char *e = buf + count;
2671 //----- Draw the status line at bottom of the screen -------------
2672 static void show_status_line(void)
2674 int cnt = 0, cksum = 0;
2676 // either we already have an error or status message, or we
2678 if (!have_status_msg) {
2679 cnt = format_edit_status();
2680 cksum = bufsum(status_buffer, cnt);
2682 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2683 last_status_cksum= cksum; // remember if we have seen this line
2684 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2685 write1((char*)status_buffer);
2687 if (have_status_msg) {
2688 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2690 have_status_msg = 0;
2693 have_status_msg = 0;
2695 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2700 //----- format the status buffer, the bottom line of screen ------
2701 // format status buffer, with STANDOUT mode
2702 static void psbs(const char *format, ...)
2706 va_start(args, format);
2707 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2708 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2709 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2712 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2717 // format status buffer
2718 static void psb(const char *format, ...)
2722 va_start(args, format);
2723 vsprintf((char *) status_buffer, format, args);
2726 have_status_msg = 1;
2731 static void ni(Byte * s) // display messages
2735 print_literal(buf, s);
2736 psbs("\'%s\' is not implemented", buf);
2739 static int format_edit_status(void) // show file status on status line
2741 int cur, percent, ret, trunc_at;
2744 // file_modified is now a counter rather than a flag. this
2745 // helps reduce the amount of line counting we need to do.
2746 // (this will cause a mis-reporting of modified status
2747 // once every MAXINT editing operations.)
2749 // it would be nice to do a similar optimization here -- if
2750 // we haven't done a motion that could have changed which line
2751 // we're on, then we shouldn't have to do this count_lines()
2752 cur = count_lines(text, dot);
2754 // reduce counting -- the total lines can't have
2755 // changed if we haven't done any edits.
2756 if (file_modified != last_file_modified) {
2757 tot = cur + count_lines(dot, end - 1) - 1;
2758 last_file_modified = file_modified;
2761 // current line percent
2762 // ------------- ~~ ----------
2765 percent = (100 * cur) / tot;
2771 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2772 columns : STATUS_BUFFER_LEN-1;
2774 ret = snprintf((char *) status_buffer, trunc_at+1,
2775 #ifdef CONFIG_FEATURE_VI_READONLY
2776 "%c %s%s%s %d/%d %d%%",
2778 "%c %s%s %d/%d %d%%",
2780 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2781 (cfn != 0 ? (char *) cfn : "No file"),
2782 #ifdef CONFIG_FEATURE_VI_READONLY
2783 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2785 (file_modified ? " [modified]" : ""),
2788 if (ret >= 0 && ret < trunc_at)
2789 return ret; /* it all fit */
2791 return trunc_at; /* had to truncate */
2794 //----- Force refresh of all Lines -----------------------------
2795 static void redraw(int full_screen)
2797 place_cursor(0, 0, FALSE); // put cursor in correct place
2798 clear_to_eos(); // tel terminal to erase display
2799 screen_erase(); // erase the internal screen buffer
2800 last_status_cksum = 0; // force status update
2801 refresh(full_screen); // this will redraw the entire display
2805 //----- Format a text[] line into a buffer ---------------------
2806 static void format_line(Byte *dest, Byte *src, int li)
2811 for (co= 0; co < MAX_SCR_COLS; co++) {
2812 c= ' '; // assume blank
2813 if (li > 0 && co == 0) {
2814 c = '~'; // not first line, assume Tilde
2816 // are there chars in text[] and have we gone past the end
2817 if (text < end && src < end) {
2822 if (c > 127 && !Isprint(c)) {
2825 if (c < ' ' || c == 127) {
2829 for (; (co % tabstop) != (tabstop - 1); co++) {
2837 c += '@'; // make it visible
2840 // the co++ is done here so that the column will
2841 // not be overwritten when we blank-out the rest of line
2848 //----- Refresh the changed screen lines -----------------------
2849 // Copy the source line from text[] into the buffer and note
2850 // if the current screenline is different from the new buffer.
2851 // If they differ then that line needs redrawing on the terminal.
2853 static void refresh(int full_screen)
2855 static int old_offset;
2857 Byte buf[MAX_SCR_COLS];
2858 Byte *tp, *sp; // pointer into text[] and screen[]
2859 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2860 int last_li= -2; // last line that changed- for optimizing cursor movement
2861 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2863 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2865 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2866 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2867 tp = screenbegin; // index into text[] of top line
2869 // compare text[] to screen[] and mark screen[] lines that need updating
2870 for (li = 0; li < rows - 1; li++) {
2871 int cs, ce; // column start & end
2872 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2873 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2874 // format current text line into buf
2875 format_line(buf, tp, li);
2877 // skip to the end of the current text[] line
2878 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2880 // see if there are any changes between vitual screen and buf
2881 changed = FALSE; // assume no change
2884 sp = &screen[li * columns]; // start of screen line
2886 // force re-draw of every single column from 0 - columns-1
2889 // compare newly formatted buffer with virtual screen
2890 // look forward for first difference between buf and screen
2891 for ( ; cs <= ce; cs++) {
2892 if (buf[cs + offset] != sp[cs]) {
2893 changed = TRUE; // mark for redraw
2898 // look backward for last difference between buf and screen
2899 for ( ; ce >= cs; ce--) {
2900 if (buf[ce + offset] != sp[ce]) {
2901 changed = TRUE; // mark for redraw
2905 // now, cs is index of first diff, and ce is index of last diff
2907 // if horz offset has changed, force a redraw
2908 if (offset != old_offset) {
2913 // make a sanity check of columns indexes
2915 if (ce > columns-1) ce= columns-1;
2916 if (cs > ce) { cs= 0; ce= columns-1; }
2917 // is there a change between vitual screen and buf
2919 // copy changed part of buffer to virtual screen
2920 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2922 // move cursor to column of first change
2923 if (offset != old_offset) {
2924 // opti_cur_move is still too stupid
2925 // to handle offsets correctly
2926 place_cursor(li, cs, FALSE);
2928 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2929 // if this just the next line
2930 // try to optimize cursor movement
2931 // otherwise, use standard ESC sequence
2932 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2934 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2935 place_cursor(li, cs, FALSE); // use standard ESC sequence
2936 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2939 // write line out to terminal
2942 char *out = (char*)sp+cs;
2949 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2951 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2955 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2956 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2959 place_cursor(crow, ccol, FALSE);
2960 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2962 if (offset != old_offset)
2963 old_offset = offset;
2966 //---------------------------------------------------------------------
2967 //----- the Ascii Chart -----------------------------------------------
2969 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2970 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2971 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2972 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2973 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2974 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2975 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2976 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2977 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2978 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2979 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2980 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2981 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2982 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2983 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2984 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2985 //---------------------------------------------------------------------
2987 //----- Execute a Vi Command -----------------------------------
2988 static void do_cmd(Byte c)
2990 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2991 int cnt, i, j, dir, yf;
2993 c1 = c; // quiet the compiler
2994 cnt = yf = dir = 0; // quiet the compiler
2995 p = q = save_dot = msg = buf; // quiet the compiler
2996 memset(buf, '\0', 9); // clear buf
3000 /* if this is a cursor key, skip these checks */
3013 if (cmd_mode == 2) {
3014 // flip-flop Insert/Replace mode
3015 if (c == VI_K_INSERT) goto dc_i;
3016 // we are 'R'eplacing the current *dot with new char
3018 // don't Replace past E-o-l
3019 cmd_mode = 1; // convert to insert
3021 if (1 <= c || Isprint(c)) {
3023 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3024 dot = char_insert(dot, c); // insert new char
3029 if (cmd_mode == 1) {
3030 // hitting "Insert" twice means "R" replace mode
3031 if (c == VI_K_INSERT) goto dc5;
3032 // insert the char c at "dot"
3033 if (1 <= c || Isprint(c)) {
3034 dot = char_insert(dot, c);
3049 #ifdef CONFIG_FEATURE_VI_CRASHME
3050 case 0x14: // dc4 ctrl-T
3051 crashme = (crashme == 0) ? 1 : 0;
3053 #endif /* CONFIG_FEATURE_VI_CRASHME */
3082 //case 'u': // u- FIXME- there is no undo
3084 default: // unrecognised command
3093 end_cmd_q(); // stop adding to q
3094 case 0x00: // nul- ignore
3096 case 2: // ctrl-B scroll up full screen
3097 case VI_K_PAGEUP: // Cursor Key Page Up
3098 dot_scroll(rows - 2, -1);
3100 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3101 case 0x03: // ctrl-C interrupt
3102 longjmp(restart, 1);
3104 case 26: // ctrl-Z suspend
3105 suspend_sig(SIGTSTP);
3107 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3108 case 4: // ctrl-D scroll down half screen
3109 dot_scroll((rows - 2) / 2, 1);
3111 case 5: // ctrl-E scroll down one line
3114 case 6: // ctrl-F scroll down full screen
3115 case VI_K_PAGEDOWN: // Cursor Key Page Down
3116 dot_scroll(rows - 2, 1);
3118 case 7: // ctrl-G show current status
3119 last_status_cksum = 0; // force status update
3121 case 'h': // h- move left
3122 case VI_K_LEFT: // cursor key Left
3123 case 8: // ctrl-H- move left (This may be ERASE char)
3124 case 127: // DEL- move left (This may be ERASE char)
3130 case 10: // Newline ^J
3131 case 'j': // j- goto next line, same col
3132 case VI_K_DOWN: // cursor key Down
3136 dot_next(); // go to next B-o-l
3137 dot = move_to_col(dot, ccol + offset); // try stay in same col
3139 case 12: // ctrl-L force redraw whole screen
3140 case 18: // ctrl-R force redraw
3141 place_cursor(0, 0, FALSE); // put cursor in correct place
3142 clear_to_eos(); // tel terminal to erase display
3144 screen_erase(); // erase the internal screen buffer
3145 last_status_cksum = 0; // force status update
3146 refresh(TRUE); // this will redraw the entire display
3148 case 13: // Carriage Return ^M
3149 case '+': // +- goto next line
3156 case 21: // ctrl-U scroll up half screen
3157 dot_scroll((rows - 2) / 2, -1);
3159 case 25: // ctrl-Y scroll up one line
3165 cmd_mode = 0; // stop insrting
3167 last_status_cksum = 0; // force status update
3169 case ' ': // move right
3170 case 'l': // move right
3171 case VI_K_RIGHT: // Cursor Key Right
3177 #ifdef CONFIG_FEATURE_VI_YANKMARK
3178 case '"': // "- name a register to use for Delete/Yank
3179 c1 = get_one_char();
3187 case '\'': // '- goto a specific mark
3188 c1 = get_one_char();
3194 if (text <= q && q < end) {
3196 dot_begin(); // go to B-o-l
3199 } else if (c1 == '\'') { // goto previous context
3200 dot = swap_context(dot); // swap current and previous context
3201 dot_begin(); // go to B-o-l
3207 case 'm': // m- Mark a line
3208 // this is really stupid. If there are any inserts or deletes
3209 // between text[0] and dot then this mark will not point to the
3210 // correct location! It could be off by many lines!
3211 // Well..., at least its quick and dirty.
3212 c1 = get_one_char();
3216 // remember the line
3217 mark[(int) c1] = dot;
3222 case 'P': // P- Put register before
3223 case 'p': // p- put register after
3226 psbs("Nothing in register %c", what_reg());
3229 // are we putting whole lines or strings
3230 if (strchr((char *) p, '\n') != NULL) {
3232 dot_begin(); // putting lines- Put above
3235 // are we putting after very last line?
3236 if (end_line(dot) == (end - 1)) {
3237 dot = end; // force dot to end of text[]
3239 dot_next(); // next line, then put before
3244 dot_right(); // move to right, can move to NL
3246 dot = string_insert(dot, p); // insert the string
3247 end_cmd_q(); // stop adding to q
3249 case 'U': // U- Undo; replace current line with original version
3250 if (reg[Ureg] != 0) {
3251 p = begin_line(dot);
3253 p = text_hole_delete(p, q); // delete cur line
3254 p = string_insert(p, reg[Ureg]); // insert orig line
3259 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3260 case '$': // $- goto end of line
3261 case VI_K_END: // Cursor Key End
3265 dot = end_line(dot);
3267 case '%': // %- find matching char of pair () [] {}
3268 for (q = dot; q < end && *q != '\n'; q++) {
3269 if (strchr("()[]{}", *q) != NULL) {
3270 // we found half of a pair
3271 p = find_pair(q, *q);
3283 case 'f': // f- forward to a user specified char
3284 last_forward_char = get_one_char(); // get the search char
3286 // dont separate these two commands. 'f' depends on ';'
3288 //**** fall thru to ... ';'
3289 case ';': // ;- look at rest of line for last forward char
3293 if (last_forward_char == 0) break;
3295 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3298 if (*q == last_forward_char)
3301 case '-': // -- goto prev line
3308 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3309 case '.': // .- repeat the last modifying command
3310 // Stuff the last_modifying_cmd back into stdin
3311 // and let it be re-executed.
3312 if (last_modifying_cmd != 0) {
3313 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3316 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3317 #ifdef CONFIG_FEATURE_VI_SEARCH
3318 case '?': // /- search for a pattern
3319 case '/': // /- search for a pattern
3322 q = get_input_line(buf); // get input line- use "status line"
3323 if (strlen((char *) q) == 1)
3324 goto dc3; // if no pat re-use old pat
3325 if (strlen((char *) q) > 1) { // new pat- save it and find
3326 // there is a new pat
3327 free(last_search_pattern);
3328 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3329 goto dc3; // now find the pattern
3331 // user changed mind and erased the "/"- do nothing
3333 case 'N': // N- backward search for last pattern
3337 dir = BACK; // assume BACKWARD search
3339 if (last_search_pattern[0] == '?') {
3343 goto dc4; // now search for pattern
3345 case 'n': // n- repeat search for last pattern
3346 // search rest of text[] starting at next char
3347 // if search fails return orignal "p" not the "p+1" address
3352 if (last_search_pattern == 0) {
3353 msg = (Byte *) "No previous regular expression";
3356 if (last_search_pattern[0] == '/') {
3357 dir = FORWARD; // assume FORWARD search
3360 if (last_search_pattern[0] == '?') {
3365 q = char_search(p, last_search_pattern + 1, dir, FULL);
3367 dot = q; // good search, update "dot"
3371 // no pattern found between "dot" and "end"- continue at top
3376 q = char_search(p, last_search_pattern + 1, dir, FULL);
3377 if (q != NULL) { // found something
3378 dot = q; // found new pattern- goto it
3379 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3381 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3384 msg = (Byte *) "Pattern not found";
3387 if (*msg) psbs("%s", msg);
3389 case '{': // {- move backward paragraph
3390 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3391 if (q != NULL) { // found blank line
3392 dot = next_line(q); // move to next blank line
3395 case '}': // }- move forward paragraph
3396 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3397 if (q != NULL) { // found blank line
3398 dot = next_line(q); // move to next blank line
3401 #endif /* CONFIG_FEATURE_VI_SEARCH */
3402 case '0': // 0- goto begining of line
3412 if (c == '0' && cmdcnt < 1) {
3413 dot_begin(); // this was a standalone zero
3415 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3418 case ':': // :- the colon mode commands
3419 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3420 #ifdef CONFIG_FEATURE_VI_COLON
3421 colon(p); // execute the command
3422 #else /* CONFIG_FEATURE_VI_COLON */
3424 p++; // move past the ':'
3425 cnt = strlen((char *) p);
3428 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3429 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3430 if (file_modified && p[1] != '!') {
3431 psbs("No write since last change (:quit! overrides)");
3435 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3436 strncasecmp((char *) p, "wq", cnt) == 0 ||
3437 strncasecmp((char *) p, "x", cnt) == 0) {
3438 cnt = file_write(cfn, text, end - 1);
3441 psbs("Write error: %s", strerror(errno));
3444 last_file_modified = -1;
3445 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3446 if (p[0] == 'x' || p[1] == 'q') {
3450 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3451 last_status_cksum = 0; // force status update
3452 } else if (sscanf((char *) p, "%d", &j) > 0) {
3453 dot = find_line(j); // go to line # j
3455 } else { // unrecognised cmd
3458 #endif /* CONFIG_FEATURE_VI_COLON */
3460 case '<': // <- Left shift something
3461 case '>': // >- Right shift something
3462 cnt = count_lines(text, dot); // remember what line we are on
3463 c1 = get_one_char(); // get the type of thing to delete
3464 find_range(&p, &q, c1);
3465 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3468 i = count_lines(p, q); // # of lines we are shifting
3469 for ( ; i > 0; i--, p = next_line(p)) {
3471 // shift left- remove tab or 8 spaces
3473 // shrink buffer 1 char
3474 (void) text_hole_delete(p, p);
3475 } else if (*p == ' ') {
3476 // we should be calculating columns, not just SPACE
3477 for (j = 0; *p == ' ' && j < tabstop; j++) {
3478 (void) text_hole_delete(p, p);
3481 } else if (c == '>') {
3482 // shift right -- add tab or 8 spaces
3483 (void) char_insert(p, '\t');
3486 dot = find_line(cnt); // what line were we on
3488 end_cmd_q(); // stop adding to q
3490 case 'A': // A- append at e-o-l
3491 dot_end(); // go to e-o-l
3492 //**** fall thru to ... 'a'
3493 case 'a': // a- append after current char
3498 case 'B': // B- back a blank-delimited Word
3499 case 'E': // E- end of a blank-delimited word
3500 case 'W': // W- forward a blank-delimited word
3507 if (c == 'W' || isspace(dot[dir])) {
3508 dot = skip_thing(dot, 1, dir, S_TO_WS);
3509 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3512 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3514 case 'C': // C- Change to e-o-l
3515 case 'D': // D- delete to e-o-l
3517 dot = dollar_line(dot); // move to before NL
3518 // copy text into a register and delete
3519 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3521 goto dc_i; // start inserting
3522 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3524 end_cmd_q(); // stop adding to q
3525 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3527 case 'G': // G- goto to a line number (default= E-O-F)
3528 dot = end - 1; // assume E-O-F
3530 dot = find_line(cmdcnt); // what line is #cmdcnt
3534 case 'H': // H- goto top line on screen
3536 if (cmdcnt > (rows - 1)) {
3537 cmdcnt = (rows - 1);
3544 case 'I': // I- insert before first non-blank
3547 //**** fall thru to ... 'i'
3548 case 'i': // i- insert before current char
3549 case VI_K_INSERT: // Cursor Key Insert
3551 cmd_mode = 1; // start insrting
3553 case 'J': // J- join current and next lines together
3557 dot_end(); // move to NL
3558 if (dot < end - 1) { // make sure not last char in text[]
3559 *dot++ = ' '; // replace NL with space
3561 while (isblnk(*dot)) { // delete leading WS
3565 end_cmd_q(); // stop adding to q
3567 case 'L': // L- goto bottom line on screen
3569 if (cmdcnt > (rows - 1)) {
3570 cmdcnt = (rows - 1);
3578 case 'M': // M- goto middle line on screen
3580 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3581 dot = next_line(dot);
3583 case 'O': // O- open a empty line above
3585 p = begin_line(dot);
3586 if (p[-1] == '\n') {
3588 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3590 dot = char_insert(dot, '\n');
3593 dot = char_insert(dot, '\n'); // i\n ESC
3598 case 'R': // R- continuous Replace char
3602 case 'X': // X- delete char before dot
3603 case 'x': // x- delete the current char
3604 case 's': // s- substitute the current char
3611 if (dot[dir] != '\n') {
3613 dot--; // delete prev char
3614 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3617 goto dc_i; // start insrting
3618 end_cmd_q(); // stop adding to q
3620 case 'Z': // Z- if modified, {write}; exit
3621 // ZZ means to save file (if necessary), then exit
3622 c1 = get_one_char();
3628 #ifdef CONFIG_FEATURE_VI_READONLY
3631 #endif /* CONFIG_FEATURE_VI_READONLY */
3633 cnt = file_write(cfn, text, end - 1);
3636 psbs("Write error: %s", strerror(errno));
3637 } else if (cnt == (end - 1 - text + 1)) {
3644 case '^': // ^- move to first non-blank on line
3648 case 'b': // b- back a word
3649 case 'e': // e- end of word
3656 if ((dot + dir) < text || (dot + dir) > end - 1)
3659 if (isspace(*dot)) {
3660 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3662 if (isalnum(*dot) || *dot == '_') {
3663 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3664 } else if (ispunct(*dot)) {
3665 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3668 case 'c': // c- change something
3669 case 'd': // d- delete something
3670 #ifdef CONFIG_FEATURE_VI_YANKMARK
3671 case 'y': // y- yank something
3672 case 'Y': // Y- Yank a line
3673 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3674 yf = YANKDEL; // assume either "c" or "d"
3675 #ifdef CONFIG_FEATURE_VI_YANKMARK
3676 if (c == 'y' || c == 'Y')
3678 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3681 c1 = get_one_char(); // get the type of thing to delete
3682 find_range(&p, &q, c1);
3683 if (c1 == 27) { // ESC- user changed mind and wants out
3684 c = c1 = 27; // Escape- do nothing
3685 } else if (strchr("wW", c1)) {
3687 // don't include trailing WS as part of word
3688 while (isblnk(*q)) {
3689 if (q <= text || q[-1] == '\n')
3694 dot = yank_delete(p, q, 0, yf); // delete word
3695 } else if (strchr("^0bBeEft$", c1)) {
3696 // single line copy text into a register and delete
3697 dot = yank_delete(p, q, 0, yf); // delete word
3698 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3699 // multiple line copy text into a register and delete
3700 dot = yank_delete(p, q, 1, yf); // delete lines
3702 dot = char_insert(dot, '\n');
3703 // on the last line of file don't move to prev line
3704 if (dot != (end-1)) {
3707 } else if (c == 'd') {
3712 // could not recognize object
3713 c = c1 = 27; // error-
3717 // if CHANGING, not deleting, start inserting after the delete
3719 strcpy((char *) buf, "Change");
3720 goto dc_i; // start inserting
3723 strcpy((char *) buf, "Delete");
3725 #ifdef CONFIG_FEATURE_VI_YANKMARK
3726 if (c == 'y' || c == 'Y') {
3727 strcpy((char *) buf, "Yank");
3730 q = p + strlen((char *) p);
3731 for (cnt = 0; p <= q; p++) {
3735 psb("%s %d lines (%d chars) using [%c]",
3736 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3737 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3738 end_cmd_q(); // stop adding to q
3741 case 'k': // k- goto prev line, same col
3742 case VI_K_UP: // cursor key Up
3747 dot = move_to_col(dot, ccol + offset); // try stay in same col
3749 case 'r': // r- replace the current char with user input
3750 c1 = get_one_char(); // get the replacement char
3753 file_modified++; // has the file been modified
3755 end_cmd_q(); // stop adding to q
3757 case 't': // t- move to char prior to next x
3758 last_forward_char = get_one_char();
3760 if (*dot == last_forward_char)
3762 last_forward_char= 0;
3764 case 'w': // w- forward a word
3768 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3769 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3770 } else if (ispunct(*dot)) { // we are on PUNCT
3771 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3774 dot++; // move over word
3775 if (isspace(*dot)) {
3776 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3780 c1 = get_one_char(); // get the replacement char
3783 cnt = (rows - 2) / 2; // put dot at center
3785 cnt = rows - 2; // put dot at bottom
3786 screenbegin = begin_line(dot); // start dot at top
3787 dot_scroll(cnt, -1);
3789 case '|': // |- move to column "cmdcnt"
3790 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3792 case '~': // ~- flip the case of letters a-z -> A-Z
3796 if (islower(*dot)) {
3797 *dot = toupper(*dot);
3798 file_modified++; // has the file been modified
3799 } else if (isupper(*dot)) {
3800 *dot = tolower(*dot);
3801 file_modified++; // has the file been modified
3804 end_cmd_q(); // stop adding to q
3806 //----- The Cursor and Function Keys -----------------------------
3807 case VI_K_HOME: // Cursor Key Home
3810 // The Fn keys could point to do_macro which could translate them
3811 case VI_K_FUN1: // Function Key F1
3812 case VI_K_FUN2: // Function Key F2
3813 case VI_K_FUN3: // Function Key F3
3814 case VI_K_FUN4: // Function Key F4
3815 case VI_K_FUN5: // Function Key F5
3816 case VI_K_FUN6: // Function Key F6
3817 case VI_K_FUN7: // Function Key F7
3818 case VI_K_FUN8: // Function Key F8
3819 case VI_K_FUN9: // Function Key F9
3820 case VI_K_FUN10: // Function Key F10
3821 case VI_K_FUN11: // Function Key F11
3822 case VI_K_FUN12: // Function Key F12
3827 // if text[] just became empty, add back an empty line
3829 (void) char_insert(text, '\n'); // start empty buf with dummy line
3832 // it is OK for dot to exactly equal to end, otherwise check dot validity
3834 dot = bound_dot(dot); // make sure "dot" is valid
3836 #ifdef CONFIG_FEATURE_VI_YANKMARK
3837 check_context(c); // update the current context
3838 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3841 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3842 cnt = dot - begin_line(dot);
3843 // Try to stay off of the Newline
3844 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3848 #ifdef CONFIG_FEATURE_VI_CRASHME
3849 static int totalcmds = 0;
3850 static int Mp = 85; // Movement command Probability
3851 static int Np = 90; // Non-movement command Probability
3852 static int Dp = 96; // Delete command Probability
3853 static int Ip = 97; // Insert command Probability
3854 static int Yp = 98; // Yank command Probability
3855 static int Pp = 99; // Put command Probability
3856 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3857 char chars[20] = "\t012345 abcdABCD-=.$";
3858 char *words[20] = { "this", "is", "a", "test",
3859 "broadcast", "the", "emergency", "of",
3860 "system", "quick", "brown", "fox",
3861 "jumped", "over", "lazy", "dogs",
3862 "back", "January", "Febuary", "March"
3865 "You should have received a copy of the GNU General Public License\n",
3866 "char c, cm, *cmd, *cmd1;\n",
3867 "generate a command by percentages\n",
3868 "Numbers may be typed as a prefix to some commands.\n",
3869 "Quit, discarding changes!\n",
3870 "Forced write, if permission originally not valid.\n",
3871 "In general, any ex or ed command (such as substitute or delete).\n",
3872 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3873 "Please get w/ me and I will go over it with you.\n",
3874 "The following is a list of scheduled, committed changes.\n",
3875 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3876 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3877 "Any question about transactions please contact Sterling Huxley.\n",
3878 "I will try to get back to you by Friday, December 31.\n",
3879 "This Change will be implemented on Friday.\n",
3880 "Let me know if you have problems accessing this;\n",
3881 "Sterling Huxley recently added you to the access list.\n",
3882 "Would you like to go to lunch?\n",
3883 "The last command will be automatically run.\n",
3884 "This is too much english for a computer geek.\n",
3886 char *multilines[20] = {
3887 "You should have received a copy of the GNU General Public License\n",
3888 "char c, cm, *cmd, *cmd1;\n",
3889 "generate a command by percentages\n",
3890 "Numbers may be typed as a prefix to some commands.\n",
3891 "Quit, discarding changes!\n",
3892 "Forced write, if permission originally not valid.\n",
3893 "In general, any ex or ed command (such as substitute or delete).\n",
3894 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3895 "Please get w/ me and I will go over it with you.\n",
3896 "The following is a list of scheduled, committed changes.\n",
3897 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3898 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3899 "Any question about transactions please contact Sterling Huxley.\n",
3900 "I will try to get back to you by Friday, December 31.\n",
3901 "This Change will be implemented on Friday.\n",
3902 "Let me know if you have problems accessing this;\n",
3903 "Sterling Huxley recently added you to the access list.\n",
3904 "Would you like to go to lunch?\n",
3905 "The last command will be automatically run.\n",
3906 "This is too much english for a computer geek.\n",
3909 // create a random command to execute
3910 static void crash_dummy()
3912 static int sleeptime; // how long to pause between commands
3913 char c, cm, *cmd, *cmd1;
3914 int i, cnt, thing, rbi, startrbi, percent;
3916 // "dot" movement commands
3917 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3919 // is there already a command running?
3920 if (readed_for_parse > 0)
3924 sleeptime = 0; // how long to pause between commands
3925 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3926 // generate a command by percentages
3927 percent = (int) lrand48() % 100; // get a number from 0-99
3928 if (percent < Mp) { // Movement commands
3929 // available commands
3932 } else if (percent < Np) { // non-movement commands
3933 cmd = "mz<>\'\""; // available commands
3935 } else if (percent < Dp) { // Delete commands
3936 cmd = "dx"; // available commands
3938 } else if (percent < Ip) { // Inset commands
3939 cmd = "iIaAsrJ"; // available commands
3941 } else if (percent < Yp) { // Yank commands
3942 cmd = "yY"; // available commands
3944 } else if (percent < Pp) { // Put commands
3945 cmd = "pP"; // available commands
3948 // We do not know how to handle this command, try again
3952 // randomly pick one of the available cmds from "cmd[]"
3953 i = (int) lrand48() % strlen(cmd);
3955 if (strchr(":\024", cm))
3956 goto cd0; // dont allow colon or ctrl-T commands
3957 readbuffer[rbi++] = cm; // put cmd into input buffer
3959 // now we have the command-
3960 // there are 1, 2, and multi char commands
3961 // find out which and generate the rest of command as necessary
3962 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3963 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3964 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3965 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3967 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3969 readbuffer[rbi++] = c; // add movement to input buffer
3971 if (strchr("iIaAsc", cm)) { // multi-char commands
3973 // change some thing
3974 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3976 readbuffer[rbi++] = c; // add movement to input buffer
3978 thing = (int) lrand48() % 4; // what thing to insert
3979 cnt = (int) lrand48() % 10; // how many to insert
3980 for (i = 0; i < cnt; i++) {
3981 if (thing == 0) { // insert chars
3982 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3983 } else if (thing == 1) { // insert words
3984 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3985 strcat((char *) readbuffer, " ");
3986 sleeptime = 0; // how fast to type
3987 } else if (thing == 2) { // insert lines
3988 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3989 sleeptime = 0; // how fast to type
3990 } else { // insert multi-lines
3991 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3992 sleeptime = 0; // how fast to type
3995 strcat((char *) readbuffer, "\033");
3997 readed_for_parse = strlen(readbuffer);
4001 (void) mysleep(sleeptime); // sleep 1/100 sec
4004 // test to see if there are any errors
4005 static void crash_test()
4007 static time_t oldtim;
4009 char d[2], msg[BUFSIZ];
4013 strcat((char *) msg, "end<text ");
4015 if (end > textend) {
4016 strcat((char *) msg, "end>textend ");
4019 strcat((char *) msg, "dot<text ");
4022 strcat((char *) msg, "dot>end ");
4024 if (screenbegin < text) {
4025 strcat((char *) msg, "screenbegin<text ");
4027 if (screenbegin > end - 1) {
4028 strcat((char *) msg, "screenbegin>end-1 ");
4031 if (strlen(msg) > 0) {
4033 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4034 totalcmds, last_input_char, msg, SOs, SOn);
4036 while (read(0, d, 1) > 0) {
4037 if (d[0] == '\n' || d[0] == '\r')
4042 tim = (time_t) time((time_t *) 0);
4043 if (tim >= (oldtim + 3)) {
4044 sprintf((char *) status_buffer,
4045 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4046 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4051 #endif /* CONFIG_FEATURE_VI_CRASHME */