1 /* vi: set sw=8 ts=8: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 static const char vi_Version[] =
22 "$Id: vi.c,v 1.38 2004/08/19 19:15:06 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -s -DSTANDALONE -DCONFIG_FEATURE_VI_CRASHME -o vi vi.c # include testing features
35 * $HOME/.exrc and ./.exrc
36 * add magic to search /foo.*bar
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 * More intelligence in refresh()
43 * ":r !cmd" and "!cmd" to filter text through an external command
44 * A true "undo" facility
45 * An "ex" line oriented mode- maybe using "cmdedit"
48 //---- Feature -------------- Bytes to implement
51 #define CONFIG_FEATURE_VI_COLON // 4288
52 #define CONFIG_FEATURE_VI_YANKMARK // 1408
53 #define CONFIG_FEATURE_VI_SEARCH // 1088
54 #define CONFIG_FEATURE_VI_USE_SIGNALS // 1056
55 #define CONFIG_FEATURE_VI_DOT_CMD // 576
56 #define CONFIG_FEATURE_VI_READONLY // 128
57 #define CONFIG_FEATURE_VI_SETOPTS // 576
58 #define CONFIG_FEATURE_VI_SET // 224
59 #define CONFIG_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
60 // To test editor using CRASHME:
62 // To stop testing, wait until all to text[] is deleted, or
63 // Ctrl-Z and kill -9 %1
64 // while in the editor Ctrl-T will toggle the crashme function on and off.
65 //#define CONFIG_FEATURE_VI_CRASHME // randomly pick commands to execute
66 #endif /* STANDALONE */
73 #include <sys/ioctl.h>
75 #include <sys/types.h>
88 #endif /* STANDALONE */
90 #ifdef CONFIG_LOCALE_SUPPORT
91 #define Isprint(c) isprint((c))
93 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
98 #define FALSE ((int)0)
100 #define MAX_SCR_COLS BUFSIZ
102 // Misc. non-Ascii keys that report an escape sequence
103 #define VI_K_UP 128 // cursor key Up
104 #define VI_K_DOWN 129 // cursor key Down
105 #define VI_K_RIGHT 130 // Cursor Key Right
106 #define VI_K_LEFT 131 // cursor key Left
107 #define VI_K_HOME 132 // Cursor Key Home
108 #define VI_K_END 133 // Cursor Key End
109 #define VI_K_INSERT 134 // Cursor Key Insert
110 #define VI_K_PAGEUP 135 // Cursor Key Page Up
111 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
112 #define VI_K_FUN1 137 // Function Key F1
113 #define VI_K_FUN2 138 // Function Key F2
114 #define VI_K_FUN3 139 // Function Key F3
115 #define VI_K_FUN4 140 // Function Key F4
116 #define VI_K_FUN5 141 // Function Key F5
117 #define VI_K_FUN6 142 // Function Key F6
118 #define VI_K_FUN7 143 // Function Key F7
119 #define VI_K_FUN8 144 // Function Key F8
120 #define VI_K_FUN9 145 // Function Key F9
121 #define VI_K_FUN10 146 // Function Key F10
122 #define VI_K_FUN11 147 // Function Key F11
123 #define VI_K_FUN12 148 // Function Key F12
125 /* vt102 typical ESC sequence */
126 /* terminal standout start/normal ESC sequence */
127 static const char SOs[] = "\033[7m";
128 static const char SOn[] = "\033[0m";
129 /* terminal bell sequence */
130 static const char bell[] = "\007";
131 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
132 static const char Ceol[] = "\033[0K";
133 static const char Ceos [] = "\033[0J";
134 /* Cursor motion arbitrary destination ESC sequence */
135 static const char CMrc[] = "\033[%d;%dH";
136 /* Cursor motion up and down ESC sequence */
137 static const char CMup[] = "\033[A";
138 static const char CMdown[] = "\n";
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 2=replace
170 static int file_modified; // buffer contents changed
171 static int last_file_modified = -1;
172 static int fn_start; // index of first cmd line file name
173 static int save_argc; // how many file names on cmd line
174 static int cmdcnt; // repetition count
175 static fd_set rfds; // use select() for small sleeps
176 static struct timeval tv; // use select() for small sleeps
177 static int rows, columns; // the terminal screen is this size
178 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
179 static Byte *status_buffer; // mesages to the user
180 #define STATUS_BUFFER_LEN 200
181 static int have_status_msg; // is default edit status needed?
182 static int last_status_cksum; // hash of current status line
183 static Byte *cfn; // previous, current, and next file name
184 static Byte *text, *end, *textend; // pointers to the user data in memory
185 static Byte *screen; // pointer to the virtual screen buffer
186 static int screensize; // and its size
187 static Byte *screenbegin; // index into text[], of top line on the screen
188 static Byte *dot; // where all the action takes place
190 static struct termios term_orig, term_vi; // remember what the cooked mode was
191 static Byte erase_char; // the users erase character
192 static Byte last_input_char; // last char read from user
193 static Byte last_forward_char; // last char searched for with 'f'
195 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
196 static int last_row; // where the cursor was last moved to
197 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
198 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
199 static jmp_buf restart; // catch_sig()
200 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
201 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
204 #ifdef CONFIG_FEATURE_VI_DOT_CMD
205 static int adding2q; // are we currently adding user input to q
206 static Byte *last_modifying_cmd; // last modifying cmd for "."
207 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
208 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
209 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
210 static Byte *modifying_cmds; // cmds that modify text[]
211 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
212 #ifdef CONFIG_FEATURE_VI_READONLY
213 static int vi_readonly, readonly;
214 #endif /* CONFIG_FEATURE_VI_READONLY */
215 #ifdef CONFIG_FEATURE_VI_YANKMARK
216 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
217 static int YDreg, Ureg; // default delete register and orig line for "U"
218 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
219 static Byte *context_start, *context_end;
220 #endif /* CONFIG_FEATURE_VI_YANKMARK */
221 #ifdef CONFIG_FEATURE_VI_SEARCH
222 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
223 #endif /* CONFIG_FEATURE_VI_SEARCH */
226 static void edit_file(Byte *); // edit one file
227 static void do_cmd(Byte); // execute a command
228 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
229 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
230 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
231 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
232 static Byte *next_line(Byte *); // return pointer to next line B-o-l
233 static Byte *end_screen(void); // get pointer to last char on screen
234 static int count_lines(Byte *, Byte *); // count line from start to stop
235 static Byte *find_line(int); // find begining of line #li
236 static Byte *move_to_col(Byte *, int); // move "p" to column l
237 static int isblnk(Byte); // is the char a blank or tab
238 static void dot_left(void); // move dot left- dont leave line
239 static void dot_right(void); // move dot right- dont leave line
240 static void dot_begin(void); // move dot to B-o-l
241 static void dot_end(void); // move dot to E-o-l
242 static void dot_next(void); // move dot to next line B-o-l
243 static void dot_prev(void); // move dot to prev line B-o-l
244 static void dot_scroll(int, int); // move the screen up or down
245 static void dot_skip_over_ws(void); // move dot pat WS
246 static void dot_delete(void); // delete the char at 'dot'
247 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
248 static Byte *new_screen(int, int); // malloc virtual screen memory
249 static Byte *new_text(int); // malloc memory for text[] buffer
250 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
251 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
252 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
253 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
254 static Byte *skip_thing(Byte *, int, int, int); // skip some object
255 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
256 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
257 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
258 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
259 static void show_help(void); // display some help info
260 static void rawmode(void); // set "raw" mode on tty
261 static void cookmode(void); // return to "cooked" mode on tty
262 static int mysleep(int); // sleep for 'h' 1/100 seconds
263 static Byte readit(void); // read (maybe cursor) key from stdin
264 static Byte get_one_char(void); // read 1 char from stdin
265 static int file_size(const Byte *); // what is the byte size of "fn"
266 static int file_insert(Byte *, Byte *, int);
267 static int file_write(Byte *, Byte *, Byte *);
268 static void place_cursor(int, int, int);
269 static void screen_erase(void);
270 static void clear_to_eol(void);
271 static void clear_to_eos(void);
272 static void standout_start(void); // send "start reverse video" sequence
273 static void standout_end(void); // send "end reverse video" sequence
274 static void flash(int); // flash the terminal screen
275 static void show_status_line(void); // put a message on the bottom line
276 static void psb(const char *, ...); // Print Status Buf
277 static void psbs(const char *, ...); // Print Status Buf in standout mode
278 static void ni(Byte *); // display messages
279 static int format_edit_status(void); // format file status on status line
280 static void redraw(int); // force a full screen refresh
281 static void format_line(Byte*, Byte*, int);
282 static void refresh(int); // update the terminal from screen[]
284 static void Indicate_Error(void); // use flash or beep to indicate error
285 #define indicate_error(c) Indicate_Error()
286 static void Hit_Return(void);
288 #ifdef CONFIG_FEATURE_VI_SEARCH
289 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
290 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
291 #endif /* CONFIG_FEATURE_VI_SEARCH */
292 #ifdef CONFIG_FEATURE_VI_COLON
293 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
294 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
295 static void colon(Byte *); // execute the "colon" mode cmds
296 #endif /* CONFIG_FEATURE_VI_COLON */
297 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
298 static void winch_sig(int); // catch window size changes
299 static void suspend_sig(int); // catch ctrl-Z
300 static void catch_sig(int); // catch ctrl-C and alarm time-outs
301 static void core_sig(int); // catch a core dump signal
302 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
303 #ifdef CONFIG_FEATURE_VI_DOT_CMD
304 static void start_new_cmd_q(Byte); // new queue for command
305 static void end_cmd_q(void); // stop saving input chars
306 #else /* CONFIG_FEATURE_VI_DOT_CMD */
308 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
309 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
310 static void window_size_get(int); // find out what size the window is
311 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
312 #ifdef CONFIG_FEATURE_VI_SETOPTS
313 static void showmatching(Byte *); // show the matching pair () [] {}
314 #endif /* CONFIG_FEATURE_VI_SETOPTS */
315 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
316 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
317 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
318 #ifdef CONFIG_FEATURE_VI_YANKMARK
319 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
320 static Byte what_reg(void); // what is letter of current YDreg
321 static void check_context(Byte); // remember context for '' command
322 #endif /* CONFIG_FEATURE_VI_YANKMARK */
323 #ifdef CONFIG_FEATURE_VI_CRASHME
324 static void crash_dummy();
325 static void crash_test();
326 static int crashme = 0;
327 #endif /* CONFIG_FEATURE_VI_CRASHME */
330 static void write1(const char *out)
335 extern int vi_main(int argc, char **argv)
338 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
340 #ifdef CONFIG_FEATURE_VI_YANKMARK
342 #endif /* CONFIG_FEATURE_VI_YANKMARK */
343 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
346 #ifdef CONFIG_FEATURE_VI_CRASHME
347 (void) srand((long) my_pid);
348 #endif /* CONFIG_FEATURE_VI_CRASHME */
350 status_buffer = STATUS_BUFFER;
351 last_status_cksum = 0;
353 #ifdef CONFIG_FEATURE_VI_READONLY
354 vi_readonly = readonly = FALSE;
355 if (strncmp(argv[0], "view", 4) == 0) {
359 #endif /* CONFIG_FEATURE_VI_READONLY */
360 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
361 #ifdef CONFIG_FEATURE_VI_YANKMARK
362 for (i = 0; i < 28; i++) {
364 } // init the yank regs
365 #endif /* CONFIG_FEATURE_VI_YANKMARK */
366 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
367 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
368 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
370 // 1- process $HOME/.exrc file
371 // 2- process EXINIT variable from environment
372 // 3- process command line args
373 while ((c = getopt(argc, argv, "hCR")) != -1) {
375 #ifdef CONFIG_FEATURE_VI_CRASHME
379 #endif /* CONFIG_FEATURE_VI_CRASHME */
380 #ifdef CONFIG_FEATURE_VI_READONLY
381 case 'R': // Read-only flag
385 #endif /* CONFIG_FEATURE_VI_READONLY */
386 //case 'r': // recover flag- ignore- we don't use tmp file
387 //case 'x': // encryption flag- ignore
388 //case 'c': // execute command first
389 //case 'h': // help -- just use default
396 // The argv array can be used by the ":next" and ":rewind" commands
398 fn_start = optind; // remember first file name for :next and :rew
401 //----- This is the main file handling loop --------------
402 if (optind >= argc) {
403 editing = 1; // 0= exit, 1= one file, 2= multiple files
406 for (; optind < argc; optind++) {
407 editing = 1; // 0=exit, 1=one file, 2+ =many files
409 cfn = (Byte *) bb_xstrdup(argv[optind]);
413 //-----------------------------------------------------------
418 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
419 //----- See what the window size currently is --------------------
420 static inline void window_size_get(int fd)
422 get_terminal_width_height(fd, &columns, &rows);
424 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
426 static void edit_file(Byte * fn)
431 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
433 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
434 #ifdef CONFIG_FEATURE_VI_YANKMARK
435 static Byte *cur_line;
436 #endif /* CONFIG_FEATURE_VI_YANKMARK */
442 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
444 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
445 new_screen(rows, columns); // get memory for virtual screen
447 cnt = file_size(fn); // file size
448 size = 2 * cnt; // 200% of file size
449 new_text(size); // get a text[] buffer
450 screenbegin = dot = end = text;
452 ch= file_insert(fn, text, cnt);
455 (void) char_insert(text, '\n'); // start empty buf with dummy line
458 last_file_modified = -1;
459 #ifdef CONFIG_FEATURE_VI_YANKMARK
460 YDreg = 26; // default Yank/Delete reg
461 Ureg = 27; // hold orig line for "U" cmd
462 for (cnt = 0; cnt < 28; cnt++) {
465 mark[26] = mark[27] = text; // init "previous context"
466 #endif /* CONFIG_FEATURE_VI_YANKMARK */
468 last_forward_char = last_input_char = '\0';
472 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
475 signal(SIGWINCH, winch_sig);
476 signal(SIGTSTP, suspend_sig);
477 sig = setjmp(restart);
479 const char *msg = "";
482 msg = "(window resize)";
492 msg = "(I tried to touch invalid memory)";
496 psbs("-- caught signal %d %s--", sig, msg);
497 screenbegin = dot = text;
499 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
502 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
505 offset = 0; // no horizontal offset
507 #ifdef CONFIG_FEATURE_VI_DOT_CMD
508 free(last_modifying_cmd);
510 ioq = ioq_start = last_modifying_cmd = 0;
512 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
513 redraw(FALSE); // dont force every col re-draw
516 //------This is the main Vi cmd handling loop -----------------------
517 while (editing > 0) {
518 #ifdef CONFIG_FEATURE_VI_CRASHME
520 if ((end - text) > 1) {
521 crash_dummy(); // generate a random command
525 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
529 #endif /* CONFIG_FEATURE_VI_CRASHME */
530 last_input_char = c = get_one_char(); // get a cmd from user
531 #ifdef CONFIG_FEATURE_VI_YANKMARK
532 // save a copy of the current line- for the 'U" command
533 if (begin_line(dot) != cur_line) {
534 cur_line = begin_line(dot);
535 text_yank(begin_line(dot), end_line(dot), Ureg);
537 #endif /* CONFIG_FEATURE_VI_YANKMARK */
538 #ifdef CONFIG_FEATURE_VI_DOT_CMD
539 // These are commands that change text[].
540 // Remember the input for the "." command
541 if (!adding2q && ioq_start == 0
542 && strchr((char *) modifying_cmds, c) != NULL) {
545 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
546 do_cmd(c); // execute the user command
548 // poll to see if there is input already waiting. if we are
549 // not able to display output fast enough to keep up, skip
550 // the display update until we catch up with input.
551 if (mysleep(0) == 0) {
552 // no input pending- so update output
556 #ifdef CONFIG_FEATURE_VI_CRASHME
558 crash_test(); // test editor variables
559 #endif /* CONFIG_FEATURE_VI_CRASHME */
561 //-------------------------------------------------------------------
563 place_cursor(rows, 0, FALSE); // go to bottom of screen
564 clear_to_eol(); // Erase to end of line
568 //----- The Colon commands -------------------------------------
569 #ifdef CONFIG_FEATURE_VI_COLON
570 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
575 #ifdef CONFIG_FEATURE_VI_YANKMARK
577 #endif /* CONFIG_FEATURE_VI_YANKMARK */
578 #ifdef CONFIG_FEATURE_VI_SEARCH
579 Byte *pat, buf[BUFSIZ];
580 #endif /* CONFIG_FEATURE_VI_SEARCH */
582 *addr = -1; // assume no addr
583 if (*p == '.') { // the current line
586 *addr = count_lines(text, q);
587 #ifdef CONFIG_FEATURE_VI_YANKMARK
588 } else if (*p == '\'') { // is this a mark addr
592 if (c >= 'a' && c <= 'z') {
596 if (q != NULL) { // is mark valid
597 *addr = count_lines(text, q); // count lines
600 #endif /* CONFIG_FEATURE_VI_YANKMARK */
601 #ifdef CONFIG_FEATURE_VI_SEARCH
602 } else if (*p == '/') { // a search pattern
610 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
613 q = char_search(dot, pat, FORWARD, FULL);
615 *addr = count_lines(text, q);
618 #endif /* CONFIG_FEATURE_VI_SEARCH */
619 } else if (*p == '$') { // the last line in file
621 q = begin_line(end - 1);
622 *addr = count_lines(text, q);
623 } else if (isdigit(*p)) { // specific line number
624 sscanf((char *) p, "%d%n", addr, &st);
626 } else { // I don't reconise this
627 // unrecognised address- assume -1
633 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
635 //----- get the address' i.e., 1,3 'a,'b -----
636 // get FIRST addr, if present
638 p++; // skip over leading spaces
639 if (*p == '%') { // alias for 1,$
642 *e = count_lines(text, end-1);
645 p = get_one_address(p, b);
648 if (*p == ',') { // is there a address separator
652 // get SECOND addr, if present
653 p = get_one_address(p, e);
657 p++; // skip over trailing spaces
661 #ifdef CONFIG_FEATURE_VI_SETOPTS
662 static void setops(const Byte *args, const char *opname, int flg_no,
663 const char *short_opname, int opt)
665 const char *a = (char *) args + flg_no;
666 int l = strlen(opname) - 1; /* opname have + ' ' */
668 if (strncasecmp(a, opname, l) == 0 ||
669 strncasecmp(a, short_opname, 2) == 0) {
678 static void colon(Byte * buf)
680 Byte c, *orig_buf, *buf1, *q, *r;
681 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
682 int i, l, li, ch, st, b, e;
683 int useforce = FALSE, forced = FALSE;
686 // :3154 // if (-e line 3154) goto it else stay put
687 // :4,33w! foo // write a portion of buffer to file "foo"
688 // :w // write all of buffer to current file
690 // :q! // quit- dont care about modified file
691 // :'a,'z!sort -u // filter block through sort
692 // :'f // goto mark "f"
693 // :'fl // list literal the mark "f" line
694 // :.r bar // read file "bar" into buffer before dot
695 // :/123/,/abc/d // delete lines from "123" line to "abc" line
696 // :/xyz/ // goto the "xyz" line
697 // :s/find/replace/ // substitute pattern "find" with "replace"
698 // :!<cmd> // run <cmd> then return
701 if (strlen((char *) buf) <= 0)
704 buf++; // move past the ':'
706 li = st = ch = i = 0;
708 q = text; // assume 1,$ for the range
710 li = count_lines(text, end - 1);
711 fn = cfn; // default to current file
712 memset(cmd, '\0', BUFSIZ); // clear cmd[]
713 memset(args, '\0', BUFSIZ); // clear args[]
715 // look for optional address(es) :. :1 :1,9 :'q,'a :%
716 buf = get_address(buf, &b, &e);
718 // remember orig command line
721 // get the COMMAND into cmd[]
723 while (*buf != '\0') {
731 strcpy((char *) args, (char *) buf);
732 buf1 = last_char_is((char *)cmd, '!');
735 *buf1 = '\0'; // get rid of !
738 // if there is only one addr, then the addr
739 // is the line number of the single line the
740 // user wants. So, reset the end
741 // pointer to point at end of the "b" line
742 q = find_line(b); // what line is #b
747 // we were given two addrs. change the
748 // end pointer to the addr given by user.
749 r = find_line(e); // what line is #e
753 // ------------ now look for the command ------------
754 i = strlen((char *) cmd);
755 if (i == 0) { // :123CR goto line #123
757 dot = find_line(b); // what line is #b
760 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
761 // :!ls run the <cmd>
762 (void) alarm(0); // wait for input- no alarms
763 place_cursor(rows - 1, 0, FALSE); // go to Status line
764 clear_to_eol(); // clear the line
766 system(orig_buf+1); // run the cmd
768 Hit_Return(); // let user see results
769 (void) alarm(3); // done waiting for input
770 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
771 if (b < 0) { // no addr given- use defaults
772 b = e = count_lines(text, dot);
775 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
776 if (b < 0) { // no addr given- use defaults
777 q = begin_line(dot); // assume .,. for the range
780 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
782 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
785 // don't edit, if the current file has been modified
786 if (file_modified && ! useforce) {
787 psbs("No write since last change (:edit! overrides)");
790 if (strlen(args) > 0) {
791 // the user supplied a file name
793 } else if (cfn != 0 && strlen(cfn) > 0) {
794 // no user supplied name- use the current filename
798 // no user file name, no current name- punt
799 psbs("No current filename");
803 // see if file exists- if not, its just a new file request
804 if ((sr=stat((char*)fn, &st_buf)) < 0) {
805 // This is just a request for a new file creation.
806 // The file_insert below will fail but we get
807 // an empty buffer with a file name. Then the "write"
808 // command can do the create.
810 if ((st_buf.st_mode & (S_IFREG)) == 0) {
811 // This is not a regular file
812 psbs("\"%s\" is not a regular file", fn);
815 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
816 // dont have any read permissions
817 psbs("\"%s\" is not readable", fn);
822 // There is a read-able regular file
823 // make this the current file
824 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
825 free(cfn); // free the old name
826 cfn = q; // remember new cfn
829 // delete all the contents of text[]
830 new_text(2 * file_size(fn));
831 screenbegin = dot = end = text;
834 ch = file_insert(fn, text, file_size(fn));
837 // start empty buf with dummy line
838 (void) char_insert(text, '\n');
842 last_file_modified = -1;
843 #ifdef CONFIG_FEATURE_VI_YANKMARK
844 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
845 free(reg[Ureg]); // free orig line reg- for 'U'
848 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
849 free(reg[YDreg]); // free default yank/delete register
852 for (li = 0; li < 28; li++) {
855 #endif /* CONFIG_FEATURE_VI_YANKMARK */
856 // how many lines in text[]?
857 li = count_lines(text, end - 1);
859 #ifdef CONFIG_FEATURE_VI_READONLY
861 #endif /* CONFIG_FEATURE_VI_READONLY */
863 (sr < 0 ? " [New file]" : ""),
864 #ifdef CONFIG_FEATURE_VI_READONLY
865 ((vi_readonly || readonly) ? " [Read only]" : ""),
866 #endif /* CONFIG_FEATURE_VI_READONLY */
868 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
869 if (b != -1 || e != -1) {
870 ni((Byte *) "No address allowed on this command");
873 if (strlen((char *) args) > 0) {
874 // user wants a new filename
876 cfn = (Byte *) bb_xstrdup((char *) args);
878 // user wants file status info
879 last_status_cksum = 0; // force status update
881 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
882 // print out values of all features
883 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
884 clear_to_eol(); // clear the line
889 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
890 if (b < 0) { // no addr given- use defaults
891 q = begin_line(dot); // assume .,. for the range
894 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
895 clear_to_eol(); // clear the line
897 for (; q <= r; q++) {
901 c_is_no_print = c > 127 && !Isprint(c);
908 } else if (c < ' ' || c == 127) {
919 #ifdef CONFIG_FEATURE_VI_SET
921 #endif /* CONFIG_FEATURE_VI_SET */
923 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
924 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
926 // force end of argv list
933 // don't exit if the file been modified
935 psbs("No write since last change (:%s! overrides)",
936 (*cmd == 'q' ? "quit" : "next"));
939 // are there other file to edit
940 if (*cmd == 'q' && optind < save_argc - 1) {
941 psbs("%d more file to edit", (save_argc - optind - 1));
944 if (*cmd == 'n' && optind >= save_argc - 1) {
945 psbs("No more files to edit");
949 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
951 if (strlen((char *) fn) <= 0) {
952 psbs("No filename given");
955 if (b < 0) { // no addr given- use defaults
956 q = begin_line(dot); // assume "dot"
958 // read after current line- unless user said ":0r foo"
961 #ifdef CONFIG_FEATURE_VI_READONLY
962 l= readonly; // remember current files' status
964 ch = file_insert(fn, q, file_size(fn));
965 #ifdef CONFIG_FEATURE_VI_READONLY
969 goto vc1; // nothing was inserted
970 // how many lines in text[]?
971 li = count_lines(q, q + ch - 1);
973 #ifdef CONFIG_FEATURE_VI_READONLY
975 #endif /* CONFIG_FEATURE_VI_READONLY */
977 #ifdef CONFIG_FEATURE_VI_READONLY
978 ((vi_readonly || readonly) ? " [Read only]" : ""),
979 #endif /* CONFIG_FEATURE_VI_READONLY */
982 // if the insert is before "dot" then we need to update
987 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
988 if (file_modified && ! useforce) {
989 psbs("No write since last change (:rewind! overrides)");
991 // reset the filenames to edit
992 optind = fn_start - 1;
995 #ifdef CONFIG_FEATURE_VI_SET
996 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
997 i = 0; // offset into args
998 if (strlen((char *) args) == 0) {
999 // print out values of all options
1000 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1001 clear_to_eol(); // clear the line
1002 printf("----------------------------------------\r\n");
1003 #ifdef CONFIG_FEATURE_VI_SETOPTS
1006 printf("autoindent ");
1012 printf("ignorecase ");
1015 printf("showmatch ");
1016 printf("tabstop=%d ", tabstop);
1017 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1021 if (strncasecmp((char *) args, "no", 2) == 0)
1022 i = 2; // ":set noautoindent"
1023 #ifdef CONFIG_FEATURE_VI_SETOPTS
1024 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1025 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1026 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1027 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1028 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1029 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1030 if (ch > 0 && ch < columns - 1)
1033 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1034 #endif /* CONFIG_FEATURE_VI_SET */
1035 #ifdef CONFIG_FEATURE_VI_SEARCH
1036 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1040 // F points to the "find" pattern
1041 // R points to the "replace" pattern
1042 // replace the cmd line delimiters "/" with NULLs
1043 gflag = 0; // global replace flag
1044 c = orig_buf[1]; // what is the delimiter
1045 F = orig_buf + 2; // start of "find"
1046 R = (Byte *) strchr((char *) F, c); // middle delimiter
1047 if (!R) goto colon_s_fail;
1048 *R++ = '\0'; // terminate "find"
1049 buf1 = (Byte *) strchr((char *) R, c);
1050 if (!buf1) goto colon_s_fail;
1051 *buf1++ = '\0'; // terminate "replace"
1052 if (*buf1 == 'g') { // :s/foo/bar/g
1054 gflag++; // turn on gflag
1057 if (b < 0) { // maybe :s/foo/bar/
1058 q = begin_line(dot); // start with cur line
1059 b = count_lines(text, q); // cur line number
1062 e = b; // maybe :.s/foo/bar/
1063 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1064 ls = q; // orig line start
1066 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1068 // we found the "find" pattern- delete it
1069 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1070 // inset the "replace" patern
1071 (void) string_insert(buf1, R); // insert the string
1072 // check for "global" :s/foo/bar/g
1074 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1075 q = buf1 + strlen((char *) R);
1076 goto vc4; // don't let q move past cur line
1082 #endif /* CONFIG_FEATURE_VI_SEARCH */
1083 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1084 psb("%s", vi_Version);
1085 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1086 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1087 (strncasecmp((char *) cmd, "x", i) == 0)) {
1088 // is there a file name to write to?
1089 if (strlen((char *) args) > 0) {
1092 #ifdef CONFIG_FEATURE_VI_READONLY
1093 if ((vi_readonly || readonly) && ! useforce) {
1094 psbs("\"%s\" File is read only", fn);
1097 #endif /* CONFIG_FEATURE_VI_READONLY */
1098 // how many lines in text[]?
1099 li = count_lines(q, r);
1101 // see if file exists- if not, its just a new file request
1103 // if "fn" is not write-able, chmod u+w
1104 // sprintf(syscmd, "chmod u+w %s", fn);
1108 l = file_write(fn, q, r);
1109 if (useforce && forced) {
1111 // sprintf(syscmd, "chmod u-w %s", fn);
1117 psbs("Write error: %s", strerror(errno));
1119 psb("\"%s\" %dL, %dC", fn, li, l);
1120 if (q == text && r == end - 1 && l == ch) {
1122 last_file_modified = -1;
1124 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1128 #ifdef CONFIG_FEATURE_VI_READONLY
1130 #endif /* CONFIG_FEATURE_VI_READONLY */
1131 #ifdef CONFIG_FEATURE_VI_YANKMARK
1132 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1133 if (b < 0) { // no addr given- use defaults
1134 q = begin_line(dot); // assume .,. for the range
1137 text_yank(q, r, YDreg);
1138 li = count_lines(q, r);
1139 psb("Yank %d lines (%d chars) into [%c]",
1140 li, strlen((char *) reg[YDreg]), what_reg());
1141 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1147 dot = bound_dot(dot); // make sure "dot" is valid
1149 #ifdef CONFIG_FEATURE_VI_SEARCH
1151 psb(":s expression missing delimiters");
1155 #endif /* CONFIG_FEATURE_VI_COLON */
1157 static void Hit_Return(void)
1161 standout_start(); // start reverse video
1162 write1("[Hit return to continue]");
1163 standout_end(); // end reverse video
1164 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1166 redraw(TRUE); // force redraw all
1169 //----- Synchronize the cursor to Dot --------------------------
1170 static void sync_cursor(Byte * d, int *row, int *col)
1172 Byte *beg_cur, *end_cur; // begin and end of "d" line
1173 Byte *beg_scr, *end_scr; // begin and end of screen
1177 beg_cur = begin_line(d); // first char of cur line
1178 end_cur = end_line(d); // last char of cur line
1180 beg_scr = end_scr = screenbegin; // first char of screen
1181 end_scr = end_screen(); // last char of screen
1183 if (beg_cur < screenbegin) {
1184 // "d" is before top line on screen
1185 // how many lines do we have to move
1186 cnt = count_lines(beg_cur, screenbegin);
1188 screenbegin = beg_cur;
1189 if (cnt > (rows - 1) / 2) {
1190 // we moved too many lines. put "dot" in middle of screen
1191 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1192 screenbegin = prev_line(screenbegin);
1195 } else if (beg_cur > end_scr) {
1196 // "d" is after bottom line on screen
1197 // how many lines do we have to move
1198 cnt = count_lines(end_scr, beg_cur);
1199 if (cnt > (rows - 1) / 2)
1200 goto sc1; // too many lines
1201 for (ro = 0; ro < cnt - 1; ro++) {
1202 // move screen begin the same amount
1203 screenbegin = next_line(screenbegin);
1204 // now, move the end of screen
1205 end_scr = next_line(end_scr);
1206 end_scr = end_line(end_scr);
1209 // "d" is on screen- find out which row
1211 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1217 // find out what col "d" is on
1219 do { // drive "co" to correct column
1220 if (*tp == '\n' || *tp == '\0')
1224 co += ((tabstop - 1) - (co % tabstop));
1225 } else if (*tp < ' ' || *tp == 127) {
1226 co++; // display as ^X, use 2 columns
1228 } while (tp++ < d && ++co);
1230 // "co" is the column where "dot" is.
1231 // The screen has "columns" columns.
1232 // The currently displayed columns are 0+offset -- columns+ofset
1233 // |-------------------------------------------------------------|
1235 // offset | |------- columns ----------------|
1237 // If "co" is already in this range then we do not have to adjust offset
1238 // but, we do have to subtract the "offset" bias from "co".
1239 // If "co" is outside this range then we have to change "offset".
1240 // If the first char of a line is a tab the cursor will try to stay
1241 // in column 7, but we have to set offset to 0.
1243 if (co < 0 + offset) {
1246 if (co >= columns + offset) {
1247 offset = co - columns + 1;
1249 // if the first char of the line is a tab, and "dot" is sitting on it
1250 // force offset to 0.
1251 if (d == beg_cur && *d == '\t') {
1260 //----- Text Movement Routines ---------------------------------
1261 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1263 while (p > text && p[-1] != '\n')
1264 p--; // go to cur line B-o-l
1268 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1270 while (p < end - 1 && *p != '\n')
1271 p++; // go to cur line E-o-l
1275 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1277 while (p < end - 1 && *p != '\n')
1278 p++; // go to cur line E-o-l
1279 // Try to stay off of the Newline
1280 if (*p == '\n' && (p - begin_line(p)) > 0)
1285 static Byte *prev_line(Byte * p) // return pointer first char prev line
1287 p = begin_line(p); // goto begining of cur line
1288 if (p[-1] == '\n' && p > text)
1289 p--; // step to prev line
1290 p = begin_line(p); // goto begining of prev line
1294 static Byte *next_line(Byte * p) // return pointer first char next line
1297 if (*p == '\n' && p < end - 1)
1298 p++; // step to next line
1302 //----- Text Information Routines ------------------------------
1303 static Byte *end_screen(void)
1308 // find new bottom line
1310 for (cnt = 0; cnt < rows - 2; cnt++)
1316 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1321 if (stop < start) { // start and stop are backwards- reverse them
1327 stop = end_line(stop); // get to end of this line
1328 for (q = start; q <= stop && q <= end - 1; q++) {
1335 static Byte *find_line(int li) // find begining of line #li
1339 for (q = text; li > 1; li--) {
1345 //----- Dot Movement Routines ----------------------------------
1346 static void dot_left(void)
1348 if (dot > text && dot[-1] != '\n')
1352 static void dot_right(void)
1354 if (dot < end - 1 && *dot != '\n')
1358 static void dot_begin(void)
1360 dot = begin_line(dot); // return pointer to first char cur line
1363 static void dot_end(void)
1365 dot = end_line(dot); // return pointer to last char cur line
1368 static Byte *move_to_col(Byte * p, int l)
1375 if (*p == '\n' || *p == '\0')
1379 co += ((tabstop - 1) - (co % tabstop));
1380 } else if (*p < ' ' || *p == 127) {
1381 co++; // display as ^X, use 2 columns
1383 } while (++co <= l && p++ < end);
1387 static void dot_next(void)
1389 dot = next_line(dot);
1392 static void dot_prev(void)
1394 dot = prev_line(dot);
1397 static void dot_scroll(int cnt, int dir)
1401 for (; cnt > 0; cnt--) {
1404 // ctrl-Y scroll up one line
1405 screenbegin = prev_line(screenbegin);
1408 // ctrl-E scroll down one line
1409 screenbegin = next_line(screenbegin);
1412 // make sure "dot" stays on the screen so we dont scroll off
1413 if (dot < screenbegin)
1415 q = end_screen(); // find new bottom line
1417 dot = begin_line(q); // is dot is below bottom line?
1421 static void dot_skip_over_ws(void)
1424 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1428 static void dot_delete(void) // delete the char at 'dot'
1430 (void) text_hole_delete(dot, dot);
1433 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1435 if (p >= end && end > text) {
1437 indicate_error('1');
1441 indicate_error('2');
1446 //----- Helper Utility Routines --------------------------------
1448 //----------------------------------------------------------------
1449 //----- Char Routines --------------------------------------------
1450 /* Chars that are part of a word-
1451 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1452 * Chars that are Not part of a word (stoppers)
1453 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1454 * Chars that are WhiteSpace
1455 * TAB NEWLINE VT FF RETURN SPACE
1456 * DO NOT COUNT NEWLINE AS WHITESPACE
1459 static Byte *new_screen(int ro, int co)
1464 screensize = ro * co + 8;
1465 screen = (Byte *) xmalloc(screensize);
1466 // initialize the new screen. assume this will be a empty file.
1468 // non-existent text[] lines start with a tilde (~).
1469 for (li = 1; li < ro - 1; li++) {
1470 screen[(li * co) + 0] = '~';
1475 static Byte *new_text(int size)
1478 size = 10240; // have a minimum size for new files
1480 text = (Byte *) xmalloc(size + 8);
1481 memset(text, '\0', size); // clear new text[]
1482 //text += 4; // leave some room for "oops"
1483 textend = text + size - 1;
1484 //textend -= 4; // leave some root for "oops"
1488 #ifdef CONFIG_FEATURE_VI_SEARCH
1489 static int mycmp(Byte * s1, Byte * s2, int len)
1493 i = strncmp((char *) s1, (char *) s2, len);
1494 #ifdef CONFIG_FEATURE_VI_SETOPTS
1496 i = strncasecmp((char *) s1, (char *) s2, len);
1498 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1502 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1504 #ifndef REGEX_SEARCH
1508 len = strlen((char *) pat);
1509 if (dir == FORWARD) {
1510 stop = end - 1; // assume range is p - end-1
1511 if (range == LIMITED)
1512 stop = next_line(p); // range is to next line
1513 for (start = p; start < stop; start++) {
1514 if (mycmp(start, pat, len) == 0) {
1518 } else if (dir == BACK) {
1519 stop = text; // assume range is text - p
1520 if (range == LIMITED)
1521 stop = prev_line(p); // range is to prev line
1522 for (start = p - len; start >= stop; start--) {
1523 if (mycmp(start, pat, len) == 0) {
1528 // pattern not found
1530 #else /*REGEX_SEARCH */
1532 struct re_pattern_buffer preg;
1536 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1542 // assume a LIMITED forward search
1550 // count the number of chars to search over, forward or backward
1554 // RANGE could be negative if we are searching backwards
1557 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1559 // The pattern was not compiled
1560 psbs("bad search pattern: \"%s\": %s", pat, q);
1561 i = 0; // return p if pattern not compiled
1571 // search for the compiled pattern, preg, in p[]
1572 // range < 0- search backward
1573 // range > 0- search forward
1575 // re_search() < 0 not found or error
1576 // re_search() > 0 index of found pattern
1577 // struct pattern char int int int struct reg
1578 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1579 i = re_search(&preg, q, size, 0, range, 0);
1582 i = 0; // return NULL if pattern not found
1585 if (dir == FORWARD) {
1591 #endif /*REGEX_SEARCH */
1593 #endif /* CONFIG_FEATURE_VI_SEARCH */
1595 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1597 if (c == 22) { // Is this an ctrl-V?
1598 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1599 p--; // backup onto ^
1600 refresh(FALSE); // show the ^
1604 file_modified++; // has the file been modified
1605 } else if (c == 27) { // Is this an ESC?
1608 end_cmd_q(); // stop adding to q
1609 last_status_cksum = 0; // force status update
1610 if ((p[-1] != '\n') && (dot>text)) {
1613 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1615 if ((p[-1] != '\n') && (dot>text)) {
1617 p = text_hole_delete(p, p); // shrink buffer 1 char
1620 // insert a char into text[]
1621 Byte *sp; // "save p"
1624 c = '\n'; // translate \r to \n
1625 sp = p; // remember addr of insert
1626 p = stupid_insert(p, c); // insert the char
1627 #ifdef CONFIG_FEATURE_VI_SETOPTS
1628 if (showmatch && strchr(")]}", *sp) != NULL) {
1631 if (autoindent && c == '\n') { // auto indent the new line
1634 q = prev_line(p); // use prev line as templet
1635 for (; isblnk(*q); q++) {
1636 p = stupid_insert(p, *q); // insert the char
1639 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1644 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1646 p = text_hole_make(p, 1);
1649 file_modified++; // has the file been modified
1655 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1657 Byte *save_dot, *p, *q;
1663 if (strchr("cdy><", c)) {
1664 // these cmds operate on whole lines
1665 p = q = begin_line(p);
1666 for (cnt = 1; cnt < cmdcnt; cnt++) {
1670 } else if (strchr("^%$0bBeEft", c)) {
1671 // These cmds operate on char positions
1672 do_cmd(c); // execute movement cmd
1674 } else if (strchr("wW", c)) {
1675 do_cmd(c); // execute movement cmd
1676 // if we are at the next word's first char
1677 // step back one char
1678 // but check the possibilities when it is true
1679 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1680 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1681 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1682 dot--; // move back off of next word
1683 if (dot > text && *dot == '\n')
1684 dot--; // stay off NL
1686 } else if (strchr("H-k{", c)) {
1687 // these operate on multi-lines backwards
1688 q = end_line(dot); // find NL
1689 do_cmd(c); // execute movement cmd
1692 } else if (strchr("L+j}\r\n", c)) {
1693 // these operate on multi-lines forwards
1694 p = begin_line(dot);
1695 do_cmd(c); // execute movement cmd
1696 dot_end(); // find NL
1699 c = 27; // error- return an ESC char
1712 static int st_test(Byte * p, int type, int dir, Byte * tested)
1722 if (type == S_BEFORE_WS) {
1724 test = ((!isspace(c)) || c == '\n');
1726 if (type == S_TO_WS) {
1728 test = ((!isspace(c)) || c == '\n');
1730 if (type == S_OVER_WS) {
1732 test = ((isspace(c)));
1734 if (type == S_END_PUNCT) {
1736 test = ((ispunct(c)));
1738 if (type == S_END_ALNUM) {
1740 test = ((isalnum(c)) || c == '_');
1746 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1750 while (st_test(p, type, dir, &c)) {
1751 // make sure we limit search to correct number of lines
1752 if (c == '\n' && --linecnt < 1)
1754 if (dir >= 0 && p >= end - 1)
1756 if (dir < 0 && p <= text)
1758 p += dir; // move to next char
1763 // find matching char of pair () [] {}
1764 static Byte *find_pair(Byte * p, Byte c)
1771 dir = 1; // assume forward
1795 for (q = p + dir; text <= q && q < end; q += dir) {
1796 // look for match, count levels of pairs (( ))
1798 level++; // increase pair levels
1800 level--; // reduce pair level
1802 break; // found matching pair
1805 q = NULL; // indicate no match
1809 #ifdef CONFIG_FEATURE_VI_SETOPTS
1810 // show the matching char of a pair, () [] {}
1811 static void showmatching(Byte * p)
1815 // we found half of a pair
1816 q = find_pair(p, *p); // get loc of matching char
1818 indicate_error('3'); // no matching char
1820 // "q" now points to matching pair
1821 save_dot = dot; // remember where we are
1822 dot = q; // go to new loc
1823 refresh(FALSE); // let the user see it
1824 (void) mysleep(40); // give user some time
1825 dot = save_dot; // go back to old loc
1829 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1831 // open a hole in text[]
1832 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1841 cnt = end - src; // the rest of buffer
1842 if (memmove(dest, src, cnt) != dest) {
1843 psbs("can't create room for new characters");
1845 memset(p, ' ', size); // clear new hole
1846 end = end + size; // adjust the new END
1847 file_modified++; // has the file been modified
1852 // close a hole in text[]
1853 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1858 // move forwards, from beginning
1862 if (q < p) { // they are backward- swap them
1866 hole_size = q - p + 1;
1868 if (src < text || src > end)
1870 if (dest < text || dest >= end)
1873 goto thd_atend; // just delete the end of the buffer
1874 if (memmove(dest, src, cnt) != dest) {
1875 psbs("can't delete the character");
1878 end = end - hole_size; // adjust the new END
1880 dest = end - 1; // make sure dest in below end-1
1882 dest = end = text; // keep pointers valid
1883 file_modified++; // has the file been modified
1888 // copy text into register, then delete text.
1889 // if dist <= 0, do not include, or go past, a NewLine
1891 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1895 // make sure start <= stop
1897 // they are backwards, reverse them
1903 // we can not cross NL boundaries
1907 // dont go past a NewLine
1908 for (; p + 1 <= stop; p++) {
1910 stop = p; // "stop" just before NewLine
1916 #ifdef CONFIG_FEATURE_VI_YANKMARK
1917 text_yank(start, stop, YDreg);
1918 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1919 if (yf == YANKDEL) {
1920 p = text_hole_delete(start, stop);
1925 static void show_help(void)
1927 puts("These features are available:"
1928 #ifdef CONFIG_FEATURE_VI_SEARCH
1929 "\n\tPattern searches with / and ?"
1930 #endif /* CONFIG_FEATURE_VI_SEARCH */
1931 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1932 "\n\tLast command repeat with \'.\'"
1933 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1934 #ifdef CONFIG_FEATURE_VI_YANKMARK
1935 "\n\tLine marking with 'x"
1936 "\n\tNamed buffers with \"x"
1937 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1938 #ifdef CONFIG_FEATURE_VI_READONLY
1939 "\n\tReadonly if vi is called as \"view\""
1940 "\n\tReadonly with -R command line arg"
1941 #endif /* CONFIG_FEATURE_VI_READONLY */
1942 #ifdef CONFIG_FEATURE_VI_SET
1943 "\n\tSome colon mode commands with \':\'"
1944 #endif /* CONFIG_FEATURE_VI_SET */
1945 #ifdef CONFIG_FEATURE_VI_SETOPTS
1946 "\n\tSettable options with \":set\""
1947 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1948 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1949 "\n\tSignal catching- ^C"
1950 "\n\tJob suspend and resume with ^Z"
1951 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1952 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1953 "\n\tAdapt to window re-sizes"
1954 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1958 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1963 strcpy((char *) buf, ""); // init buf
1964 if (strlen((char *) s) <= 0)
1965 s = (Byte *) "(NULL)";
1966 for (; *s > '\0'; s++) {
1970 c_is_no_print = c > 127 && !Isprint(c);
1971 if (c_is_no_print) {
1972 strcat((char *) buf, SOn);
1975 if (c < ' ' || c == 127) {
1976 strcat((char *) buf, "^");
1983 strcat((char *) buf, (char *) b);
1985 strcat((char *) buf, SOs);
1987 strcat((char *) buf, "$");
1992 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1993 static void start_new_cmd_q(Byte c)
1996 free(last_modifying_cmd);
1997 // get buffer for new cmd
1998 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1999 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2000 // if there is a current cmd count put it in the buffer first
2002 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
2003 else // just save char c onto queue
2004 last_modifying_cmd[0] = c;
2008 static void end_cmd_q(void)
2010 #ifdef CONFIG_FEATURE_VI_YANKMARK
2011 YDreg = 26; // go back to default Yank/Delete reg
2012 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2016 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2018 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2019 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2023 i = strlen((char *) s);
2024 p = text_hole_make(p, i);
2025 strncpy((char *) p, (char *) s, i);
2026 for (cnt = 0; *s != '\0'; s++) {
2030 #ifdef CONFIG_FEATURE_VI_YANKMARK
2031 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2032 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2035 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2037 #ifdef CONFIG_FEATURE_VI_YANKMARK
2038 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2043 if (q < p) { // they are backwards- reverse them
2050 free(t); // if already a yank register, free it
2051 t = (Byte *) xmalloc(cnt + 1); // get a new register
2052 memset(t, '\0', cnt + 1); // clear new text[]
2053 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2058 static Byte what_reg(void)
2064 c = 'D'; // default to D-reg
2065 if (0 <= YDreg && YDreg <= 25)
2066 c = 'a' + (Byte) YDreg;
2074 static void check_context(Byte cmd)
2076 // A context is defined to be "modifying text"
2077 // Any modifying command establishes a new context.
2079 if (dot < context_start || dot > context_end) {
2080 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2081 // we are trying to modify text[]- make this the current context
2082 mark[27] = mark[26]; // move cur to prev
2083 mark[26] = dot; // move local to cur
2084 context_start = prev_line(prev_line(dot));
2085 context_end = next_line(next_line(dot));
2086 //loiter= start_loiter= now;
2092 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2096 // the current context is in mark[26]
2097 // the previous context is in mark[27]
2098 // only swap context if other context is valid
2099 if (text <= mark[27] && mark[27] <= end - 1) {
2101 mark[27] = mark[26];
2103 p = mark[26]; // where we are going- previous context
2104 context_start = prev_line(prev_line(prev_line(p)));
2105 context_end = next_line(next_line(next_line(p)));
2109 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2111 static int isblnk(Byte c) // is the char a blank or tab
2113 return (c == ' ' || c == '\t');
2116 //----- Set terminal attributes --------------------------------
2117 static void rawmode(void)
2119 tcgetattr(0, &term_orig);
2120 term_vi = term_orig;
2121 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2122 term_vi.c_iflag &= (~IXON & ~ICRNL);
2123 term_vi.c_oflag &= (~ONLCR);
2124 term_vi.c_cc[VMIN] = 1;
2125 term_vi.c_cc[VTIME] = 0;
2126 erase_char = term_vi.c_cc[VERASE];
2127 tcsetattr(0, TCSANOW, &term_vi);
2130 static void cookmode(void)
2133 tcsetattr(0, TCSANOW, &term_orig);
2136 //----- Come here when we get a window resize signal ---------
2137 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2138 static void winch_sig(int sig)
2140 signal(SIGWINCH, winch_sig);
2141 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2143 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2144 new_screen(rows, columns); // get memory for virtual screen
2145 redraw(TRUE); // re-draw the screen
2148 //----- Come here when we get a continue signal -------------------
2149 static void cont_sig(int sig)
2151 rawmode(); // terminal to "raw"
2152 last_status_cksum = 0; // force status update
2153 redraw(TRUE); // re-draw the screen
2155 signal(SIGTSTP, suspend_sig);
2156 signal(SIGCONT, SIG_DFL);
2157 kill(my_pid, SIGCONT);
2160 //----- Come here when we get a Suspend signal -------------------
2161 static void suspend_sig(int sig)
2163 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2164 clear_to_eol(); // Erase to end of line
2165 cookmode(); // terminal to "cooked"
2167 signal(SIGCONT, cont_sig);
2168 signal(SIGTSTP, SIG_DFL);
2169 kill(my_pid, SIGTSTP);
2172 //----- Come here when we get a signal ---------------------------
2173 static void catch_sig(int sig)
2175 signal(SIGHUP, catch_sig);
2176 signal(SIGINT, catch_sig);
2177 signal(SIGTERM, catch_sig);
2178 signal(SIGALRM, catch_sig);
2180 longjmp(restart, sig);
2183 //----- Come here when we get a core dump signal -----------------
2184 static void core_sig(int sig)
2186 signal(SIGQUIT, core_sig);
2187 signal(SIGILL, core_sig);
2188 signal(SIGTRAP, core_sig);
2189 signal(SIGIOT, core_sig);
2190 signal(SIGABRT, core_sig);
2191 signal(SIGFPE, core_sig);
2192 signal(SIGBUS, core_sig);
2193 signal(SIGSEGV, core_sig);
2195 signal(SIGSYS, core_sig);
2198 if(sig) { // signaled
2199 dot = bound_dot(dot); // make sure "dot" is valid
2201 longjmp(restart, sig);
2204 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2206 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2208 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2213 tv.tv_usec = hund * 10000;
2214 select(1, &rfds, NULL, NULL, &tv);
2215 return (FD_ISSET(0, &rfds));
2218 #define readbuffer bb_common_bufsiz1
2220 static int readed_for_parse;
2222 //----- IO Routines --------------------------------------------
2223 static Byte readit(void) // read (maybe cursor) key from stdin
2232 static const struct esc_cmds esccmds[] = {
2233 {"OA", (Byte) VI_K_UP}, // cursor key Up
2234 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2235 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2236 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2237 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2238 {"OF", (Byte) VI_K_END}, // Cursor Key End
2239 {"[A", (Byte) VI_K_UP}, // cursor key Up
2240 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2241 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2242 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2243 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2244 {"[F", (Byte) VI_K_END}, // Cursor Key End
2245 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2246 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2247 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2248 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2249 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2250 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2251 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2252 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2253 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2254 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2255 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2256 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2257 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2258 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2259 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2260 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2261 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2262 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2263 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2264 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2265 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2268 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2270 (void) alarm(0); // turn alarm OFF while we wait for input
2272 n = readed_for_parse;
2273 // get input from User- are there already input chars in Q?
2276 // the Q is empty, wait for a typed char
2277 n = read(0, readbuffer, BUFSIZ - 1);
2280 goto ri0; // interrupted sys call
2283 if (errno == EFAULT)
2285 if (errno == EINVAL)
2293 if (readbuffer[0] == 27) {
2294 // This is an ESC char. Is this Esc sequence?
2295 // Could be bare Esc key. See if there are any
2296 // more chars to read after the ESC. This would
2297 // be a Function or Cursor Key sequence.
2301 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2303 // keep reading while there are input chars and room in buffer
2304 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2305 // read the rest of the ESC string
2306 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2312 readed_for_parse = n;
2315 if(c == 27 && n > 1) {
2316 // Maybe cursor or function key?
2317 const struct esc_cmds *eindex;
2319 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2320 int cnt = strlen(eindex->seq);
2324 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2326 // is a Cursor key- put derived value back into Q
2328 // for squeeze out the ESC sequence
2332 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2333 /* defined ESC sequence not found, set only one ESC */
2339 // remove key sequence from Q
2340 readed_for_parse -= n;
2341 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2342 (void) alarm(3); // we are done waiting for input, turn alarm ON
2346 //----- IO Routines --------------------------------------------
2347 static Byte get_one_char(void)
2351 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2352 // ! adding2q && ioq == 0 read()
2353 // ! adding2q && ioq != 0 *ioq
2354 // adding2q *last_modifying_cmd= read()
2356 // we are not adding to the q.
2357 // but, we may be reading from a q
2359 // there is no current q, read from STDIN
2360 c = readit(); // get the users input
2362 // there is a queue to get chars from first
2365 // the end of the q, read from STDIN
2367 ioq_start = ioq = 0;
2368 c = readit(); // get the users input
2372 // adding STDIN chars to q
2373 c = readit(); // get the users input
2374 if (last_modifying_cmd != 0) {
2375 int len = strlen((char *) last_modifying_cmd);
2376 if (len + 1 >= BUFSIZ) {
2377 psbs("last_modifying_cmd overrun");
2379 // add new char to q
2380 last_modifying_cmd[len] = c;
2384 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2385 c = readit(); // get the users input
2386 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2387 return (c); // return the char, where ever it came from
2390 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2395 static Byte *obufp = NULL;
2397 strcpy((char *) buf, (char *) prompt);
2398 last_status_cksum = 0; // force status update
2399 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2400 clear_to_eol(); // clear the line
2401 write1(prompt); // write out the :, /, or ? prompt
2403 for (i = strlen((char *) buf); i < BUFSIZ;) {
2404 c = get_one_char(); // read user input
2405 if (c == '\n' || c == '\r' || c == 27)
2406 break; // is this end of input
2407 if (c == erase_char || c == 8 || c == 127) {
2408 // user wants to erase prev char
2409 i--; // backup to prev char
2410 buf[i] = '\0'; // erase the char
2411 buf[i + 1] = '\0'; // null terminate buffer
2412 write1("\b \b"); // erase char on screen
2413 if (i <= 0) { // user backs up before b-o-l, exit
2417 buf[i] = c; // save char in buffer
2418 buf[i + 1] = '\0'; // make sure buffer is null terminated
2419 putchar(c); // echo the char back to user
2425 obufp = (Byte *) bb_xstrdup((char *) buf);
2429 static int file_size(const Byte * fn) // what is the byte size of "fn"
2434 if (fn == 0 || strlen(fn) <= 0)
2437 sr = stat((char *) fn, &st_buf); // see if file exists
2439 cnt = (int) st_buf.st_size;
2444 static int file_insert(Byte * fn, Byte * p, int size)
2449 #ifdef CONFIG_FEATURE_VI_READONLY
2451 #endif /* CONFIG_FEATURE_VI_READONLY */
2452 if (fn == 0 || strlen((char*) fn) <= 0) {
2453 psbs("No filename given");
2457 // OK- this is just a no-op
2462 psbs("Trying to insert a negative number (%d) of characters", size);
2465 if (p < text || p > end) {
2466 psbs("Trying to insert file outside of memory");
2470 // see if we can open the file
2471 #ifdef CONFIG_FEATURE_VI_READONLY
2472 if (vi_readonly) goto fi1; // do not try write-mode
2474 fd = open((char *) fn, O_RDWR); // assume read & write
2476 // could not open for writing- maybe file is read only
2477 #ifdef CONFIG_FEATURE_VI_READONLY
2480 fd = open((char *) fn, O_RDONLY); // try read-only
2482 psbs("\"%s\" %s", fn, "could not open file");
2485 #ifdef CONFIG_FEATURE_VI_READONLY
2486 // got the file- read-only
2488 #endif /* CONFIG_FEATURE_VI_READONLY */
2490 p = text_hole_make(p, size);
2491 cnt = read(fd, p, size);
2495 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2496 psbs("could not read file \"%s\"", fn);
2497 } else if (cnt < size) {
2498 // There was a partial read, shrink unused space text[]
2499 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2500 psbs("could not read all of file \"%s\"", fn);
2508 static int file_write(Byte * fn, Byte * first, Byte * last)
2510 int fd, cnt, charcnt;
2513 psbs("No current filename");
2517 // FIXIT- use the correct umask()
2518 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2521 cnt = last - first + 1;
2522 charcnt = write(fd, first, cnt);
2523 if (charcnt == cnt) {
2525 //file_modified= FALSE; // the file has not been modified
2533 //----- Terminal Drawing ---------------------------------------
2534 // The terminal is made up of 'rows' line of 'columns' columns.
2535 // classically this would be 24 x 80.
2536 // screen coordinates
2542 // 23,0 ... 23,79 status line
2545 //----- Move the cursor to row x col (count from 0, not 1) -------
2546 static void place_cursor(int row, int col, int opti)
2550 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2553 // char cm3[BUFSIZ];
2555 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2557 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2559 if (row < 0) row = 0;
2560 if (row >= rows) row = rows - 1;
2561 if (col < 0) col = 0;
2562 if (col >= columns) col = columns - 1;
2564 //----- 1. Try the standard terminal ESC sequence
2565 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2567 if (! opti) goto pc0;
2569 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2570 //----- find the minimum # of chars to move cursor -------------
2571 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2572 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2574 // move to the correct row
2575 while (row < Rrow) {
2576 // the cursor has to move up
2580 while (row > Rrow) {
2581 // the cursor has to move down
2582 strcat(cm2, CMdown);
2586 // now move to the correct column
2587 strcat(cm2, "\r"); // start at col 0
2588 // just send out orignal source char to get to correct place
2589 screenp = &screen[row * columns]; // start of screen line
2590 strncat(cm2, screenp, col);
2592 //----- 3. Try some other way of moving cursor
2593 //---------------------------------------------
2595 // pick the shortest cursor motion to send out
2597 if (strlen(cm2) < strlen(cm)) {
2599 } /* else if (strlen(cm3) < strlen(cm)) {
2602 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2604 write1(cm); // move the cursor
2607 //----- Erase from cursor to end of line -----------------------
2608 static void clear_to_eol(void)
2610 write1(Ceol); // Erase from cursor to end of line
2613 //----- Erase from cursor to end of screen -----------------------
2614 static void clear_to_eos(void)
2616 write1(Ceos); // Erase from cursor to end of screen
2619 //----- Start standout mode ------------------------------------
2620 static void standout_start(void) // send "start reverse video" sequence
2622 write1(SOs); // Start reverse video mode
2625 //----- End standout mode --------------------------------------
2626 static void standout_end(void) // send "end reverse video" sequence
2628 write1(SOn); // End reverse video mode
2631 //----- Flash the screen --------------------------------------
2632 static void flash(int h)
2634 standout_start(); // send "start reverse video" sequence
2637 standout_end(); // send "end reverse video" sequence
2641 static void Indicate_Error(void)
2643 #ifdef CONFIG_FEATURE_VI_CRASHME
2645 return; // generate a random command
2646 #endif /* CONFIG_FEATURE_VI_CRASHME */
2648 write1(bell); // send out a bell character
2654 //----- Screen[] Routines --------------------------------------
2655 //----- Erase the Screen[] memory ------------------------------
2656 static void screen_erase(void)
2658 memset(screen, ' ', screensize); // clear new screen
2661 static int bufsum(char *buf, int count)
2664 char *e = buf + count;
2670 //----- Draw the status line at bottom of the screen -------------
2671 static void show_status_line(void)
2673 int cnt = 0, cksum = 0;
2675 // either we already have an error or status message, or we
2677 if (!have_status_msg) {
2678 cnt = format_edit_status();
2679 cksum = bufsum(status_buffer, cnt);
2681 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2682 last_status_cksum= cksum; // remember if we have seen this line
2683 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2684 write1(status_buffer);
2686 if (have_status_msg) {
2687 if ((strlen(status_buffer) - (have_status_msg - 1)) >
2689 have_status_msg = 0;
2692 have_status_msg = 0;
2694 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2699 //----- format the status buffer, the bottom line of screen ------
2700 // format status buffer, with STANDOUT mode
2701 static void psbs(const char *format, ...)
2705 va_start(args, format);
2706 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2707 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2708 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2711 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2716 // format status buffer
2717 static void psb(const char *format, ...)
2721 va_start(args, format);
2722 vsprintf((char *) status_buffer, format, args);
2725 have_status_msg = 1;
2730 static void ni(Byte * s) // display messages
2734 print_literal(buf, s);
2735 psbs("\'%s\' is not implemented", buf);
2738 static int format_edit_status(void) // show file status on status line
2740 int cur, percent, ret, trunc_at;
2743 // file_modified is now a counter rather than a flag. this
2744 // helps reduce the amount of line counting we need to do.
2745 // (this will cause a mis-reporting of modified status
2746 // once every MAXINT editing operations.)
2748 // it would be nice to do a similar optimization here -- if
2749 // we haven't done a motion that could have changed which line
2750 // we're on, then we shouldn't have to do this count_lines()
2751 cur = count_lines(text, dot);
2753 // reduce counting -- the total lines can't have
2754 // changed if we haven't done any edits.
2755 if (file_modified != last_file_modified) {
2756 tot = cur + count_lines(dot, end - 1) - 1;
2757 last_file_modified = file_modified;
2760 // current line percent
2761 // ------------- ~~ ----------
2764 percent = (100 * cur) / tot;
2770 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2771 columns : STATUS_BUFFER_LEN-1;
2773 ret = snprintf((char *) status_buffer, trunc_at+1,
2774 #ifdef CONFIG_FEATURE_VI_READONLY
2775 "%c %s%s%s %d/%d %d%%",
2777 "%c %s%s %d/%d %d%%",
2779 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2780 (cfn != 0 ? (char *) cfn : "No file"),
2781 #ifdef CONFIG_FEATURE_VI_READONLY
2782 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2784 (file_modified ? " [modified]" : ""),
2787 if (ret >= 0 && ret < trunc_at)
2788 return ret; /* it all fit */
2790 return trunc_at; /* had to truncate */
2793 //----- Force refresh of all Lines -----------------------------
2794 static void redraw(int full_screen)
2796 place_cursor(0, 0, FALSE); // put cursor in correct place
2797 clear_to_eos(); // tel terminal to erase display
2798 screen_erase(); // erase the internal screen buffer
2799 last_status_cksum = 0; // force status update
2800 refresh(full_screen); // this will redraw the entire display
2804 //----- Format a text[] line into a buffer ---------------------
2805 static void format_line(Byte *dest, Byte *src, int li)
2810 for (co= 0; co < MAX_SCR_COLS; co++) {
2811 c= ' '; // assume blank
2812 if (li > 0 && co == 0) {
2813 c = '~'; // not first line, assume Tilde
2815 // are there chars in text[] and have we gone past the end
2816 if (text < end && src < end) {
2821 if (c > 127 && !Isprint(c)) {
2824 if (c < ' ' || c == 127) {
2828 for (; (co % tabstop) != (tabstop - 1); co++) {
2836 c += '@'; // make it visible
2839 // the co++ is done here so that the column will
2840 // not be overwritten when we blank-out the rest of line
2847 //----- Refresh the changed screen lines -----------------------
2848 // Copy the source line from text[] into the buffer and note
2849 // if the current screenline is different from the new buffer.
2850 // If they differ then that line needs redrawing on the terminal.
2852 static void refresh(int full_screen)
2854 static int old_offset;
2856 Byte buf[MAX_SCR_COLS];
2857 Byte *tp, *sp; // pointer into text[] and screen[]
2858 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2859 int last_li= -2; // last line that changed- for optimizing cursor movement
2860 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2862 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2864 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2865 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2866 tp = screenbegin; // index into text[] of top line
2868 // compare text[] to screen[] and mark screen[] lines that need updating
2869 for (li = 0; li < rows - 1; li++) {
2870 int cs, ce; // column start & end
2871 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2872 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2873 // format current text line into buf
2874 format_line(buf, tp, li);
2876 // skip to the end of the current text[] line
2877 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2879 // see if there are any changes between vitual screen and buf
2880 changed = FALSE; // assume no change
2883 sp = &screen[li * columns]; // start of screen line
2885 // force re-draw of every single column from 0 - columns-1
2888 // compare newly formatted buffer with virtual screen
2889 // look forward for first difference between buf and screen
2890 for ( ; cs <= ce; cs++) {
2891 if (buf[cs + offset] != sp[cs]) {
2892 changed = TRUE; // mark for redraw
2897 // look backward for last difference between buf and screen
2898 for ( ; ce >= cs; ce--) {
2899 if (buf[ce + offset] != sp[ce]) {
2900 changed = TRUE; // mark for redraw
2904 // now, cs is index of first diff, and ce is index of last diff
2906 // if horz offset has changed, force a redraw
2907 if (offset != old_offset) {
2912 // make a sanity check of columns indexes
2914 if (ce > columns-1) ce= columns-1;
2915 if (cs > ce) { cs= 0; ce= columns-1; }
2916 // is there a change between vitual screen and buf
2918 // copy changed part of buffer to virtual screen
2919 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2921 // move cursor to column of first change
2922 if (offset != old_offset) {
2923 // opti_cur_move is still too stupid
2924 // to handle offsets correctly
2925 place_cursor(li, cs, FALSE);
2927 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2928 // if this just the next line
2929 // try to optimize cursor movement
2930 // otherwise, use standard ESC sequence
2931 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2933 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2934 place_cursor(li, cs, FALSE); // use standard ESC sequence
2935 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2938 // write line out to terminal
2948 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2950 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2954 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2955 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2958 place_cursor(crow, ccol, FALSE);
2959 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2961 if (offset != old_offset)
2962 old_offset = offset;
2965 //---------------------------------------------------------------------
2966 //----- the Ascii Chart -----------------------------------------------
2968 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2969 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2970 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2971 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2972 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2973 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2974 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2975 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2976 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2977 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2978 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2979 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2980 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2981 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2982 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2983 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2984 //---------------------------------------------------------------------
2986 //----- Execute a Vi Command -----------------------------------
2987 static void do_cmd(Byte c)
2989 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2990 int cnt, i, j, dir, yf;
2992 c1 = c; // quiet the compiler
2993 cnt = yf = dir = 0; // quiet the compiler
2994 p = q = save_dot = msg = buf; // quiet the compiler
2995 memset(buf, '\0', 9); // clear buf
2999 /* if this is a cursor key, skip these checks */
3012 if (cmd_mode == 2) {
3013 // flip-flop Insert/Replace mode
3014 if (c == VI_K_INSERT) goto dc_i;
3015 // we are 'R'eplacing the current *dot with new char
3017 // don't Replace past E-o-l
3018 cmd_mode = 1; // convert to insert
3020 if (1 <= c || Isprint(c)) {
3022 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3023 dot = char_insert(dot, c); // insert new char
3028 if (cmd_mode == 1) {
3029 // hitting "Insert" twice means "R" replace mode
3030 if (c == VI_K_INSERT) goto dc5;
3031 // insert the char c at "dot"
3032 if (1 <= c || Isprint(c)) {
3033 dot = char_insert(dot, c);
3048 #ifdef CONFIG_FEATURE_VI_CRASHME
3049 case 0x14: // dc4 ctrl-T
3050 crashme = (crashme == 0) ? 1 : 0;
3052 #endif /* CONFIG_FEATURE_VI_CRASHME */
3081 //case 'u': // u- FIXME- there is no undo
3083 default: // unrecognised command
3092 end_cmd_q(); // stop adding to q
3093 case 0x00: // nul- ignore
3095 case 2: // ctrl-B scroll up full screen
3096 case VI_K_PAGEUP: // Cursor Key Page Up
3097 dot_scroll(rows - 2, -1);
3099 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3100 case 0x03: // ctrl-C interrupt
3101 longjmp(restart, 1);
3103 case 26: // ctrl-Z suspend
3104 suspend_sig(SIGTSTP);
3106 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3107 case 4: // ctrl-D scroll down half screen
3108 dot_scroll((rows - 2) / 2, 1);
3110 case 5: // ctrl-E scroll down one line
3113 case 6: // ctrl-F scroll down full screen
3114 case VI_K_PAGEDOWN: // Cursor Key Page Down
3115 dot_scroll(rows - 2, 1);
3117 case 7: // ctrl-G show current status
3118 last_status_cksum = 0; // force status update
3120 case 'h': // h- move left
3121 case VI_K_LEFT: // cursor key Left
3122 case 8: // ctrl-H- move left (This may be ERASE char)
3123 case 127: // DEL- move left (This may be ERASE char)
3129 case 10: // Newline ^J
3130 case 'j': // j- goto next line, same col
3131 case VI_K_DOWN: // cursor key Down
3135 dot_next(); // go to next B-o-l
3136 dot = move_to_col(dot, ccol + offset); // try stay in same col
3138 case 12: // ctrl-L force redraw whole screen
3139 case 18: // ctrl-R force redraw
3140 place_cursor(0, 0, FALSE); // put cursor in correct place
3141 clear_to_eos(); // tel terminal to erase display
3143 screen_erase(); // erase the internal screen buffer
3144 last_status_cksum = 0; // force status update
3145 refresh(TRUE); // this will redraw the entire display
3147 case 13: // Carriage Return ^M
3148 case '+': // +- goto next line
3155 case 21: // ctrl-U scroll up half screen
3156 dot_scroll((rows - 2) / 2, -1);
3158 case 25: // ctrl-Y scroll up one line
3164 cmd_mode = 0; // stop insrting
3166 last_status_cksum = 0; // force status update
3168 case ' ': // move right
3169 case 'l': // move right
3170 case VI_K_RIGHT: // Cursor Key Right
3176 #ifdef CONFIG_FEATURE_VI_YANKMARK
3177 case '"': // "- name a register to use for Delete/Yank
3178 c1 = get_one_char();
3186 case '\'': // '- goto a specific mark
3187 c1 = get_one_char();
3193 if (text <= q && q < end) {
3195 dot_begin(); // go to B-o-l
3198 } else if (c1 == '\'') { // goto previous context
3199 dot = swap_context(dot); // swap current and previous context
3200 dot_begin(); // go to B-o-l
3206 case 'm': // m- Mark a line
3207 // this is really stupid. If there are any inserts or deletes
3208 // between text[0] and dot then this mark will not point to the
3209 // correct location! It could be off by many lines!
3210 // Well..., at least its quick and dirty.
3211 c1 = get_one_char();
3215 // remember the line
3216 mark[(int) c1] = dot;
3221 case 'P': // P- Put register before
3222 case 'p': // p- put register after
3225 psbs("Nothing in register %c", what_reg());
3228 // are we putting whole lines or strings
3229 if (strchr((char *) p, '\n') != NULL) {
3231 dot_begin(); // putting lines- Put above
3234 // are we putting after very last line?
3235 if (end_line(dot) == (end - 1)) {
3236 dot = end; // force dot to end of text[]
3238 dot_next(); // next line, then put before
3243 dot_right(); // move to right, can move to NL
3245 dot = string_insert(dot, p); // insert the string
3246 end_cmd_q(); // stop adding to q
3248 case 'U': // U- Undo; replace current line with original version
3249 if (reg[Ureg] != 0) {
3250 p = begin_line(dot);
3252 p = text_hole_delete(p, q); // delete cur line
3253 p = string_insert(p, reg[Ureg]); // insert orig line
3258 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3259 case '$': // $- goto end of line
3260 case VI_K_END: // Cursor Key End
3264 dot = end_line(dot);
3266 case '%': // %- find matching char of pair () [] {}
3267 for (q = dot; q < end && *q != '\n'; q++) {
3268 if (strchr("()[]{}", *q) != NULL) {
3269 // we found half of a pair
3270 p = find_pair(q, *q);
3282 case 'f': // f- forward to a user specified char
3283 last_forward_char = get_one_char(); // get the search char
3285 // dont separate these two commands. 'f' depends on ';'
3287 //**** fall thru to ... ';'
3288 case ';': // ;- look at rest of line for last forward char
3292 if (last_forward_char == 0) break;
3294 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3297 if (*q == last_forward_char)
3300 case '-': // -- goto prev line
3307 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3308 case '.': // .- repeat the last modifying command
3309 // Stuff the last_modifying_cmd back into stdin
3310 // and let it be re-executed.
3311 if (last_modifying_cmd != 0) {
3312 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3315 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3316 #ifdef CONFIG_FEATURE_VI_SEARCH
3317 case '?': // /- search for a pattern
3318 case '/': // /- search for a pattern
3321 q = get_input_line(buf); // get input line- use "status line"
3322 if (strlen((char *) q) == 1)
3323 goto dc3; // if no pat re-use old pat
3324 if (strlen((char *) q) > 1) { // new pat- save it and find
3325 // there is a new pat
3326 free(last_search_pattern);
3327 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3328 goto dc3; // now find the pattern
3330 // user changed mind and erased the "/"- do nothing
3332 case 'N': // N- backward search for last pattern
3336 dir = BACK; // assume BACKWARD search
3338 if (last_search_pattern[0] == '?') {
3342 goto dc4; // now search for pattern
3344 case 'n': // n- repeat search for last pattern
3345 // search rest of text[] starting at next char
3346 // if search fails return orignal "p" not the "p+1" address
3351 if (last_search_pattern == 0) {
3352 msg = (Byte *) "No previous regular expression";
3355 if (last_search_pattern[0] == '/') {
3356 dir = FORWARD; // assume FORWARD search
3359 if (last_search_pattern[0] == '?') {
3364 q = char_search(p, last_search_pattern + 1, dir, FULL);
3366 dot = q; // good search, update "dot"
3370 // no pattern found between "dot" and "end"- continue at top
3375 q = char_search(p, last_search_pattern + 1, dir, FULL);
3376 if (q != NULL) { // found something
3377 dot = q; // found new pattern- goto it
3378 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3380 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3383 msg = (Byte *) "Pattern not found";
3386 if (*msg) psbs("%s", msg);
3388 case '{': // {- move backward paragraph
3389 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3390 if (q != NULL) { // found blank line
3391 dot = next_line(q); // move to next blank line
3394 case '}': // }- move forward paragraph
3395 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3396 if (q != NULL) { // found blank line
3397 dot = next_line(q); // move to next blank line
3400 #endif /* CONFIG_FEATURE_VI_SEARCH */
3401 case '0': // 0- goto begining of line
3411 if (c == '0' && cmdcnt < 1) {
3412 dot_begin(); // this was a standalone zero
3414 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3417 case ':': // :- the colon mode commands
3418 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3419 #ifdef CONFIG_FEATURE_VI_COLON
3420 colon(p); // execute the command
3421 #else /* CONFIG_FEATURE_VI_COLON */
3423 p++; // move past the ':'
3424 cnt = strlen((char *) p);
3427 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3428 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3429 if (file_modified && p[1] != '!') {
3430 psbs("No write since last change (:quit! overrides)");
3434 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3435 strncasecmp((char *) p, "wq", cnt) == 0 ||
3436 strncasecmp((char *) p, "x", cnt) == 0) {
3437 cnt = file_write(cfn, text, end - 1);
3440 psbs("Write error: %s", strerror(errno));
3443 last_file_modified = -1;
3444 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3445 if (p[0] == 'x' || p[1] == 'q') {
3449 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3450 last_status_cksum = 0; // force status update
3451 } else if (sscanf((char *) p, "%d", &j) > 0) {
3452 dot = find_line(j); // go to line # j
3454 } else { // unrecognised cmd
3457 #endif /* CONFIG_FEATURE_VI_COLON */
3459 case '<': // <- Left shift something
3460 case '>': // >- Right shift something
3461 cnt = count_lines(text, dot); // remember what line we are on
3462 c1 = get_one_char(); // get the type of thing to delete
3463 find_range(&p, &q, c1);
3464 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3467 i = count_lines(p, q); // # of lines we are shifting
3468 for ( ; i > 0; i--, p = next_line(p)) {
3470 // shift left- remove tab or 8 spaces
3472 // shrink buffer 1 char
3473 (void) text_hole_delete(p, p);
3474 } else if (*p == ' ') {
3475 // we should be calculating columns, not just SPACE
3476 for (j = 0; *p == ' ' && j < tabstop; j++) {
3477 (void) text_hole_delete(p, p);
3480 } else if (c == '>') {
3481 // shift right -- add tab or 8 spaces
3482 (void) char_insert(p, '\t');
3485 dot = find_line(cnt); // what line were we on
3487 end_cmd_q(); // stop adding to q
3489 case 'A': // A- append at e-o-l
3490 dot_end(); // go to e-o-l
3491 //**** fall thru to ... 'a'
3492 case 'a': // a- append after current char
3497 case 'B': // B- back a blank-delimited Word
3498 case 'E': // E- end of a blank-delimited word
3499 case 'W': // W- forward a blank-delimited word
3506 if (c == 'W' || isspace(dot[dir])) {
3507 dot = skip_thing(dot, 1, dir, S_TO_WS);
3508 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3511 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3513 case 'C': // C- Change to e-o-l
3514 case 'D': // D- delete to e-o-l
3516 dot = dollar_line(dot); // move to before NL
3517 // copy text into a register and delete
3518 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3520 goto dc_i; // start inserting
3521 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3523 end_cmd_q(); // stop adding to q
3524 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3526 case 'G': // G- goto to a line number (default= E-O-F)
3527 dot = end - 1; // assume E-O-F
3529 dot = find_line(cmdcnt); // what line is #cmdcnt
3533 case 'H': // H- goto top line on screen
3535 if (cmdcnt > (rows - 1)) {
3536 cmdcnt = (rows - 1);
3543 case 'I': // I- insert before first non-blank
3546 //**** fall thru to ... 'i'
3547 case 'i': // i- insert before current char
3548 case VI_K_INSERT: // Cursor Key Insert
3550 cmd_mode = 1; // start insrting
3552 case 'J': // J- join current and next lines together
3556 dot_end(); // move to NL
3557 if (dot < end - 1) { // make sure not last char in text[]
3558 *dot++ = ' '; // replace NL with space
3560 while (isblnk(*dot)) { // delete leading WS
3564 end_cmd_q(); // stop adding to q
3566 case 'L': // L- goto bottom line on screen
3568 if (cmdcnt > (rows - 1)) {
3569 cmdcnt = (rows - 1);
3577 case 'M': // M- goto middle line on screen
3579 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3580 dot = next_line(dot);
3582 case 'O': // O- open a empty line above
3584 p = begin_line(dot);
3585 if (p[-1] == '\n') {
3587 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3589 dot = char_insert(dot, '\n');
3592 dot = char_insert(dot, '\n'); // i\n ESC
3597 case 'R': // R- continuous Replace char
3601 case 'X': // X- delete char before dot
3602 case 'x': // x- delete the current char
3603 case 's': // s- substitute the current char
3610 if (dot[dir] != '\n') {
3612 dot--; // delete prev char
3613 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3616 goto dc_i; // start insrting
3617 end_cmd_q(); // stop adding to q
3619 case 'Z': // Z- if modified, {write}; exit
3620 // ZZ means to save file (if necessary), then exit
3621 c1 = get_one_char();
3627 #ifdef CONFIG_FEATURE_VI_READONLY
3630 #endif /* CONFIG_FEATURE_VI_READONLY */
3632 cnt = file_write(cfn, text, end - 1);
3635 psbs("Write error: %s", strerror(errno));
3636 } else if (cnt == (end - 1 - text + 1)) {
3643 case '^': // ^- move to first non-blank on line
3647 case 'b': // b- back a word
3648 case 'e': // e- end of word
3655 if ((dot + dir) < text || (dot + dir) > end - 1)
3658 if (isspace(*dot)) {
3659 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3661 if (isalnum(*dot) || *dot == '_') {
3662 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3663 } else if (ispunct(*dot)) {
3664 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3667 case 'c': // c- change something
3668 case 'd': // d- delete something
3669 #ifdef CONFIG_FEATURE_VI_YANKMARK
3670 case 'y': // y- yank something
3671 case 'Y': // Y- Yank a line
3672 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3673 yf = YANKDEL; // assume either "c" or "d"
3674 #ifdef CONFIG_FEATURE_VI_YANKMARK
3675 if (c == 'y' || c == 'Y')
3677 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3680 c1 = get_one_char(); // get the type of thing to delete
3681 find_range(&p, &q, c1);
3682 if (c1 == 27) { // ESC- user changed mind and wants out
3683 c = c1 = 27; // Escape- do nothing
3684 } else if (strchr("wW", c1)) {
3686 // don't include trailing WS as part of word
3687 while (isblnk(*q)) {
3688 if (q <= text || q[-1] == '\n')
3693 dot = yank_delete(p, q, 0, yf); // delete word
3694 } else if (strchr("^0bBeEft$", c1)) {
3695 // single line copy text into a register and delete
3696 dot = yank_delete(p, q, 0, yf); // delete word
3697 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3698 // multiple line copy text into a register and delete
3699 dot = yank_delete(p, q, 1, yf); // delete lines
3701 dot = char_insert(dot, '\n');
3702 // on the last line of file don't move to prev line
3703 if (dot != (end-1)) {
3706 } else if (c == 'd') {
3711 // could not recognize object
3712 c = c1 = 27; // error-
3716 // if CHANGING, not deleting, start inserting after the delete
3718 strcpy((char *) buf, "Change");
3719 goto dc_i; // start inserting
3722 strcpy((char *) buf, "Delete");
3724 #ifdef CONFIG_FEATURE_VI_YANKMARK
3725 if (c == 'y' || c == 'Y') {
3726 strcpy((char *) buf, "Yank");
3729 q = p + strlen((char *) p);
3730 for (cnt = 0; p <= q; p++) {
3734 psb("%s %d lines (%d chars) using [%c]",
3735 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3736 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3737 end_cmd_q(); // stop adding to q
3740 case 'k': // k- goto prev line, same col
3741 case VI_K_UP: // cursor key Up
3746 dot = move_to_col(dot, ccol + offset); // try stay in same col
3748 case 'r': // r- replace the current char with user input
3749 c1 = get_one_char(); // get the replacement char
3752 file_modified++; // has the file been modified
3754 end_cmd_q(); // stop adding to q
3756 case 't': // t- move to char prior to next x
3757 last_forward_char = get_one_char();
3759 if (*dot == last_forward_char)
3761 last_forward_char= 0;
3763 case 'w': // w- forward a word
3767 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3768 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3769 } else if (ispunct(*dot)) { // we are on PUNCT
3770 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3773 dot++; // move over word
3774 if (isspace(*dot)) {
3775 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3779 c1 = get_one_char(); // get the replacement char
3782 cnt = (rows - 2) / 2; // put dot at center
3784 cnt = rows - 2; // put dot at bottom
3785 screenbegin = begin_line(dot); // start dot at top
3786 dot_scroll(cnt, -1);
3788 case '|': // |- move to column "cmdcnt"
3789 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3791 case '~': // ~- flip the case of letters a-z -> A-Z
3795 if (islower(*dot)) {
3796 *dot = toupper(*dot);
3797 file_modified++; // has the file been modified
3798 } else if (isupper(*dot)) {
3799 *dot = tolower(*dot);
3800 file_modified++; // has the file been modified
3803 end_cmd_q(); // stop adding to q
3805 //----- The Cursor and Function Keys -----------------------------
3806 case VI_K_HOME: // Cursor Key Home
3809 // The Fn keys could point to do_macro which could translate them
3810 case VI_K_FUN1: // Function Key F1
3811 case VI_K_FUN2: // Function Key F2
3812 case VI_K_FUN3: // Function Key F3
3813 case VI_K_FUN4: // Function Key F4
3814 case VI_K_FUN5: // Function Key F5
3815 case VI_K_FUN6: // Function Key F6
3816 case VI_K_FUN7: // Function Key F7
3817 case VI_K_FUN8: // Function Key F8
3818 case VI_K_FUN9: // Function Key F9
3819 case VI_K_FUN10: // Function Key F10
3820 case VI_K_FUN11: // Function Key F11
3821 case VI_K_FUN12: // Function Key F12
3826 // if text[] just became empty, add back an empty line
3828 (void) char_insert(text, '\n'); // start empty buf with dummy line
3831 // it is OK for dot to exactly equal to end, otherwise check dot validity
3833 dot = bound_dot(dot); // make sure "dot" is valid
3835 #ifdef CONFIG_FEATURE_VI_YANKMARK
3836 check_context(c); // update the current context
3837 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3840 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3841 cnt = dot - begin_line(dot);
3842 // Try to stay off of the Newline
3843 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3847 #ifdef CONFIG_FEATURE_VI_CRASHME
3848 static int totalcmds = 0;
3849 static int Mp = 85; // Movement command Probability
3850 static int Np = 90; // Non-movement command Probability
3851 static int Dp = 96; // Delete command Probability
3852 static int Ip = 97; // Insert command Probability
3853 static int Yp = 98; // Yank command Probability
3854 static int Pp = 99; // Put command Probability
3855 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3856 char chars[20] = "\t012345 abcdABCD-=.$";
3857 char *words[20] = { "this", "is", "a", "test",
3858 "broadcast", "the", "emergency", "of",
3859 "system", "quick", "brown", "fox",
3860 "jumped", "over", "lazy", "dogs",
3861 "back", "January", "Febuary", "March"
3864 "You should have received a copy of the GNU General Public License\n",
3865 "char c, cm, *cmd, *cmd1;\n",
3866 "generate a command by percentages\n",
3867 "Numbers may be typed as a prefix to some commands.\n",
3868 "Quit, discarding changes!\n",
3869 "Forced write, if permission originally not valid.\n",
3870 "In general, any ex or ed command (such as substitute or delete).\n",
3871 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3872 "Please get w/ me and I will go over it with you.\n",
3873 "The following is a list of scheduled, committed changes.\n",
3874 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3875 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3876 "Any question about transactions please contact Sterling Huxley.\n",
3877 "I will try to get back to you by Friday, December 31.\n",
3878 "This Change will be implemented on Friday.\n",
3879 "Let me know if you have problems accessing this;\n",
3880 "Sterling Huxley recently added you to the access list.\n",
3881 "Would you like to go to lunch?\n",
3882 "The last command will be automatically run.\n",
3883 "This is too much english for a computer geek.\n",
3885 char *multilines[20] = {
3886 "You should have received a copy of the GNU General Public License\n",
3887 "char c, cm, *cmd, *cmd1;\n",
3888 "generate a command by percentages\n",
3889 "Numbers may be typed as a prefix to some commands.\n",
3890 "Quit, discarding changes!\n",
3891 "Forced write, if permission originally not valid.\n",
3892 "In general, any ex or ed command (such as substitute or delete).\n",
3893 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3894 "Please get w/ me and I will go over it with you.\n",
3895 "The following is a list of scheduled, committed changes.\n",
3896 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3897 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3898 "Any question about transactions please contact Sterling Huxley.\n",
3899 "I will try to get back to you by Friday, December 31.\n",
3900 "This Change will be implemented on Friday.\n",
3901 "Let me know if you have problems accessing this;\n",
3902 "Sterling Huxley recently added you to the access list.\n",
3903 "Would you like to go to lunch?\n",
3904 "The last command will be automatically run.\n",
3905 "This is too much english for a computer geek.\n",
3908 // create a random command to execute
3909 static void crash_dummy()
3911 static int sleeptime; // how long to pause between commands
3912 char c, cm, *cmd, *cmd1;
3913 int i, cnt, thing, rbi, startrbi, percent;
3915 // "dot" movement commands
3916 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3918 // is there already a command running?
3919 if (readed_for_parse > 0)
3923 sleeptime = 0; // how long to pause between commands
3924 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3925 // generate a command by percentages
3926 percent = (int) lrand48() % 100; // get a number from 0-99
3927 if (percent < Mp) { // Movement commands
3928 // available commands
3931 } else if (percent < Np) { // non-movement commands
3932 cmd = "mz<>\'\""; // available commands
3934 } else if (percent < Dp) { // Delete commands
3935 cmd = "dx"; // available commands
3937 } else if (percent < Ip) { // Inset commands
3938 cmd = "iIaAsrJ"; // available commands
3940 } else if (percent < Yp) { // Yank commands
3941 cmd = "yY"; // available commands
3943 } else if (percent < Pp) { // Put commands
3944 cmd = "pP"; // available commands
3947 // We do not know how to handle this command, try again
3951 // randomly pick one of the available cmds from "cmd[]"
3952 i = (int) lrand48() % strlen(cmd);
3954 if (strchr(":\024", cm))
3955 goto cd0; // dont allow colon or ctrl-T commands
3956 readbuffer[rbi++] = cm; // put cmd into input buffer
3958 // now we have the command-
3959 // there are 1, 2, and multi char commands
3960 // find out which and generate the rest of command as necessary
3961 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3962 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3963 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3964 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3966 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3968 readbuffer[rbi++] = c; // add movement to input buffer
3970 if (strchr("iIaAsc", cm)) { // multi-char commands
3972 // change some thing
3973 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3975 readbuffer[rbi++] = c; // add movement to input buffer
3977 thing = (int) lrand48() % 4; // what thing to insert
3978 cnt = (int) lrand48() % 10; // how many to insert
3979 for (i = 0; i < cnt; i++) {
3980 if (thing == 0) { // insert chars
3981 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3982 } else if (thing == 1) { // insert words
3983 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3984 strcat((char *) readbuffer, " ");
3985 sleeptime = 0; // how fast to type
3986 } else if (thing == 2) { // insert lines
3987 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3988 sleeptime = 0; // how fast to type
3989 } else { // insert multi-lines
3990 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3991 sleeptime = 0; // how fast to type
3994 strcat((char *) readbuffer, "\033");
3996 readed_for_parse = strlen(readbuffer);
4000 (void) mysleep(sleeptime); // sleep 1/100 sec
4003 // test to see if there are any errors
4004 static void crash_test()
4006 static time_t oldtim;
4008 char d[2], msg[BUFSIZ];
4012 strcat((char *) msg, "end<text ");
4014 if (end > textend) {
4015 strcat((char *) msg, "end>textend ");
4018 strcat((char *) msg, "dot<text ");
4021 strcat((char *) msg, "dot>end ");
4023 if (screenbegin < text) {
4024 strcat((char *) msg, "screenbegin<text ");
4026 if (screenbegin > end - 1) {
4027 strcat((char *) msg, "screenbegin>end-1 ");
4030 if (strlen(msg) > 0) {
4032 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4033 totalcmds, last_input_char, msg, SOs, SOn);
4035 while (read(0, d, 1) > 0) {
4036 if (d[0] == '\n' || d[0] == '\r')
4041 tim = (time_t) time((time_t *) 0);
4042 if (tim >= (oldtim + 3)) {
4043 sprintf((char *) status_buffer,
4044 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4045 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4050 #endif /* CONFIG_FEATURE_VI_CRASHME */