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.31 2004/01/21 10:59:45 bug1 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 immplement
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";
141 static const int YANKONLY = FALSE;
142 static const int YANKDEL = TRUE;
143 static const int FORWARD = 1; // code depends on "1" for array index
144 static const int BACK = -1; // code depends on "-1" for array index
145 static const int LIMITED = 0; // how much of text[] in char_search
146 static const int FULL = 1; // how much of text[] in char_search
148 static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
149 static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
150 static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
151 static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
152 static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
154 typedef unsigned char Byte;
156 static int vi_setops;
157 #define VI_AUTOINDENT 1
158 #define VI_SHOWMATCH 2
159 #define VI_IGNORECASE 4
160 #define VI_ERR_METHOD 8
161 #define autoindent (vi_setops & VI_AUTOINDENT)
162 #define showmatch (vi_setops & VI_SHOWMATCH )
163 #define ignorecase (vi_setops & VI_IGNORECASE)
164 /* indicate error with beep or flash */
165 #define err_method (vi_setops & VI_ERR_METHOD)
168 static int editing; // >0 while we are editing a file
169 static int cmd_mode; // 0=command 1=insert
170 static int file_modified; // buffer contents changed
171 static int fn_start; // index of first cmd line file name
172 static int save_argc; // how many file names on cmd line
173 static int cmdcnt; // repetition count
174 static fd_set rfds; // use select() for small sleeps
175 static struct timeval tv; // use select() for small sleeps
176 static int rows, columns; // the terminal screen is this size
177 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
178 static Byte *status_buffer; // mesages to the user
179 static Byte *cfn; // previous, current, and next file name
180 static Byte *text, *end, *textend; // pointers to the user data in memory
181 static Byte *screen; // pointer to the virtual screen buffer
182 static int screensize; // and its size
183 static Byte *screenbegin; // index into text[], of top line on the screen
184 static Byte *dot; // where all the action takes place
186 static struct termios term_orig, term_vi; // remember what the cooked mode was
187 static Byte erase_char; // the users erase character
188 static Byte last_input_char; // last char read from user
189 static Byte last_forward_char; // last char searched for with 'f'
191 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
192 static int last_row; // where the cursor was last moved to
193 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
194 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
195 static jmp_buf restart; // catch_sig()
196 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
197 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
200 #ifdef CONFIG_FEATURE_VI_DOT_CMD
201 static int adding2q; // are we currently adding user input to q
202 static Byte *last_modifying_cmd; // last modifying cmd for "."
203 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
204 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
205 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
206 static Byte *modifying_cmds; // cmds that modify text[]
207 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
208 #ifdef CONFIG_FEATURE_VI_READONLY
209 static int vi_readonly, readonly;
210 #endif /* CONFIG_FEATURE_VI_READONLY */
211 #ifdef CONFIG_FEATURE_VI_YANKMARK
212 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
213 static int YDreg, Ureg; // default delete register and orig line for "U"
214 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
215 static Byte *context_start, *context_end;
216 #endif /* CONFIG_FEATURE_VI_YANKMARK */
217 #ifdef CONFIG_FEATURE_VI_SEARCH
218 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
219 #endif /* CONFIG_FEATURE_VI_SEARCH */
222 static void edit_file(Byte *); // edit one file
223 static void do_cmd(Byte); // execute a command
224 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
225 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
226 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
227 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
228 static Byte *next_line(Byte *); // return pointer to next line B-o-l
229 static Byte *end_screen(void); // get pointer to last char on screen
230 static int count_lines(Byte *, Byte *); // count line from start to stop
231 static Byte *find_line(int); // find begining of line #li
232 static Byte *move_to_col(Byte *, int); // move "p" to column l
233 static int isblnk(Byte); // is the char a blank or tab
234 static void dot_left(void); // move dot left- dont leave line
235 static void dot_right(void); // move dot right- dont leave line
236 static void dot_begin(void); // move dot to B-o-l
237 static void dot_end(void); // move dot to E-o-l
238 static void dot_next(void); // move dot to next line B-o-l
239 static void dot_prev(void); // move dot to prev line B-o-l
240 static void dot_scroll(int, int); // move the screen up or down
241 static void dot_skip_over_ws(void); // move dot pat WS
242 static void dot_delete(void); // delete the char at 'dot'
243 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
244 static Byte *new_screen(int, int); // malloc virtual screen memory
245 static Byte *new_text(int); // malloc memory for text[] buffer
246 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
247 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
248 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
249 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
250 static Byte *skip_thing(Byte *, int, int, int); // skip some object
251 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
252 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
253 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
254 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
255 static void show_help(void); // display some help info
256 static void rawmode(void); // set "raw" mode on tty
257 static void cookmode(void); // return to "cooked" mode on tty
258 static int mysleep(int); // sleep for 'h' 1/100 seconds
259 static Byte readit(void); // read (maybe cursor) key from stdin
260 static Byte get_one_char(void); // read 1 char from stdin
261 static int file_size(const Byte *); // what is the byte size of "fn"
262 static int file_insert(Byte *, Byte *, int);
263 static int file_write(Byte *, Byte *, Byte *);
264 static void place_cursor(int, int, int);
265 static void screen_erase(void);
266 static void clear_to_eol(void);
267 static void clear_to_eos(void);
268 static void standout_start(void); // send "start reverse video" sequence
269 static void standout_end(void); // send "end reverse video" sequence
270 static void flash(int); // flash the terminal screen
271 static void show_status_line(void); // put a message on the bottom line
272 static void psb(const char *, ...); // Print Status Buf
273 static void psbs(const char *, ...); // Print Status Buf in standout mode
274 static void ni(Byte *); // display messages
275 static void edit_status(void); // show file status on status line
276 static void redraw(int); // force a full screen refresh
277 static void format_line(Byte*, Byte*, int);
278 static void refresh(int); // update the terminal from screen[]
280 static void Indicate_Error(void); // use flash or beep to indicate error
281 #define indicate_error(c) Indicate_Error()
283 #ifdef CONFIG_FEATURE_VI_SEARCH
284 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
285 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
286 #endif /* CONFIG_FEATURE_VI_SEARCH */
287 #ifdef CONFIG_FEATURE_VI_COLON
288 static void Hit_Return(void);
289 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
290 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
291 static void colon(Byte *); // execute the "colon" mode cmds
292 #endif /* CONFIG_FEATURE_VI_COLON */
293 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
294 static void winch_sig(int); // catch window size changes
295 static void suspend_sig(int); // catch ctrl-Z
296 static void catch_sig(int); // catch ctrl-C and alarm time-outs
297 static void core_sig(int); // catch a core dump signal
298 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
299 #ifdef CONFIG_FEATURE_VI_DOT_CMD
300 static void start_new_cmd_q(Byte); // new queue for command
301 static void end_cmd_q(void); // stop saving input chars
302 #else /* CONFIG_FEATURE_VI_DOT_CMD */
304 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
305 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
306 static void window_size_get(int); // find out what size the window is
307 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
308 #ifdef CONFIG_FEATURE_VI_SETOPTS
309 static void showmatching(Byte *); // show the matching pair () [] {}
310 #endif /* CONFIG_FEATURE_VI_SETOPTS */
311 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
312 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
313 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
314 #ifdef CONFIG_FEATURE_VI_YANKMARK
315 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
316 static Byte what_reg(void); // what is letter of current YDreg
317 static void check_context(Byte); // remember context for '' command
318 #endif /* CONFIG_FEATURE_VI_YANKMARK */
319 #ifdef CONFIG_FEATURE_VI_CRASHME
320 static void crash_dummy();
321 static void crash_test();
322 static int crashme = 0;
323 #endif /* CONFIG_FEATURE_VI_CRASHME */
326 static void write1(const char *out)
331 extern int vi_main(int argc, char **argv)
334 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, 200);
336 #ifdef CONFIG_FEATURE_VI_YANKMARK
338 #endif /* CONFIG_FEATURE_VI_YANKMARK */
339 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
342 #ifdef CONFIG_FEATURE_VI_CRASHME
343 (void) srand((long) my_pid);
344 #endif /* CONFIG_FEATURE_VI_CRASHME */
346 status_buffer = STATUS_BUFFER;
348 #ifdef CONFIG_FEATURE_VI_READONLY
349 vi_readonly = readonly = FALSE;
350 if (strncmp(argv[0], "view", 4) == 0) {
354 #endif /* CONFIG_FEATURE_VI_READONLY */
355 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
356 #ifdef CONFIG_FEATURE_VI_YANKMARK
357 for (i = 0; i < 28; i++) {
359 } // init the yank regs
360 #endif /* CONFIG_FEATURE_VI_YANKMARK */
361 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
362 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
363 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
365 // 1- process $HOME/.exrc file
366 // 2- process EXINIT variable from environment
367 // 3- process command line args
368 while ((c = getopt(argc, argv, "hCR")) != -1) {
370 #ifdef CONFIG_FEATURE_VI_CRASHME
374 #endif /* CONFIG_FEATURE_VI_CRASHME */
375 #ifdef CONFIG_FEATURE_VI_READONLY
376 case 'R': // Read-only flag
379 #endif /* CONFIG_FEATURE_VI_READONLY */
380 //case 'r': // recover flag- ignore- we don't use tmp file
381 //case 'x': // encryption flag- ignore
382 //case 'c': // execute command first
383 //case 'h': // help -- just use default
390 // The argv array can be used by the ":next" and ":rewind" commands
392 fn_start = optind; // remember first file name for :next and :rew
395 //----- This is the main file handling loop --------------
396 if (optind >= argc) {
397 editing = 1; // 0= exit, 1= one file, 2= multiple files
400 for (; optind < argc; optind++) {
401 editing = 1; // 0=exit, 1=one file, 2+ =many files
403 cfn = (Byte *) bb_xstrdup(argv[optind]);
407 //-----------------------------------------------------------
412 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
413 //----- See what the window size currently is --------------------
414 static inline void window_size_get(int fd)
416 get_terminal_width_height(fd, &columns, &rows);
418 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
420 static void edit_file(Byte * fn)
425 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
427 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
428 #ifdef CONFIG_FEATURE_VI_YANKMARK
429 static Byte *cur_line;
430 #endif /* CONFIG_FEATURE_VI_YANKMARK */
436 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
438 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
439 new_screen(rows, columns); // get memory for virtual screen
441 cnt = file_size(fn); // file size
442 size = 2 * cnt; // 200% of file size
443 new_text(size); // get a text[] buffer
444 screenbegin = dot = end = text;
446 ch= file_insert(fn, text, cnt);
449 (void) char_insert(text, '\n'); // start empty buf with dummy line
451 file_modified = FALSE;
452 #ifdef CONFIG_FEATURE_VI_YANKMARK
453 YDreg = 26; // default Yank/Delete reg
454 Ureg = 27; // hold orig line for "U" cmd
455 for (cnt = 0; cnt < 28; cnt++) {
458 mark[26] = mark[27] = text; // init "previous context"
459 #endif /* CONFIG_FEATURE_VI_YANKMARK */
461 last_forward_char = last_input_char = '\0';
466 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
469 signal(SIGWINCH, winch_sig);
470 signal(SIGTSTP, suspend_sig);
471 sig = setjmp(restart);
473 const char *msg = "";
476 msg = "(window resize)";
486 msg = "(I tried to touch invalid memory)";
490 psbs("-- caught signal %d %s--", sig, msg);
491 screenbegin = dot = text;
493 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
496 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
499 offset = 0; // no horizontal offset
501 #ifdef CONFIG_FEATURE_VI_DOT_CMD
502 free(last_modifying_cmd);
504 ioq = ioq_start = last_modifying_cmd = 0;
506 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
507 redraw(FALSE); // dont force every col re-draw
511 //------This is the main Vi cmd handling loop -----------------------
512 while (editing > 0) {
513 #ifdef CONFIG_FEATURE_VI_CRASHME
515 if ((end - text) > 1) {
516 crash_dummy(); // generate a random command
520 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
524 #endif /* CONFIG_FEATURE_VI_CRASHME */
525 last_input_char = c = get_one_char(); // get a cmd from user
526 #ifdef CONFIG_FEATURE_VI_YANKMARK
527 // save a copy of the current line- for the 'U" command
528 if (begin_line(dot) != cur_line) {
529 cur_line = begin_line(dot);
530 text_yank(begin_line(dot), end_line(dot), Ureg);
532 #endif /* CONFIG_FEATURE_VI_YANKMARK */
533 #ifdef CONFIG_FEATURE_VI_DOT_CMD
534 // These are commands that change text[].
535 // Remember the input for the "." command
536 if (!adding2q && ioq_start == 0
537 && strchr((char *) modifying_cmds, c) != NULL) {
540 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
541 do_cmd(c); // execute the user command
543 // poll to see if there is input already waiting. if we are
544 // not able to display output fast enough to keep up, skip
545 // the display update until we catch up with input.
546 if (mysleep(0) == 0) {
547 // no input pending- so update output
551 #ifdef CONFIG_FEATURE_VI_CRASHME
553 crash_test(); // test editor variables
554 #endif /* CONFIG_FEATURE_VI_CRASHME */
556 //-------------------------------------------------------------------
558 place_cursor(rows, 0, FALSE); // go to bottom of screen
559 clear_to_eol(); // Erase to end of line
563 //----- The Colon commands -------------------------------------
564 #ifdef CONFIG_FEATURE_VI_COLON
565 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
570 #ifdef CONFIG_FEATURE_VI_YANKMARK
572 #endif /* CONFIG_FEATURE_VI_YANKMARK */
573 #ifdef CONFIG_FEATURE_VI_SEARCH
574 Byte *pat, buf[BUFSIZ];
575 #endif /* CONFIG_FEATURE_VI_SEARCH */
577 *addr = -1; // assume no addr
578 if (*p == '.') { // the current line
581 *addr = count_lines(text, q);
582 #ifdef CONFIG_FEATURE_VI_YANKMARK
583 } else if (*p == '\'') { // is this a mark addr
587 if (c >= 'a' && c <= 'z') {
591 if (q != NULL) { // is mark valid
592 *addr = count_lines(text, q); // count lines
595 #endif /* CONFIG_FEATURE_VI_YANKMARK */
596 #ifdef CONFIG_FEATURE_VI_SEARCH
597 } else if (*p == '/') { // a search pattern
605 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
608 q = char_search(dot, pat, FORWARD, FULL);
610 *addr = count_lines(text, q);
613 #endif /* CONFIG_FEATURE_VI_SEARCH */
614 } else if (*p == '$') { // the last line in file
616 q = begin_line(end - 1);
617 *addr = count_lines(text, q);
618 } else if (isdigit(*p)) { // specific line number
619 sscanf((char *) p, "%d%n", addr, &st);
621 } else { // I don't reconise this
622 // unrecognised address- assume -1
628 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
630 //----- get the address' i.e., 1,3 'a,'b -----
631 // get FIRST addr, if present
633 p++; // skip over leading spaces
634 if (*p == '%') { // alias for 1,$
637 *e = count_lines(text, end-1);
640 p = get_one_address(p, b);
643 if (*p == ',') { // is there a address seperator
647 // get SECOND addr, if present
648 p = get_one_address(p, e);
652 p++; // skip over trailing spaces
656 #ifdef CONFIG_FEATURE_VI_SETOPTS
657 static void setops(const Byte *args, const char *opname, int flg_no,
658 const char *short_opname, int opt)
660 const char *a = (char *) args + flg_no;
661 int l = strlen(opname) - 1; /* opname have + ' ' */
663 if (strncasecmp(a, opname, l) == 0 ||
664 strncasecmp(a, short_opname, 2) == 0) {
673 static void colon(Byte * buf)
675 Byte c, *orig_buf, *buf1, *q, *r;
676 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
677 int i, l, li, ch, st, b, e;
678 int useforce, forced;
681 // :3154 // if (-e line 3154) goto it else stay put
682 // :4,33w! foo // write a portion of buffer to file "foo"
683 // :w // write all of buffer to current file
685 // :q! // quit- dont care about modified file
686 // :'a,'z!sort -u // filter block through sort
687 // :'f // goto mark "f"
688 // :'fl // list literal the mark "f" line
689 // :.r bar // read file "bar" into buffer before dot
690 // :/123/,/abc/d // delete lines from "123" line to "abc" line
691 // :/xyz/ // goto the "xyz" line
692 // :s/find/replace/ // substitute pattern "find" with "replace"
693 // :!<cmd> // run <cmd> then return
695 if (strlen((char *) buf) <= 0)
698 buf++; // move past the ':'
700 forced = useforce = FALSE;
701 li = st = ch = i = 0;
703 q = text; // assume 1,$ for the range
705 li = count_lines(text, end - 1);
706 fn = cfn; // default to current file
707 memset(cmd, '\0', BUFSIZ); // clear cmd[]
708 memset(args, '\0', BUFSIZ); // clear args[]
710 // look for optional address(es) :. :1 :1,9 :'q,'a :%
711 buf = get_address(buf, &b, &e);
713 // remember orig command line
716 // get the COMMAND into cmd[]
718 while (*buf != '\0') {
726 strcpy((char *) args, (char *) buf);
727 buf1 = last_char_is((char *)cmd, '!');
730 *buf1 = '\0'; // get rid of !
733 // if there is only one addr, then the addr
734 // is the line number of the single line the
735 // user wants. So, reset the end
736 // pointer to point at end of the "b" line
737 q = find_line(b); // what line is #b
742 // we were given two addrs. change the
743 // end pointer to the addr given by user.
744 r = find_line(e); // what line is #e
748 // ------------ now look for the command ------------
749 i = strlen((char *) cmd);
750 if (i == 0) { // :123CR goto line #123
752 dot = find_line(b); // what line is #b
755 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
756 // :!ls run the <cmd>
757 (void) alarm(0); // wait for input- no alarms
758 place_cursor(rows - 1, 0, FALSE); // go to Status line
759 clear_to_eol(); // clear the line
761 system(orig_buf+1); // run the cmd
763 Hit_Return(); // let user see results
764 (void) alarm(3); // done waiting for input
765 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
766 if (b < 0) { // no addr given- use defaults
767 b = e = count_lines(text, dot);
770 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
771 if (b < 0) { // no addr given- use defaults
772 q = begin_line(dot); // assume .,. for the range
775 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
777 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
780 // don't edit, if the current file has been modified
781 if (file_modified && ! useforce) {
782 psbs("No write since last change (:edit! overrides)");
785 if (strlen(args) > 0) {
786 // the user supplied a file name
788 } else if (cfn != 0 && strlen(cfn) > 0) {
789 // no user supplied name- use the current filename
793 // no user file name, no current name- punt
794 psbs("No current filename");
798 // see if file exists- if not, its just a new file request
799 if ((sr=stat((char*)fn, &st_buf)) < 0) {
800 // This is just a request for a new file creation.
801 // The file_insert below will fail but we get
802 // an empty buffer with a file name. Then the "write"
803 // command can do the create.
805 if ((st_buf.st_mode & (S_IFREG)) == 0) {
806 // This is not a regular file
807 psbs("\"%s\" is not a regular file", fn);
810 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
811 // dont have any read permissions
812 psbs("\"%s\" is not readable", fn);
817 // There is a read-able regular file
818 // make this the current file
819 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
820 free(cfn); // free the old name
821 cfn = q; // remember new cfn
824 // delete all the contents of text[]
825 new_text(2 * file_size(fn));
826 screenbegin = dot = end = text;
829 ch = file_insert(fn, text, file_size(fn));
832 // start empty buf with dummy line
833 (void) char_insert(text, '\n');
836 file_modified = FALSE;
837 #ifdef CONFIG_FEATURE_VI_YANKMARK
838 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
839 free(reg[Ureg]); // free orig line reg- for 'U'
842 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
843 free(reg[YDreg]); // free default yank/delete register
846 for (li = 0; li < 28; li++) {
849 #endif /* CONFIG_FEATURE_VI_YANKMARK */
850 // how many lines in text[]?
851 li = count_lines(text, end - 1);
853 #ifdef CONFIG_FEATURE_VI_READONLY
855 #endif /* CONFIG_FEATURE_VI_READONLY */
857 (sr < 0 ? " [New file]" : ""),
858 #ifdef CONFIG_FEATURE_VI_READONLY
859 ((vi_readonly || readonly) ? " [Read only]" : ""),
860 #endif /* CONFIG_FEATURE_VI_READONLY */
862 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
863 if (b != -1 || e != -1) {
864 ni((Byte *) "No address allowed on this command");
867 if (strlen((char *) args) > 0) {
868 // user wants a new filename
870 cfn = (Byte *) bb_xstrdup((char *) args);
872 // user wants file status info
875 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
876 // print out values of all features
877 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
878 clear_to_eol(); // clear the line
883 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
884 if (b < 0) { // no addr given- use defaults
885 q = begin_line(dot); // assume .,. for the range
888 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
889 clear_to_eol(); // clear the line
891 for (; q <= r; q++) {
895 c_is_no_print = c > 127 && !Isprint(c);
902 } else if (c < ' ' || c == 127) {
913 #ifdef CONFIG_FEATURE_VI_SET
915 #endif /* CONFIG_FEATURE_VI_SET */
917 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
918 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
920 // force end of argv list
927 // don't exit if the file been modified
929 psbs("No write since last change (:%s! overrides)",
930 (*cmd == 'q' ? "quit" : "next"));
933 // are there other file to edit
934 if (*cmd == 'q' && optind < save_argc - 1) {
935 psbs("%d more file to edit", (save_argc - optind - 1));
938 if (*cmd == 'n' && optind >= save_argc - 1) {
939 psbs("No more files to edit");
943 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
945 if (strlen((char *) fn) <= 0) {
946 psbs("No filename given");
949 if (b < 0) { // no addr given- use defaults
950 q = begin_line(dot); // assume "dot"
952 // read after current line- unless user said ":0r foo"
955 #ifdef CONFIG_FEATURE_VI_READONLY
956 l= readonly; // remember current files' status
958 ch = file_insert(fn, q, file_size(fn));
959 #ifdef CONFIG_FEATURE_VI_READONLY
963 goto vc1; // nothing was inserted
964 // how many lines in text[]?
965 li = count_lines(q, q + ch - 1);
967 #ifdef CONFIG_FEATURE_VI_READONLY
969 #endif /* CONFIG_FEATURE_VI_READONLY */
971 #ifdef CONFIG_FEATURE_VI_READONLY
972 ((vi_readonly || readonly) ? " [Read only]" : ""),
973 #endif /* CONFIG_FEATURE_VI_READONLY */
976 // if the insert is before "dot" then we need to update
979 file_modified = TRUE;
981 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
982 if (file_modified && ! useforce) {
983 psbs("No write since last change (:rewind! overrides)");
985 // reset the filenames to edit
986 optind = fn_start - 1;
989 #ifdef CONFIG_FEATURE_VI_SET
990 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
991 i = 0; // offset into args
992 if (strlen((char *) args) == 0) {
993 // print out values of all options
994 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
995 clear_to_eol(); // clear the line
996 printf("----------------------------------------\r\n");
997 #ifdef CONFIG_FEATURE_VI_SETOPTS
1000 printf("autoindent ");
1006 printf("ignorecase ");
1009 printf("showmatch ");
1010 printf("tabstop=%d ", tabstop);
1011 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1015 if (strncasecmp((char *) args, "no", 2) == 0)
1016 i = 2; // ":set noautoindent"
1017 #ifdef CONFIG_FEATURE_VI_SETOPTS
1018 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1019 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1020 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1021 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1022 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1023 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1024 if (ch > 0 && ch < columns - 1)
1027 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1028 #endif /* CONFIG_FEATURE_VI_SET */
1029 #ifdef CONFIG_FEATURE_VI_SEARCH
1030 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1034 // F points to the "find" pattern
1035 // R points to the "replace" pattern
1036 // replace the cmd line delimiters "/" with NULLs
1037 gflag = 0; // global replace flag
1038 c = orig_buf[1]; // what is the delimiter
1039 F = orig_buf + 2; // start of "find"
1040 R = (Byte *) strchr((char *) F, c); // middle delimiter
1041 if (!R) goto colon_s_fail;
1042 *R++ = '\0'; // terminate "find"
1043 buf1 = (Byte *) strchr((char *) R, c);
1044 if (!buf1) goto colon_s_fail;
1045 *buf1++ = '\0'; // terminate "replace"
1046 if (*buf1 == 'g') { // :s/foo/bar/g
1048 gflag++; // turn on gflag
1051 if (b < 0) { // maybe :s/foo/bar/
1052 q = begin_line(dot); // start with cur line
1053 b = count_lines(text, q); // cur line number
1056 e = b; // maybe :.s/foo/bar/
1057 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1058 ls = q; // orig line start
1060 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1062 // we found the "find" pattern- delete it
1063 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1064 // inset the "replace" patern
1065 (void) string_insert(buf1, R); // insert the string
1066 // check for "global" :s/foo/bar/g
1068 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1069 q = buf1 + strlen((char *) R);
1070 goto vc4; // don't let q move past cur line
1076 #endif /* CONFIG_FEATURE_VI_SEARCH */
1077 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1078 psb("%s", vi_Version);
1079 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1080 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1081 (strncasecmp((char *) cmd, "x", i) == 0)) {
1082 // is there a file name to write to?
1083 if (strlen((char *) args) > 0) {
1086 #ifdef CONFIG_FEATURE_VI_READONLY
1087 if ((vi_readonly || readonly) && ! useforce) {
1088 psbs("\"%s\" File is read only", fn);
1091 #endif /* CONFIG_FEATURE_VI_READONLY */
1092 // how many lines in text[]?
1093 li = count_lines(q, r);
1095 // see if file exists- if not, its just a new file request
1097 // if "fn" is not write-able, chmod u+w
1098 // sprintf(syscmd, "chmod u+w %s", fn);
1102 l = file_write(fn, q, r);
1103 if (useforce && forced) {
1105 // sprintf(syscmd, "chmod u-w %s", fn);
1109 psb("\"%s\" %dL, %dC", fn, li, l);
1110 if (q == text && r == end - 1 && l == ch)
1111 file_modified = FALSE;
1112 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1115 #ifdef CONFIG_FEATURE_VI_READONLY
1117 #endif /* CONFIG_FEATURE_VI_READONLY */
1118 #ifdef CONFIG_FEATURE_VI_YANKMARK
1119 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1120 if (b < 0) { // no addr given- use defaults
1121 q = begin_line(dot); // assume .,. for the range
1124 text_yank(q, r, YDreg);
1125 li = count_lines(q, r);
1126 psb("Yank %d lines (%d chars) into [%c]",
1127 li, strlen((char *) reg[YDreg]), what_reg());
1128 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1134 dot = bound_dot(dot); // make sure "dot" is valid
1136 #ifdef CONFIG_FEATURE_VI_SEARCH
1138 psb(":s expression missing delimiters");
1142 static void Hit_Return(void)
1146 standout_start(); // start reverse video
1147 write1("[Hit return to continue]");
1148 standout_end(); // end reverse video
1149 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1151 redraw(TRUE); // force redraw all
1153 #endif /* CONFIG_FEATURE_VI_COLON */
1155 //----- Synchronize the cursor to Dot --------------------------
1156 static void sync_cursor(Byte * d, int *row, int *col)
1158 Byte *beg_cur, *end_cur; // begin and end of "d" line
1159 Byte *beg_scr, *end_scr; // begin and end of screen
1163 beg_cur = begin_line(d); // first char of cur line
1164 end_cur = end_line(d); // last char of cur line
1166 beg_scr = end_scr = screenbegin; // first char of screen
1167 end_scr = end_screen(); // last char of screen
1169 if (beg_cur < screenbegin) {
1170 // "d" is before top line on screen
1171 // how many lines do we have to move
1172 cnt = count_lines(beg_cur, screenbegin);
1174 screenbegin = beg_cur;
1175 if (cnt > (rows - 1) / 2) {
1176 // we moved too many lines. put "dot" in middle of screen
1177 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1178 screenbegin = prev_line(screenbegin);
1181 } else if (beg_cur > end_scr) {
1182 // "d" is after bottom line on screen
1183 // how many lines do we have to move
1184 cnt = count_lines(end_scr, beg_cur);
1185 if (cnt > (rows - 1) / 2)
1186 goto sc1; // too many lines
1187 for (ro = 0; ro < cnt - 1; ro++) {
1188 // move screen begin the same amount
1189 screenbegin = next_line(screenbegin);
1190 // now, move the end of screen
1191 end_scr = next_line(end_scr);
1192 end_scr = end_line(end_scr);
1195 // "d" is on screen- find out which row
1197 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1203 // find out what col "d" is on
1205 do { // drive "co" to correct column
1206 if (*tp == '\n' || *tp == '\0')
1210 co += ((tabstop - 1) - (co % tabstop));
1211 } else if (*tp < ' ' || *tp == 127) {
1212 co++; // display as ^X, use 2 columns
1214 } while (tp++ < d && ++co);
1216 // "co" is the column where "dot" is.
1217 // The screen has "columns" columns.
1218 // The currently displayed columns are 0+offset -- columns+ofset
1219 // |-------------------------------------------------------------|
1221 // offset | |------- columns ----------------|
1223 // If "co" is already in this range then we do not have to adjust offset
1224 // but, we do have to subtract the "offset" bias from "co".
1225 // If "co" is outside this range then we have to change "offset".
1226 // If the first char of a line is a tab the cursor will try to stay
1227 // in column 7, but we have to set offset to 0.
1229 if (co < 0 + offset) {
1232 if (co >= columns + offset) {
1233 offset = co - columns + 1;
1235 // if the first char of the line is a tab, and "dot" is sitting on it
1236 // force offset to 0.
1237 if (d == beg_cur && *d == '\t') {
1246 //----- Text Movement Routines ---------------------------------
1247 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1249 while (p > text && p[-1] != '\n')
1250 p--; // go to cur line B-o-l
1254 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1256 while (p < end - 1 && *p != '\n')
1257 p++; // go to cur line E-o-l
1261 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1263 while (p < end - 1 && *p != '\n')
1264 p++; // go to cur line E-o-l
1265 // Try to stay off of the Newline
1266 if (*p == '\n' && (p - begin_line(p)) > 0)
1271 static Byte *prev_line(Byte * p) // return pointer first char prev line
1273 p = begin_line(p); // goto begining of cur line
1274 if (p[-1] == '\n' && p > text)
1275 p--; // step to prev line
1276 p = begin_line(p); // goto begining of prev line
1280 static Byte *next_line(Byte * p) // return pointer first char next line
1283 if (*p == '\n' && p < end - 1)
1284 p++; // step to next line
1288 //----- Text Information Routines ------------------------------
1289 static Byte *end_screen(void)
1294 // find new bottom line
1296 for (cnt = 0; cnt < rows - 2; cnt++)
1302 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1307 if (stop < start) { // start and stop are backwards- reverse them
1313 stop = end_line(stop); // get to end of this line
1314 for (q = start; q <= stop && q <= end - 1; q++) {
1321 static Byte *find_line(int li) // find begining of line #li
1325 for (q = text; li > 1; li--) {
1331 //----- Dot Movement Routines ----------------------------------
1332 static void dot_left(void)
1334 if (dot > text && dot[-1] != '\n')
1338 static void dot_right(void)
1340 if (dot < end - 1 && *dot != '\n')
1344 static void dot_begin(void)
1346 dot = begin_line(dot); // return pointer to first char cur line
1349 static void dot_end(void)
1351 dot = end_line(dot); // return pointer to last char cur line
1354 static Byte *move_to_col(Byte * p, int l)
1361 if (*p == '\n' || *p == '\0')
1365 co += ((tabstop - 1) - (co % tabstop));
1366 } else if (*p < ' ' || *p == 127) {
1367 co++; // display as ^X, use 2 columns
1369 } while (++co <= l && p++ < end);
1373 static void dot_next(void)
1375 dot = next_line(dot);
1378 static void dot_prev(void)
1380 dot = prev_line(dot);
1383 static void dot_scroll(int cnt, int dir)
1387 for (; cnt > 0; cnt--) {
1390 // ctrl-Y scroll up one line
1391 screenbegin = prev_line(screenbegin);
1394 // ctrl-E scroll down one line
1395 screenbegin = next_line(screenbegin);
1398 // make sure "dot" stays on the screen so we dont scroll off
1399 if (dot < screenbegin)
1401 q = end_screen(); // find new bottom line
1403 dot = begin_line(q); // is dot is below bottom line?
1407 static void dot_skip_over_ws(void)
1410 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1414 static void dot_delete(void) // delete the char at 'dot'
1416 (void) text_hole_delete(dot, dot);
1419 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1421 if (p >= end && end > text) {
1423 indicate_error('1');
1427 indicate_error('2');
1432 //----- Helper Utility Routines --------------------------------
1434 //----------------------------------------------------------------
1435 //----- Char Routines --------------------------------------------
1436 /* Chars that are part of a word-
1437 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1438 * Chars that are Not part of a word (stoppers)
1439 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1440 * Chars that are WhiteSpace
1441 * TAB NEWLINE VT FF RETURN SPACE
1442 * DO NOT COUNT NEWLINE AS WHITESPACE
1445 static Byte *new_screen(int ro, int co)
1450 screensize = ro * co + 8;
1451 screen = (Byte *) xmalloc(screensize);
1452 // initialize the new screen. assume this will be a empty file.
1454 // non-existant text[] lines start with a tilde (~).
1455 for (li = 1; li < ro - 1; li++) {
1456 screen[(li * co) + 0] = '~';
1461 static Byte *new_text(int size)
1464 size = 10240; // have a minimum size for new files
1466 text = (Byte *) xmalloc(size + 8);
1467 memset(text, '\0', size); // clear new text[]
1468 //text += 4; // leave some room for "oops"
1469 textend = text + size - 1;
1470 //textend -= 4; // leave some root for "oops"
1474 #ifdef CONFIG_FEATURE_VI_SEARCH
1475 static int mycmp(Byte * s1, Byte * s2, int len)
1479 i = strncmp((char *) s1, (char *) s2, len);
1480 #ifdef CONFIG_FEATURE_VI_SETOPTS
1482 i = strncasecmp((char *) s1, (char *) s2, len);
1484 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1488 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1490 #ifndef REGEX_SEARCH
1494 len = strlen((char *) pat);
1495 if (dir == FORWARD) {
1496 stop = end - 1; // assume range is p - end-1
1497 if (range == LIMITED)
1498 stop = next_line(p); // range is to next line
1499 for (start = p; start < stop; start++) {
1500 if (mycmp(start, pat, len) == 0) {
1504 } else if (dir == BACK) {
1505 stop = text; // assume range is text - p
1506 if (range == LIMITED)
1507 stop = prev_line(p); // range is to prev line
1508 for (start = p - len; start >= stop; start--) {
1509 if (mycmp(start, pat, len) == 0) {
1514 // pattern not found
1516 #else /*REGEX_SEARCH */
1518 struct re_pattern_buffer preg;
1522 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1528 // assume a LIMITED forward search
1536 // count the number of chars to search over, forward or backward
1540 // RANGE could be negative if we are searching backwards
1543 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1545 // The pattern was not compiled
1546 psbs("bad search pattern: \"%s\": %s", pat, q);
1547 i = 0; // return p if pattern not compiled
1557 // search for the compiled pattern, preg, in p[]
1558 // range < 0- search backward
1559 // range > 0- search forward
1561 // re_search() < 0 not found or error
1562 // re_search() > 0 index of found pattern
1563 // struct pattern char int int int struct reg
1564 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1565 i = re_search(&preg, q, size, 0, range, 0);
1568 i = 0; // return NULL if pattern not found
1571 if (dir == FORWARD) {
1577 #endif /*REGEX_SEARCH */
1579 #endif /* CONFIG_FEATURE_VI_SEARCH */
1581 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1583 if (c == 22) { // Is this an ctrl-V?
1584 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1585 p--; // backup onto ^
1586 refresh(FALSE); // show the ^
1590 file_modified = TRUE; // has the file been modified
1591 } else if (c == 27) { // Is this an ESC?
1594 end_cmd_q(); // stop adding to q
1595 strcpy((char *) status_buffer, " "); // clear the status buffer
1596 if ((p[-1] != '\n') && (dot>text)) {
1599 } else if (c == erase_char) { // Is this a BS
1601 if ((p[-1] != '\n') && (dot>text)) {
1603 p = text_hole_delete(p, p); // shrink buffer 1 char
1604 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1605 // also rmove char from last_modifying_cmd
1606 if (last_modifying_cmd != 0 && strlen((char *) last_modifying_cmd) > 0) {
1609 q = last_modifying_cmd;
1610 q[strlen((char *) q) - 1] = '\0'; // erase BS
1611 q[strlen((char *) q) - 1] = '\0'; // erase prev char
1613 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1616 // insert a char into text[]
1617 Byte *sp; // "save p"
1620 c = '\n'; // translate \r to \n
1621 sp = p; // remember addr of insert
1622 p = stupid_insert(p, c); // insert the char
1623 #ifdef CONFIG_FEATURE_VI_SETOPTS
1624 if (showmatch && strchr(")]}", *sp) != NULL) {
1627 if (autoindent && c == '\n') { // auto indent the new line
1630 q = prev_line(p); // use prev line as templet
1631 for (; isblnk(*q); q++) {
1632 p = stupid_insert(p, *q); // insert the char
1635 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1640 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1642 p = text_hole_make(p, 1);
1645 file_modified = TRUE; // has the file been modified
1651 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1653 Byte *save_dot, *p, *q;
1659 if (strchr("cdy><", c)) {
1660 // these cmds operate on whole lines
1661 p = q = begin_line(p);
1662 for (cnt = 1; cnt < cmdcnt; cnt++) {
1666 } else if (strchr("^%$0bBeEft", c)) {
1667 // These cmds operate on char positions
1668 do_cmd(c); // execute movement cmd
1670 } else if (strchr("wW", c)) {
1671 do_cmd(c); // execute movement cmd
1672 // if we are at the next word's first char
1673 // step back one char
1674 if (dot > text && isspace(dot[-1]))
1675 dot--; // move back off of next word
1676 if (dot > text && *dot == '\n')
1677 dot--; // stay off NL
1679 } else if (strchr("H-k{", c)) {
1680 // these operate on multi-lines backwards
1681 q = end_line(dot); // find NL
1682 do_cmd(c); // execute movement cmd
1685 } else if (strchr("L+j}\r\n", c)) {
1686 // these operate on multi-lines forwards
1687 p = begin_line(dot);
1688 do_cmd(c); // execute movement cmd
1689 dot_end(); // find NL
1692 c = 27; // error- return an ESC char
1705 static int st_test(Byte * p, int type, int dir, Byte * tested)
1715 if (type == S_BEFORE_WS) {
1717 test = ((!isspace(c)) || c == '\n');
1719 if (type == S_TO_WS) {
1721 test = ((!isspace(c)) || c == '\n');
1723 if (type == S_OVER_WS) {
1725 test = ((isspace(c)));
1727 if (type == S_END_PUNCT) {
1729 test = ((ispunct(c)));
1731 if (type == S_END_ALNUM) {
1733 test = ((isalnum(c)) || c == '_');
1739 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1743 while (st_test(p, type, dir, &c)) {
1744 // make sure we limit search to correct number of lines
1745 if (c == '\n' && --linecnt < 1)
1747 if (dir >= 0 && p >= end - 1)
1749 if (dir < 0 && p <= text)
1751 p += dir; // move to next char
1756 // find matching char of pair () [] {}
1757 static Byte *find_pair(Byte * p, Byte c)
1764 dir = 1; // assume forward
1788 for (q = p + dir; text <= q && q < end; q += dir) {
1789 // look for match, count levels of pairs (( ))
1791 level++; // increase pair levels
1793 level--; // reduce pair level
1795 break; // found matching pair
1798 q = NULL; // indicate no match
1802 #ifdef CONFIG_FEATURE_VI_SETOPTS
1803 // show the matching char of a pair, () [] {}
1804 static void showmatching(Byte * p)
1808 // we found half of a pair
1809 q = find_pair(p, *p); // get loc of matching char
1811 indicate_error('3'); // no matching char
1813 // "q" now points to matching pair
1814 save_dot = dot; // remember where we are
1815 dot = q; // go to new loc
1816 refresh(FALSE); // let the user see it
1817 (void) mysleep(40); // give user some time
1818 dot = save_dot; // go back to old loc
1822 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1824 // open a hole in text[]
1825 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1834 cnt = end - src; // the rest of buffer
1835 if (memmove(dest, src, cnt) != dest) {
1836 psbs("can't create room for new characters");
1838 memset(p, ' ', size); // clear new hole
1839 end = end + size; // adjust the new END
1840 file_modified = TRUE; // has the file been modified
1845 // close a hole in text[]
1846 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1851 // move forwards, from beginning
1855 if (q < p) { // they are backward- swap them
1859 hole_size = q - p + 1;
1861 if (src < text || src > end)
1863 if (dest < text || dest >= end)
1866 goto thd_atend; // just delete the end of the buffer
1867 if (memmove(dest, src, cnt) != dest) {
1868 psbs("can't delete the character");
1871 end = end - hole_size; // adjust the new END
1873 dest = end - 1; // make sure dest in below end-1
1875 dest = end = text; // keep pointers valid
1876 file_modified = TRUE; // has the file been modified
1881 // copy text into register, then delete text.
1882 // if dist <= 0, do not include, or go past, a NewLine
1884 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1888 // make sure start <= stop
1890 // they are backwards, reverse them
1896 // we can not cross NL boundaries
1900 // dont go past a NewLine
1901 for (; p + 1 <= stop; p++) {
1903 stop = p; // "stop" just before NewLine
1909 #ifdef CONFIG_FEATURE_VI_YANKMARK
1910 text_yank(start, stop, YDreg);
1911 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1912 if (yf == YANKDEL) {
1913 p = text_hole_delete(start, stop);
1918 static void show_help(void)
1920 puts("These features are available:"
1921 #ifdef CONFIG_FEATURE_VI_SEARCH
1922 "\n\tPattern searches with / and ?"
1923 #endif /* CONFIG_FEATURE_VI_SEARCH */
1924 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1925 "\n\tLast command repeat with \'.\'"
1926 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1927 #ifdef CONFIG_FEATURE_VI_YANKMARK
1928 "\n\tLine marking with 'x"
1929 "\n\tNamed buffers with \"x"
1930 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1931 #ifdef CONFIG_FEATURE_VI_READONLY
1932 "\n\tReadonly if vi is called as \"view\""
1933 "\n\tReadonly with -R command line arg"
1934 #endif /* CONFIG_FEATURE_VI_READONLY */
1935 #ifdef CONFIG_FEATURE_VI_SET
1936 "\n\tSome colon mode commands with \':\'"
1937 #endif /* CONFIG_FEATURE_VI_SET */
1938 #ifdef CONFIG_FEATURE_VI_SETOPTS
1939 "\n\tSettable options with \":set\""
1940 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1941 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1942 "\n\tSignal catching- ^C"
1943 "\n\tJob suspend and resume with ^Z"
1944 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1945 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1946 "\n\tAdapt to window re-sizes"
1947 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1951 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1956 strcpy((char *) buf, ""); // init buf
1957 if (strlen((char *) s) <= 0)
1958 s = (Byte *) "(NULL)";
1959 for (; *s > '\0'; s++) {
1963 c_is_no_print = c > 127 && !Isprint(c);
1964 if (c_is_no_print) {
1965 strcat((char *) buf, SOn);
1968 if (c < ' ' || c == 127) {
1969 strcat((char *) buf, "^");
1976 strcat((char *) buf, (char *) b);
1978 strcat((char *) buf, SOs);
1980 strcat((char *) buf, "$");
1985 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1986 static void start_new_cmd_q(Byte c)
1989 free(last_modifying_cmd);
1990 // get buffer for new cmd
1991 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1992 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1993 // if there is a current cmd count put it in the buffer first
1995 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
1996 // save char c onto queue
1997 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2002 static void end_cmd_q(void)
2004 #ifdef CONFIG_FEATURE_VI_YANKMARK
2005 YDreg = 26; // go back to default Yank/Delete reg
2006 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2010 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2012 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2013 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2017 i = strlen((char *) s);
2018 p = text_hole_make(p, i);
2019 strncpy((char *) p, (char *) s, i);
2020 for (cnt = 0; *s != '\0'; s++) {
2024 #ifdef CONFIG_FEATURE_VI_YANKMARK
2025 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2026 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2029 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2031 #ifdef CONFIG_FEATURE_VI_YANKMARK
2032 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2037 if (q < p) { // they are backwards- reverse them
2044 free(t); // if already a yank register, free it
2045 t = (Byte *) xmalloc(cnt + 1); // get a new register
2046 memset(t, '\0', cnt + 1); // clear new text[]
2047 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2052 static Byte what_reg(void)
2058 c = 'D'; // default to D-reg
2059 if (0 <= YDreg && YDreg <= 25)
2060 c = 'a' + (Byte) YDreg;
2068 static void check_context(Byte cmd)
2070 // A context is defined to be "modifying text"
2071 // Any modifying command establishes a new context.
2073 if (dot < context_start || dot > context_end) {
2074 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2075 // we are trying to modify text[]- make this the current context
2076 mark[27] = mark[26]; // move cur to prev
2077 mark[26] = dot; // move local to cur
2078 context_start = prev_line(prev_line(dot));
2079 context_end = next_line(next_line(dot));
2080 //loiter= start_loiter= now;
2086 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2090 // the current context is in mark[26]
2091 // the previous context is in mark[27]
2092 // only swap context if other context is valid
2093 if (text <= mark[27] && mark[27] <= end - 1) {
2095 mark[27] = mark[26];
2097 p = mark[26]; // where we are going- previous context
2098 context_start = prev_line(prev_line(prev_line(p)));
2099 context_end = next_line(next_line(next_line(p)));
2103 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2105 static int isblnk(Byte c) // is the char a blank or tab
2107 return (c == ' ' || c == '\t');
2110 //----- Set terminal attributes --------------------------------
2111 static void rawmode(void)
2113 tcgetattr(0, &term_orig);
2114 term_vi = term_orig;
2115 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2116 term_vi.c_iflag &= (~IXON & ~ICRNL);
2117 term_vi.c_oflag &= (~ONLCR);
2118 term_vi.c_cc[VMIN] = 1;
2119 term_vi.c_cc[VTIME] = 0;
2120 erase_char = term_vi.c_cc[VERASE];
2121 tcsetattr(0, TCSANOW, &term_vi);
2124 static void cookmode(void)
2127 tcsetattr(0, TCSANOW, &term_orig);
2130 //----- Come here when we get a window resize signal ---------
2131 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2132 static void winch_sig(int sig)
2134 signal(SIGWINCH, winch_sig);
2135 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2137 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2138 new_screen(rows, columns); // get memory for virtual screen
2139 redraw(TRUE); // re-draw the screen
2142 //----- Come here when we get a continue signal -------------------
2143 static void cont_sig(int sig)
2145 rawmode(); // terminal to "raw"
2146 *status_buffer = '\0'; // clear the status buffer
2147 redraw(TRUE); // re-draw the screen
2149 signal(SIGTSTP, suspend_sig);
2150 signal(SIGCONT, SIG_DFL);
2151 kill(my_pid, SIGCONT);
2154 //----- Come here when we get a Suspend signal -------------------
2155 static void suspend_sig(int sig)
2157 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2158 clear_to_eol(); // Erase to end of line
2159 cookmode(); // terminal to "cooked"
2161 signal(SIGCONT, cont_sig);
2162 signal(SIGTSTP, SIG_DFL);
2163 kill(my_pid, SIGTSTP);
2166 //----- Come here when we get a signal ---------------------------
2167 static void catch_sig(int sig)
2169 signal(SIGHUP, catch_sig);
2170 signal(SIGINT, catch_sig);
2171 signal(SIGTERM, catch_sig);
2172 signal(SIGALRM, catch_sig);
2174 longjmp(restart, sig);
2177 //----- Come here when we get a core dump signal -----------------
2178 static void core_sig(int sig)
2180 signal(SIGQUIT, core_sig);
2181 signal(SIGILL, core_sig);
2182 signal(SIGTRAP, core_sig);
2183 signal(SIGIOT, core_sig);
2184 signal(SIGABRT, core_sig);
2185 signal(SIGFPE, core_sig);
2186 signal(SIGBUS, core_sig);
2187 signal(SIGSEGV, core_sig);
2189 signal(SIGSYS, core_sig);
2192 if(sig) { // signaled
2193 dot = bound_dot(dot); // make sure "dot" is valid
2195 longjmp(restart, sig);
2198 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2200 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2202 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2207 tv.tv_usec = hund * 10000;
2208 select(1, &rfds, NULL, NULL, &tv);
2209 return (FD_ISSET(0, &rfds));
2212 static Byte readbuffer[BUFSIZ];
2213 static int readed_for_parse;
2215 //----- IO Routines --------------------------------------------
2216 static Byte readit(void) // read (maybe cursor) key from stdin
2225 static const struct esc_cmds esccmds[] = {
2226 {"OA", (Byte) VI_K_UP}, // cursor key Up
2227 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2228 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2229 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2230 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2231 {"OF", (Byte) VI_K_END}, // Cursor Key End
2232 {"[A", (Byte) VI_K_UP}, // cursor key Up
2233 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2234 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2235 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2236 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2237 {"[F", (Byte) VI_K_END}, // Cursor Key End
2238 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2239 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2240 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2241 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2242 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2243 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2244 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2245 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2246 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2247 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2248 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2249 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2250 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2251 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2252 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2253 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2254 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2255 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2256 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2257 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2258 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2261 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2263 (void) alarm(0); // turn alarm OFF while we wait for input
2265 n = readed_for_parse;
2266 // get input from User- are there already input chars in Q?
2269 // the Q is empty, wait for a typed char
2270 n = read(0, readbuffer, BUFSIZ - 1);
2273 goto ri0; // interrupted sys call
2276 if (errno == EFAULT)
2278 if (errno == EINVAL)
2286 if (readbuffer[0] == 27) {
2287 // This is an ESC char. Is this Esc sequence?
2288 // Could be bare Esc key. See if there are any
2289 // more chars to read after the ESC. This would
2290 // be a Function or Cursor Key sequence.
2294 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2296 // keep reading while there are input chars and room in buffer
2297 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2298 // read the rest of the ESC string
2299 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2305 readed_for_parse = n;
2308 if(c == 27 && n > 1) {
2309 // Maybe cursor or function key?
2310 const struct esc_cmds *eindex;
2312 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2313 int cnt = strlen(eindex->seq);
2317 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2319 // is a Cursor key- put derived value back into Q
2321 // for squeeze out the ESC sequence
2325 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2326 /* defined ESC sequence not found, set only one ESC */
2332 // remove key sequence from Q
2333 readed_for_parse -= n;
2334 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2335 (void) alarm(3); // we are done waiting for input, turn alarm ON
2339 //----- IO Routines --------------------------------------------
2340 static Byte get_one_char()
2344 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2345 // ! adding2q && ioq == 0 read()
2346 // ! adding2q && ioq != 0 *ioq
2347 // adding2q *last_modifying_cmd= read()
2349 // we are not adding to the q.
2350 // but, we may be reading from a q
2352 // there is no current q, read from STDIN
2353 c = readit(); // get the users input
2355 // there is a queue to get chars from first
2358 // the end of the q, read from STDIN
2360 ioq_start = ioq = 0;
2361 c = readit(); // get the users input
2365 // adding STDIN chars to q
2366 c = readit(); // get the users input
2367 if (last_modifying_cmd != 0) {
2368 int len = strlen((char *) last_modifying_cmd);
2369 if (len + 1 >= BUFSIZ) {
2370 psbs("last_modifying_cmd overrun");
2372 // add new char to q
2373 last_modifying_cmd[len] = c;
2377 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2378 c = readit(); // get the users input
2379 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2380 return (c); // return the char, where ever it came from
2383 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2388 static Byte *obufp = NULL;
2390 strcpy((char *) buf, (char *) prompt);
2391 *status_buffer = '\0'; // clear the status buffer
2392 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2393 clear_to_eol(); // clear the line
2394 write1(prompt); // write out the :, /, or ? prompt
2396 for (i = strlen((char *) buf); i < BUFSIZ;) {
2397 c = get_one_char(); // read user input
2398 if (c == '\n' || c == '\r' || c == 27)
2399 break; // is this end of input
2400 if (c == erase_char) { // user wants to erase prev char
2401 i--; // backup to prev char
2402 buf[i] = '\0'; // erase the char
2403 buf[i + 1] = '\0'; // null terminate buffer
2404 write1("\b \b"); // erase char on screen
2405 if (i <= 0) { // user backs up before b-o-l, exit
2409 buf[i] = c; // save char in buffer
2410 buf[i + 1] = '\0'; // make sure buffer is null terminated
2411 putchar(c); // echo the char back to user
2417 obufp = (Byte *) bb_xstrdup((char *) buf);
2421 static int file_size(const Byte * fn) // what is the byte size of "fn"
2426 if (fn == 0 || strlen(fn) <= 0)
2429 sr = stat((char *) fn, &st_buf); // see if file exists
2431 cnt = (int) st_buf.st_size;
2436 static int file_insert(Byte * fn, Byte * p, int size)
2441 #ifdef CONFIG_FEATURE_VI_READONLY
2443 #endif /* CONFIG_FEATURE_VI_READONLY */
2444 if (fn == 0 || strlen((char*) fn) <= 0) {
2445 psbs("No filename given");
2449 // OK- this is just a no-op
2454 psbs("Trying to insert a negative number (%d) of characters", size);
2457 if (p < text || p > end) {
2458 psbs("Trying to insert file outside of memory");
2462 // see if we can open the file
2463 #ifdef CONFIG_FEATURE_VI_READONLY
2464 if (vi_readonly) goto fi1; // do not try write-mode
2466 fd = open((char *) fn, O_RDWR); // assume read & write
2468 // could not open for writing- maybe file is read only
2469 #ifdef CONFIG_FEATURE_VI_READONLY
2472 fd = open((char *) fn, O_RDONLY); // try read-only
2474 psbs("\"%s\" %s", fn, "could not open file");
2477 #ifdef CONFIG_FEATURE_VI_READONLY
2478 // got the file- read-only
2480 #endif /* CONFIG_FEATURE_VI_READONLY */
2482 p = text_hole_make(p, size);
2483 cnt = read(fd, p, size);
2487 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2488 psbs("could not read file \"%s\"", fn);
2489 } else if (cnt < size) {
2490 // There was a partial read, shrink unused space text[]
2491 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2492 psbs("could not read all of file \"%s\"", fn);
2495 file_modified = TRUE;
2500 static int file_write(Byte * fn, Byte * first, Byte * last)
2502 int fd, cnt, charcnt;
2505 psbs("No current filename");
2509 // FIXIT- use the correct umask()
2510 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2513 cnt = last - first + 1;
2514 charcnt = write(fd, first, cnt);
2515 if (charcnt == cnt) {
2517 //file_modified= FALSE; // the file has not been modified
2525 //----- Terminal Drawing ---------------------------------------
2526 // The terminal is made up of 'rows' line of 'columns' columns.
2527 // classicly this would be 24 x 80.
2528 // screen coordinates
2534 // 23,0 ... 23,79 status line
2537 //----- Move the cursor to row x col (count from 0, not 1) -------
2538 static void place_cursor(int row, int col, int opti)
2542 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2545 // char cm3[BUFSIZ];
2547 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2549 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2551 if (row < 0) row = 0;
2552 if (row >= rows) row = rows - 1;
2553 if (col < 0) col = 0;
2554 if (col >= columns) col = columns - 1;
2556 //----- 1. Try the standard terminal ESC sequence
2557 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2559 if (! opti) goto pc0;
2561 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2562 //----- find the minimum # of chars to move cursor -------------
2563 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2564 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2566 // move to the correct row
2567 while (row < Rrow) {
2568 // the cursor has to move up
2572 while (row > Rrow) {
2573 // the cursor has to move down
2574 strcat(cm2, CMdown);
2578 // now move to the correct column
2579 strcat(cm2, "\r"); // start at col 0
2580 // just send out orignal source char to get to correct place
2581 screenp = &screen[row * columns]; // start of screen line
2582 strncat(cm2, screenp, col);
2584 //----- 3. Try some other way of moving cursor
2585 //---------------------------------------------
2587 // pick the shortest cursor motion to send out
2589 if (strlen(cm2) < strlen(cm)) {
2591 } /* else if (strlen(cm3) < strlen(cm)) {
2594 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2596 write1(cm); // move the cursor
2599 //----- Erase from cursor to end of line -----------------------
2600 static void clear_to_eol()
2602 write1(Ceol); // Erase from cursor to end of line
2605 //----- Erase from cursor to end of screen -----------------------
2606 static void clear_to_eos()
2608 write1(Ceos); // Erase from cursor to end of screen
2611 //----- Start standout mode ------------------------------------
2612 static void standout_start() // send "start reverse video" sequence
2614 write1(SOs); // Start reverse video mode
2617 //----- End standout mode --------------------------------------
2618 static void standout_end() // send "end reverse video" sequence
2620 write1(SOn); // End reverse video mode
2623 //----- Flash the screen --------------------------------------
2624 static void flash(int h)
2626 standout_start(); // send "start reverse video" sequence
2629 standout_end(); // send "end reverse video" sequence
2633 static void Indicate_Error(void)
2635 #ifdef CONFIG_FEATURE_VI_CRASHME
2637 return; // generate a random command
2638 #endif /* CONFIG_FEATURE_VI_CRASHME */
2640 write1(bell); // send out a bell character
2646 //----- Screen[] Routines --------------------------------------
2647 //----- Erase the Screen[] memory ------------------------------
2648 static void screen_erase()
2650 memset(screen, ' ', screensize); // clear new screen
2653 //----- Draw the status line at bottom of the screen -------------
2654 static void show_status_line(void)
2656 static int last_cksum;
2659 cnt = strlen((char *) status_buffer);
2660 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
2661 // don't write the status line unless it changes
2662 if (cnt > 0 && last_cksum != cksum) {
2663 last_cksum= cksum; // remember if we have seen this line
2664 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2665 write1(status_buffer);
2667 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2671 //----- format the status buffer, the bottom line of screen ------
2672 // print status buffer, with STANDOUT mode
2673 static void psbs(const char *format, ...)
2677 va_start(args, format);
2678 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2679 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
2681 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2687 // print status buffer
2688 static void psb(const char *format, ...)
2692 va_start(args, format);
2693 vsprintf((char *) status_buffer, format, args);
2698 static void ni(Byte * s) // display messages
2702 print_literal(buf, s);
2703 psbs("\'%s\' is not implemented", buf);
2706 static void edit_status(void) // show file status on status line
2708 int cur, tot, percent;
2710 cur = count_lines(text, dot);
2711 tot = count_lines(text, end - 1);
2712 // current line percent
2713 // ------------- ~~ ----------
2716 percent = (100 * cur) / tot;
2722 #ifdef CONFIG_FEATURE_VI_READONLY
2724 #endif /* CONFIG_FEATURE_VI_READONLY */
2725 "%s line %d of %d --%d%%--",
2726 (cfn != 0 ? (char *) cfn : "No file"),
2727 #ifdef CONFIG_FEATURE_VI_READONLY
2728 ((vi_readonly || readonly) ? " [Read only]" : ""),
2729 #endif /* CONFIG_FEATURE_VI_READONLY */
2730 (file_modified ? " [modified]" : ""),
2734 //----- Force refresh of all Lines -----------------------------
2735 static void redraw(int full_screen)
2737 place_cursor(0, 0, FALSE); // put cursor in correct place
2738 clear_to_eos(); // tel terminal to erase display
2739 screen_erase(); // erase the internal screen buffer
2740 refresh(full_screen); // this will redraw the entire display
2743 //----- Format a text[] line into a buffer ---------------------
2744 static void format_line(Byte *dest, Byte *src, int li)
2749 for (co= 0; co < MAX_SCR_COLS; co++) {
2750 c= ' '; // assume blank
2751 if (li > 0 && co == 0) {
2752 c = '~'; // not first line, assume Tilde
2754 // are there chars in text[] and have we gone past the end
2755 if (text < end && src < end) {
2760 if (c > 127 && !Isprint(c)) {
2763 if (c < ' ' || c == 127) {
2767 for (; (co % tabstop) != (tabstop - 1); co++) {
2775 c += '@'; // make it visible
2778 // the co++ is done here so that the column will
2779 // not be overwritten when we blank-out the rest of line
2786 //----- Refresh the changed screen lines -----------------------
2787 // Copy the source line from text[] into the buffer and note
2788 // if the current screenline is different from the new buffer.
2789 // If they differ then that line needs redrawing on the terminal.
2791 static void refresh(int full_screen)
2793 static int old_offset;
2795 Byte buf[MAX_SCR_COLS];
2796 Byte *tp, *sp; // pointer into text[] and screen[]
2797 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2798 int last_li= -2; // last line that changed- for optimizing cursor movement
2799 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2801 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2803 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2804 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2805 tp = screenbegin; // index into text[] of top line
2807 // compare text[] to screen[] and mark screen[] lines that need updating
2808 for (li = 0; li < rows - 1; li++) {
2809 int cs, ce; // column start & end
2810 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2811 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2812 // format current text line into buf
2813 format_line(buf, tp, li);
2815 // skip to the end of the current text[] line
2816 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2818 // see if there are any changes between vitual screen and buf
2819 changed = FALSE; // assume no change
2822 sp = &screen[li * columns]; // start of screen line
2824 // force re-draw of every single column from 0 - columns-1
2827 // compare newly formatted buffer with virtual screen
2828 // look forward for first difference between buf and screen
2829 for ( ; cs <= ce; cs++) {
2830 if (buf[cs + offset] != sp[cs]) {
2831 changed = TRUE; // mark for redraw
2836 // look backward for last difference between buf and screen
2837 for ( ; ce >= cs; ce--) {
2838 if (buf[ce + offset] != sp[ce]) {
2839 changed = TRUE; // mark for redraw
2843 // now, cs is index of first diff, and ce is index of last diff
2845 // if horz offset has changed, force a redraw
2846 if (offset != old_offset) {
2851 // make a sanity check of columns indexes
2853 if (ce > columns-1) ce= columns-1;
2854 if (cs > ce) { cs= 0; ce= columns-1; }
2855 // is there a change between vitual screen and buf
2857 // copy changed part of buffer to virtual screen
2858 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2860 // move cursor to column of first change
2861 if (offset != old_offset) {
2862 // opti_cur_move is still too stupid
2863 // to handle offsets correctly
2864 place_cursor(li, cs, FALSE);
2866 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2867 // if this just the next line
2868 // try to optimize cursor movement
2869 // otherwise, use standard ESC sequence
2870 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2872 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2873 place_cursor(li, cs, FALSE); // use standard ESC sequence
2874 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2877 // write line out to terminal
2887 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2889 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2893 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2894 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2897 place_cursor(crow, ccol, FALSE);
2898 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2900 if (offset != old_offset)
2901 old_offset = offset;
2904 //---------------------------------------------------------------------
2905 //----- the Ascii Chart -----------------------------------------------
2907 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2908 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2909 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2910 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2911 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2912 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2913 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2914 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2915 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2916 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2917 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2918 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2919 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2920 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2921 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2922 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2923 //---------------------------------------------------------------------
2925 //----- Execute a Vi Command -----------------------------------
2926 static void do_cmd(Byte c)
2928 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2929 int cnt, i, j, dir, yf;
2931 c1 = c; // quiet the compiler
2932 cnt = yf = dir = 0; // quiet the compiler
2933 p = q = save_dot = msg = buf; // quiet the compiler
2934 memset(buf, '\0', 9); // clear buf
2936 /* if this is a cursor key, skip these checks */
2949 if (cmd_mode == 2) {
2950 // flip-flop Insert/Replace mode
2951 if (c == VI_K_INSERT) goto dc_i;
2952 // we are 'R'eplacing the current *dot with new char
2954 // don't Replace past E-o-l
2955 cmd_mode = 1; // convert to insert
2957 if (1 <= c || Isprint(c)) {
2959 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2960 dot = char_insert(dot, c); // insert new char
2965 if (cmd_mode == 1) {
2966 // hitting "Insert" twice means "R" replace mode
2967 if (c == VI_K_INSERT) goto dc5;
2968 // insert the char c at "dot"
2969 if (1 <= c || Isprint(c)) {
2970 dot = char_insert(dot, c);
2985 #ifdef CONFIG_FEATURE_VI_CRASHME
2986 case 0x14: // dc4 ctrl-T
2987 crashme = (crashme == 0) ? 1 : 0;
2989 #endif /* CONFIG_FEATURE_VI_CRASHME */
3018 //case 'u': // u- FIXME- there is no undo
3020 default: // unrecognised command
3029 end_cmd_q(); // stop adding to q
3030 case 0x00: // nul- ignore
3032 case 2: // ctrl-B scroll up full screen
3033 case VI_K_PAGEUP: // Cursor Key Page Up
3034 dot_scroll(rows - 2, -1);
3036 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3037 case 0x03: // ctrl-C interrupt
3038 longjmp(restart, 1);
3040 case 26: // ctrl-Z suspend
3041 suspend_sig(SIGTSTP);
3043 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3044 case 4: // ctrl-D scroll down half screen
3045 dot_scroll((rows - 2) / 2, 1);
3047 case 5: // ctrl-E scroll down one line
3050 case 6: // ctrl-F scroll down full screen
3051 case VI_K_PAGEDOWN: // Cursor Key Page Down
3052 dot_scroll(rows - 2, 1);
3054 case 7: // ctrl-G show current status
3057 case 'h': // h- move left
3058 case VI_K_LEFT: // cursor key Left
3059 case 8: // ctrl-H- move left (This may be ERASE char)
3060 case 127: // DEL- move left (This may be ERASE char)
3066 case 10: // Newline ^J
3067 case 'j': // j- goto next line, same col
3068 case VI_K_DOWN: // cursor key Down
3072 dot_next(); // go to next B-o-l
3073 dot = move_to_col(dot, ccol + offset); // try stay in same col
3075 case 12: // ctrl-L force redraw whole screen
3076 case 18: // ctrl-R force redraw
3077 place_cursor(0, 0, FALSE); // put cursor in correct place
3078 clear_to_eos(); // tel terminal to erase display
3080 screen_erase(); // erase the internal screen buffer
3081 refresh(TRUE); // this will redraw the entire display
3083 case 13: // Carriage Return ^M
3084 case '+': // +- goto next line
3091 case 21: // ctrl-U scroll up half screen
3092 dot_scroll((rows - 2) / 2, -1);
3094 case 25: // ctrl-Y scroll up one line
3100 cmd_mode = 0; // stop insrting
3102 *status_buffer = '\0'; // clear status buffer
3104 case ' ': // move right
3105 case 'l': // move right
3106 case VI_K_RIGHT: // Cursor Key Right
3112 #ifdef CONFIG_FEATURE_VI_YANKMARK
3113 case '"': // "- name a register to use for Delete/Yank
3114 c1 = get_one_char();
3122 case '\'': // '- goto a specific mark
3123 c1 = get_one_char();
3129 if (text <= q && q < end) {
3131 dot_begin(); // go to B-o-l
3134 } else if (c1 == '\'') { // goto previous context
3135 dot = swap_context(dot); // swap current and previous context
3136 dot_begin(); // go to B-o-l
3142 case 'm': // m- Mark a line
3143 // this is really stupid. If there are any inserts or deletes
3144 // between text[0] and dot then this mark will not point to the
3145 // correct location! It could be off by many lines!
3146 // Well..., at least its quick and dirty.
3147 c1 = get_one_char();
3151 // remember the line
3152 mark[(int) c1] = dot;
3157 case 'P': // P- Put register before
3158 case 'p': // p- put register after
3161 psbs("Nothing in register %c", what_reg());
3164 // are we putting whole lines or strings
3165 if (strchr((char *) p, '\n') != NULL) {
3167 dot_begin(); // putting lines- Put above
3170 // are we putting after very last line?
3171 if (end_line(dot) == (end - 1)) {
3172 dot = end; // force dot to end of text[]
3174 dot_next(); // next line, then put before
3179 dot_right(); // move to right, can move to NL
3181 dot = string_insert(dot, p); // insert the string
3182 end_cmd_q(); // stop adding to q
3184 case 'U': // U- Undo; replace current line with original version
3185 if (reg[Ureg] != 0) {
3186 p = begin_line(dot);
3188 p = text_hole_delete(p, q); // delete cur line
3189 p = string_insert(p, reg[Ureg]); // insert orig line
3194 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3195 case '$': // $- goto end of line
3196 case VI_K_END: // Cursor Key End
3200 dot = end_line(dot);
3202 case '%': // %- find matching char of pair () [] {}
3203 for (q = dot; q < end && *q != '\n'; q++) {
3204 if (strchr("()[]{}", *q) != NULL) {
3205 // we found half of a pair
3206 p = find_pair(q, *q);
3218 case 'f': // f- forward to a user specified char
3219 last_forward_char = get_one_char(); // get the search char
3221 // dont seperate these two commands. 'f' depends on ';'
3223 //**** fall thru to ... 'i'
3224 case ';': // ;- look at rest of line for last forward char
3228 if (last_forward_char == 0) break;
3230 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3233 if (*q == last_forward_char)
3236 case '-': // -- goto prev line
3243 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3244 case '.': // .- repeat the last modifying command
3245 // Stuff the last_modifying_cmd back into stdin
3246 // and let it be re-executed.
3247 if (last_modifying_cmd != 0) {
3248 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3251 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3252 #ifdef CONFIG_FEATURE_VI_SEARCH
3253 case '?': // /- search for a pattern
3254 case '/': // /- search for a pattern
3257 q = get_input_line(buf); // get input line- use "status line"
3258 if (strlen((char *) q) == 1)
3259 goto dc3; // if no pat re-use old pat
3260 if (strlen((char *) q) > 1) { // new pat- save it and find
3261 // there is a new pat
3262 free(last_search_pattern);
3263 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3264 goto dc3; // now find the pattern
3266 // user changed mind and erased the "/"- do nothing
3268 case 'N': // N- backward search for last pattern
3272 dir = BACK; // assume BACKWARD search
3274 if (last_search_pattern[0] == '?') {
3278 goto dc4; // now search for pattern
3280 case 'n': // n- repeat search for last pattern
3281 // search rest of text[] starting at next char
3282 // if search fails return orignal "p" not the "p+1" address
3287 if (last_search_pattern == 0) {
3288 msg = (Byte *) "No previous regular expression";
3291 if (last_search_pattern[0] == '/') {
3292 dir = FORWARD; // assume FORWARD search
3295 if (last_search_pattern[0] == '?') {
3300 q = char_search(p, last_search_pattern + 1, dir, FULL);
3302 dot = q; // good search, update "dot"
3306 // no pattern found between "dot" and "end"- continue at top
3311 q = char_search(p, last_search_pattern + 1, dir, FULL);
3312 if (q != NULL) { // found something
3313 dot = q; // found new pattern- goto it
3314 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3316 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3319 msg = (Byte *) "Pattern not found";
3324 case '{': // {- move backward paragraph
3325 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3326 if (q != NULL) { // found blank line
3327 dot = next_line(q); // move to next blank line
3330 case '}': // }- move forward paragraph
3331 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3332 if (q != NULL) { // found blank line
3333 dot = next_line(q); // move to next blank line
3336 #endif /* CONFIG_FEATURE_VI_SEARCH */
3337 case '0': // 0- goto begining of line
3347 if (c == '0' && cmdcnt < 1) {
3348 dot_begin(); // this was a standalone zero
3350 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3353 case ':': // :- the colon mode commands
3354 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3355 #ifdef CONFIG_FEATURE_VI_COLON
3356 colon(p); // execute the command
3357 #else /* CONFIG_FEATURE_VI_COLON */
3359 p++; // move past the ':'
3360 cnt = strlen((char *) p);
3363 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3364 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3365 if (file_modified && p[1] != '!') {
3366 psbs("No write since last change (:quit! overrides)");
3370 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3371 strncasecmp((char *) p, "wq", cnt) == 0 ||
3372 strncasecmp((char *) p, "x", cnt) == 0) {
3373 cnt = file_write(cfn, text, end - 1);
3374 file_modified = FALSE;
3375 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3376 if (p[0] == 'x' || p[1] == 'q') {
3379 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3380 edit_status(); // show current file status
3381 } else if (sscanf((char *) p, "%d", &j) > 0) {
3382 dot = find_line(j); // go to line # j
3384 } else { // unrecognised cmd
3387 #endif /* CONFIG_FEATURE_VI_COLON */
3389 case '<': // <- Left shift something
3390 case '>': // >- Right shift something
3391 cnt = count_lines(text, dot); // remember what line we are on
3392 c1 = get_one_char(); // get the type of thing to delete
3393 find_range(&p, &q, c1);
3394 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3397 i = count_lines(p, q); // # of lines we are shifting
3398 for ( ; i > 0; i--, p = next_line(p)) {
3400 // shift left- remove tab or 8 spaces
3402 // shrink buffer 1 char
3403 (void) text_hole_delete(p, p);
3404 } else if (*p == ' ') {
3405 // we should be calculating columns, not just SPACE
3406 for (j = 0; *p == ' ' && j < tabstop; j++) {
3407 (void) text_hole_delete(p, p);
3410 } else if (c == '>') {
3411 // shift right -- add tab or 8 spaces
3412 (void) char_insert(p, '\t');
3415 dot = find_line(cnt); // what line were we on
3417 end_cmd_q(); // stop adding to q
3419 case 'A': // A- append at e-o-l
3420 dot_end(); // go to e-o-l
3421 //**** fall thru to ... 'a'
3422 case 'a': // a- append after current char
3427 case 'B': // B- back a blank-delimited Word
3428 case 'E': // E- end of a blank-delimited word
3429 case 'W': // W- forward a blank-delimited word
3436 if (c == 'W' || isspace(dot[dir])) {
3437 dot = skip_thing(dot, 1, dir, S_TO_WS);
3438 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3441 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3443 case 'C': // C- Change to e-o-l
3444 case 'D': // D- delete to e-o-l
3446 dot = dollar_line(dot); // move to before NL
3447 // copy text into a register and delete
3448 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3450 goto dc_i; // start inserting
3451 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3453 end_cmd_q(); // stop adding to q
3454 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3456 case 'G': // G- goto to a line number (default= E-O-F)
3457 dot = end - 1; // assume E-O-F
3459 dot = find_line(cmdcnt); // what line is #cmdcnt
3463 case 'H': // H- goto top line on screen
3465 if (cmdcnt > (rows - 1)) {
3466 cmdcnt = (rows - 1);
3473 case 'I': // I- insert before first non-blank
3476 //**** fall thru to ... 'i'
3477 case 'i': // i- insert before current char
3478 case VI_K_INSERT: // Cursor Key Insert
3480 cmd_mode = 1; // start insrting
3481 psb("-- Insert --");
3483 case 'J': // J- join current and next lines together
3487 dot_end(); // move to NL
3488 if (dot < end - 1) { // make sure not last char in text[]
3489 *dot++ = ' '; // replace NL with space
3490 while (isblnk(*dot)) { // delete leading WS
3494 end_cmd_q(); // stop adding to q
3496 case 'L': // L- goto bottom line on screen
3498 if (cmdcnt > (rows - 1)) {
3499 cmdcnt = (rows - 1);
3507 case 'M': // M- goto middle line on screen
3509 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3510 dot = next_line(dot);
3512 case 'O': // O- open a empty line above
3514 p = begin_line(dot);
3515 if (p[-1] == '\n') {
3517 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3519 dot = char_insert(dot, '\n');
3522 dot = char_insert(dot, '\n'); // i\n ESC
3527 case 'R': // R- continuous Replace char
3530 psb("-- Replace --");
3532 case 'X': // X- delete char before dot
3533 case 'x': // x- delete the current char
3534 case 's': // s- substitute the current char
3541 if (dot[dir] != '\n') {
3543 dot--; // delete prev char
3544 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3547 goto dc_i; // start insrting
3548 end_cmd_q(); // stop adding to q
3550 case 'Z': // Z- if modified, {write}; exit
3551 // ZZ means to save file (if necessary), then exit
3552 c1 = get_one_char();
3558 #ifdef CONFIG_FEATURE_VI_READONLY
3561 #endif /* CONFIG_FEATURE_VI_READONLY */
3563 cnt = file_write(cfn, text, end - 1);
3564 if (cnt == (end - 1 - text + 1)) {
3571 case '^': // ^- move to first non-blank on line
3575 case 'b': // b- back a word
3576 case 'e': // e- end of word
3583 if ((dot + dir) < text || (dot + dir) > end - 1)
3586 if (isspace(*dot)) {
3587 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3589 if (isalnum(*dot) || *dot == '_') {
3590 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3591 } else if (ispunct(*dot)) {
3592 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3595 case 'c': // c- change something
3596 case 'd': // d- delete something
3597 #ifdef CONFIG_FEATURE_VI_YANKMARK
3598 case 'y': // y- yank something
3599 case 'Y': // Y- Yank a line
3600 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3601 yf = YANKDEL; // assume either "c" or "d"
3602 #ifdef CONFIG_FEATURE_VI_YANKMARK
3603 if (c == 'y' || c == 'Y')
3605 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3608 c1 = get_one_char(); // get the type of thing to delete
3609 find_range(&p, &q, c1);
3610 if (c1 == 27) { // ESC- user changed mind and wants out
3611 c = c1 = 27; // Escape- do nothing
3612 } else if (strchr("wW", c1)) {
3614 // don't include trailing WS as part of word
3615 while (isblnk(*q)) {
3616 if (q <= text || q[-1] == '\n')
3621 dot = yank_delete(p, q, 0, yf); // delete word
3622 } else if (strchr("^0bBeEft$", c1)) {
3623 // single line copy text into a register and delete
3624 dot = yank_delete(p, q, 0, yf); // delete word
3625 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3626 // multiple line copy text into a register and delete
3627 dot = yank_delete(p, q, 1, yf); // delete lines
3629 dot = char_insert(dot, '\n');
3630 // on the last line of file don't move to prev line
3631 if (dot != (end-1)) {
3634 } else if (c == 'd') {
3639 // could not recognize object
3640 c = c1 = 27; // error-
3644 // if CHANGING, not deleting, start inserting after the delete
3646 strcpy((char *) buf, "Change");
3647 goto dc_i; // start inserting
3650 strcpy((char *) buf, "Delete");
3652 #ifdef CONFIG_FEATURE_VI_YANKMARK
3653 if (c == 'y' || c == 'Y') {
3654 strcpy((char *) buf, "Yank");
3657 q = p + strlen((char *) p);
3658 for (cnt = 0; p <= q; p++) {
3662 psb("%s %d lines (%d chars) using [%c]",
3663 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3664 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3665 end_cmd_q(); // stop adding to q
3668 case 'k': // k- goto prev line, same col
3669 case VI_K_UP: // cursor key Up
3674 dot = move_to_col(dot, ccol + offset); // try stay in same col
3676 case 'r': // r- replace the current char with user input
3677 c1 = get_one_char(); // get the replacement char
3680 file_modified = TRUE; // has the file been modified
3682 end_cmd_q(); // stop adding to q
3684 case 't': // t- move to char prior to next x
3685 last_forward_char = get_one_char();
3687 if (*dot == last_forward_char)
3689 last_forward_char= 0;
3691 case 'w': // w- forward a word
3695 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3696 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3697 } else if (ispunct(*dot)) { // we are on PUNCT
3698 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3701 dot++; // move over word
3702 if (isspace(*dot)) {
3703 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3707 c1 = get_one_char(); // get the replacement char
3710 cnt = (rows - 2) / 2; // put dot at center
3712 cnt = rows - 2; // put dot at bottom
3713 screenbegin = begin_line(dot); // start dot at top
3714 dot_scroll(cnt, -1);
3716 case '|': // |- move to column "cmdcnt"
3717 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3719 case '~': // ~- flip the case of letters a-z -> A-Z
3723 if (islower(*dot)) {
3724 *dot = toupper(*dot);
3725 file_modified = TRUE; // has the file been modified
3726 } else if (isupper(*dot)) {
3727 *dot = tolower(*dot);
3728 file_modified = TRUE; // has the file been modified
3731 end_cmd_q(); // stop adding to q
3733 //----- The Cursor and Function Keys -----------------------------
3734 case VI_K_HOME: // Cursor Key Home
3737 // The Fn keys could point to do_macro which could translate them
3738 case VI_K_FUN1: // Function Key F1
3739 case VI_K_FUN2: // Function Key F2
3740 case VI_K_FUN3: // Function Key F3
3741 case VI_K_FUN4: // Function Key F4
3742 case VI_K_FUN5: // Function Key F5
3743 case VI_K_FUN6: // Function Key F6
3744 case VI_K_FUN7: // Function Key F7
3745 case VI_K_FUN8: // Function Key F8
3746 case VI_K_FUN9: // Function Key F9
3747 case VI_K_FUN10: // Function Key F10
3748 case VI_K_FUN11: // Function Key F11
3749 case VI_K_FUN12: // Function Key F12
3754 // if text[] just became empty, add back an empty line
3756 (void) char_insert(text, '\n'); // start empty buf with dummy line
3759 // it is OK for dot to exactly equal to end, otherwise check dot validity
3761 dot = bound_dot(dot); // make sure "dot" is valid
3763 #ifdef CONFIG_FEATURE_VI_YANKMARK
3764 check_context(c); // update the current context
3765 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3768 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3769 cnt = dot - begin_line(dot);
3770 // Try to stay off of the Newline
3771 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3775 #ifdef CONFIG_FEATURE_VI_CRASHME
3776 static int totalcmds = 0;
3777 static int Mp = 85; // Movement command Probability
3778 static int Np = 90; // Non-movement command Probability
3779 static int Dp = 96; // Delete command Probability
3780 static int Ip = 97; // Insert command Probability
3781 static int Yp = 98; // Yank command Probability
3782 static int Pp = 99; // Put command Probability
3783 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3784 char chars[20] = "\t012345 abcdABCD-=.$";
3785 char *words[20] = { "this", "is", "a", "test",
3786 "broadcast", "the", "emergency", "of",
3787 "system", "quick", "brown", "fox",
3788 "jumped", "over", "lazy", "dogs",
3789 "back", "January", "Febuary", "March"
3792 "You should have received a copy of the GNU General Public License\n",
3793 "char c, cm, *cmd, *cmd1;\n",
3794 "generate a command by percentages\n",
3795 "Numbers may be typed as a prefix to some commands.\n",
3796 "Quit, discarding changes!\n",
3797 "Forced write, if permission originally not valid.\n",
3798 "In general, any ex or ed command (such as substitute or delete).\n",
3799 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3800 "Please get w/ me and I will go over it with you.\n",
3801 "The following is a list of scheduled, committed changes.\n",
3802 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3803 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3804 "Any question about transactions please contact Sterling Huxley.\n",
3805 "I will try to get back to you by Friday, December 31.\n",
3806 "This Change will be implemented on Friday.\n",
3807 "Let me know if you have problems accessing this;\n",
3808 "Sterling Huxley recently added you to the access list.\n",
3809 "Would you like to go to lunch?\n",
3810 "The last command will be automatically run.\n",
3811 "This is too much english for a computer geek.\n",
3813 char *multilines[20] = {
3814 "You should have received a copy of the GNU General Public License\n",
3815 "char c, cm, *cmd, *cmd1;\n",
3816 "generate a command by percentages\n",
3817 "Numbers may be typed as a prefix to some commands.\n",
3818 "Quit, discarding changes!\n",
3819 "Forced write, if permission originally not valid.\n",
3820 "In general, any ex or ed command (such as substitute or delete).\n",
3821 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3822 "Please get w/ me and I will go over it with you.\n",
3823 "The following is a list of scheduled, committed changes.\n",
3824 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3825 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3826 "Any question about transactions please contact Sterling Huxley.\n",
3827 "I will try to get back to you by Friday, December 31.\n",
3828 "This Change will be implemented on Friday.\n",
3829 "Let me know if you have problems accessing this;\n",
3830 "Sterling Huxley recently added you to the access list.\n",
3831 "Would you like to go to lunch?\n",
3832 "The last command will be automatically run.\n",
3833 "This is too much english for a computer geek.\n",
3836 // create a random command to execute
3837 static void crash_dummy()
3839 static int sleeptime; // how long to pause between commands
3840 char c, cm, *cmd, *cmd1;
3841 int i, cnt, thing, rbi, startrbi, percent;
3843 // "dot" movement commands
3844 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3846 // is there already a command running?
3847 if (readed_for_parse > 0)
3851 sleeptime = 0; // how long to pause between commands
3852 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3853 // generate a command by percentages
3854 percent = (int) lrand48() % 100; // get a number from 0-99
3855 if (percent < Mp) { // Movement commands
3856 // available commands
3859 } else if (percent < Np) { // non-movement commands
3860 cmd = "mz<>\'\""; // available commands
3862 } else if (percent < Dp) { // Delete commands
3863 cmd = "dx"; // available commands
3865 } else if (percent < Ip) { // Inset commands
3866 cmd = "iIaAsrJ"; // available commands
3868 } else if (percent < Yp) { // Yank commands
3869 cmd = "yY"; // available commands
3871 } else if (percent < Pp) { // Put commands
3872 cmd = "pP"; // available commands
3875 // We do not know how to handle this command, try again
3879 // randomly pick one of the available cmds from "cmd[]"
3880 i = (int) lrand48() % strlen(cmd);
3882 if (strchr(":\024", cm))
3883 goto cd0; // dont allow colon or ctrl-T commands
3884 readbuffer[rbi++] = cm; // put cmd into input buffer
3886 // now we have the command-
3887 // there are 1, 2, and multi char commands
3888 // find out which and generate the rest of command as necessary
3889 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3890 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3891 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3892 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3894 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3896 readbuffer[rbi++] = c; // add movement to input buffer
3898 if (strchr("iIaAsc", cm)) { // multi-char commands
3900 // change some thing
3901 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3903 readbuffer[rbi++] = c; // add movement to input buffer
3905 thing = (int) lrand48() % 4; // what thing to insert
3906 cnt = (int) lrand48() % 10; // how many to insert
3907 for (i = 0; i < cnt; i++) {
3908 if (thing == 0) { // insert chars
3909 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3910 } else if (thing == 1) { // insert words
3911 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3912 strcat((char *) readbuffer, " ");
3913 sleeptime = 0; // how fast to type
3914 } else if (thing == 2) { // insert lines
3915 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3916 sleeptime = 0; // how fast to type
3917 } else { // insert multi-lines
3918 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3919 sleeptime = 0; // how fast to type
3922 strcat((char *) readbuffer, "\033");
3924 readed_for_parse = strlen(readbuffer);
3928 (void) mysleep(sleeptime); // sleep 1/100 sec
3931 // test to see if there are any errors
3932 static void crash_test()
3934 static time_t oldtim;
3936 char d[2], msg[BUFSIZ];
3940 strcat((char *) msg, "end<text ");
3942 if (end > textend) {
3943 strcat((char *) msg, "end>textend ");
3946 strcat((char *) msg, "dot<text ");
3949 strcat((char *) msg, "dot>end ");
3951 if (screenbegin < text) {
3952 strcat((char *) msg, "screenbegin<text ");
3954 if (screenbegin > end - 1) {
3955 strcat((char *) msg, "screenbegin>end-1 ");
3958 if (strlen(msg) > 0) {
3960 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3961 totalcmds, last_input_char, msg, SOs, SOn);
3963 while (read(0, d, 1) > 0) {
3964 if (d[0] == '\n' || d[0] == '\r')
3969 tim = (time_t) time((time_t *) 0);
3970 if (tim >= (oldtim + 3)) {
3971 sprintf((char *) status_buffer,
3972 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3973 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3978 #endif /* CONFIG_FEATURE_VI_CRASHME */