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.
22 "$Id: vi.c,v 1.2 2001/04/04 19:29:48 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -s -DSTANDALONE -DCRASHME -o vi vi.c # include testing features
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
44 //---- Feature -------------- Bytes to immplement
47 #define BB_FEATURE_VI_COLON // 4288
48 #define BB_FEATURE_VI_YANKMARK // 1408
49 #define BB_FEATURE_VI_SEARCH // 1088
50 #define BB_FEATURE_VI_USE_SIGNALS // 1056
51 #define BB_FEATURE_VI_DOT_CMD // 576
52 #define BB_FEATURE_VI_READONLY // 128
53 #define BB_FEATURE_VI_SETOPTS // 576
54 #define BB_FEATURE_VI_SET // 224
55 #define BB_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
56 // To test editor using CRASHME:
58 // To stop testing, wait until all to text[] is deleted, or
59 // Ctrl-Z and kill -9 %1
60 // while in the editor Ctrl-T will toggle the crashme function on and off.
61 //#define BB_FEATURE_VI_CRASHME // randomly pick commands to execute
62 #endif /* STANDALONE */
66 #endif /* STANDALONE */
72 #include <sys/ioctl.h>
74 #include <sys/types.h>
88 #define FALSE ((int)0)
90 #define MAX_SCR_COLS 300
92 // Misc. non-Ascii keys that report an escape sequence
93 #define VI_K_UP 128 // cursor key Up
94 #define VI_K_DOWN 129 // cursor key Down
95 #define VI_K_RIGHT 130 // Cursor Key Right
96 #define VI_K_LEFT 131 // cursor key Left
97 #define VI_K_HOME 132 // Cursor Key Home
98 #define VI_K_END 133 // Cursor Key End
99 #define VI_K_INSERT 134 // Cursor Key Insert
100 #define VI_K_PAGEUP 135 // Cursor Key Page Up
101 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
102 #define VI_K_FUN1 137 // Function Key F1
103 #define VI_K_FUN2 138 // Function Key F2
104 #define VI_K_FUN3 139 // Function Key F3
105 #define VI_K_FUN4 140 // Function Key F4
106 #define VI_K_FUN5 141 // Function Key F5
107 #define VI_K_FUN6 142 // Function Key F6
108 #define VI_K_FUN7 143 // Function Key F7
109 #define VI_K_FUN8 144 // Function Key F8
110 #define VI_K_FUN9 145 // Function Key F9
111 #define VI_K_FUN10 146 // Function Key F10
112 #define VI_K_FUN11 147 // Function Key F11
113 #define VI_K_FUN12 148 // Function Key F12
115 static const int YANKONLY = FALSE;
116 static const int YANKDEL = TRUE;
117 static const int FORWARD = 1; // code depends on "1" for array index
118 static const int BACK = -1; // code depends on "-1" for array index
119 static const int LIMITED = 0; // how much of text[] in char_search
120 static const int FULL = 1; // how much of text[] in char_search
122 static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
123 static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
124 static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
125 static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
126 static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
128 typedef unsigned char Byte;
131 static int editing; // >0 while we are editing a file
132 static int cmd_mode; // 0=command 1=insert
133 static int file_modified; // buffer contents changed
134 static int err_method; // indicate error with beep or flash
135 static int fn_start; // index of first cmd line file name
136 static int save_argc; // how many file names on cmd line
137 static int cmdcnt; // repetition count
138 static fd_set rfds; // use select() for small sleeps
139 static struct timeval tv; // use select() for small sleeps
140 static char erase_char; // the users erase character
141 static int rows, columns; // the terminal screen is this size
142 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
143 static char *SOs, *SOn;
144 static Byte *status_buffer; // mesages to the user
145 static Byte last_input_char; // last char read from user
146 static Byte last_forward_char; // last char searched for with 'f'
147 static Byte *cfn; // previous, current, and next file name
148 static Byte *text, *end, *textend; // pointers to the user data in memory
149 static Byte *screen; // pointer to the virtual screen buffer
150 static int screensize; // and its size
151 static Byte *screenbegin; // index into text[], of top line on the screen
152 static Byte *dot; // where all the action takes place
154 static struct termios term_orig, term_vi; // remember what the cooked mode was
156 #ifdef BB_FEATURE_VI_USE_SIGNALS
157 static jmp_buf restart; // catch_sig()
158 #endif /* BB_FEATURE_VI_USE_SIGNALS */
159 #ifdef BB_FEATURE_VI_WIN_RESIZE
160 static struct winsize winsize; // remember the window size
161 #endif /* BB_FEATURE_VI_WIN_RESIZE */
162 #ifdef BB_FEATURE_VI_DOT_CMD
163 static int adding2q; // are we currently adding user input to q
164 static Byte *last_modifying_cmd; // last modifying cmd for "."
165 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
166 #endif /* BB_FEATURE_VI_DOT_CMD */
167 #if defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
168 static Byte *modifying_cmds; // cmds that modify text[]
169 #endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
170 #ifdef BB_FEATURE_VI_READONLY
172 #endif /* BB_FEATURE_VI_READONLY */
173 #ifdef BB_FEATURE_VI_SETOPTS
174 static int autoindent;
175 static int showmatch;
176 static int ignorecase;
177 #endif /* BB_FEATURE_VI_SETOPTS */
178 #ifdef BB_FEATURE_VI_YANKMARK
179 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
180 static int YDreg, Ureg; // default delete register and orig line for "U"
181 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
182 static Byte *context_start, *context_end;
183 #endif /* BB_FEATURE_VI_YANKMARK */
184 #ifdef BB_FEATURE_VI_SEARCH
185 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
186 #endif /* BB_FEATURE_VI_SEARCH */
189 static void edit_file(Byte *); // edit one file
190 static void do_cmd(Byte); // execute a command
191 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
192 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
193 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
194 static Byte *dollar_line(Byte *); // return pointer to just before NL
195 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
196 static Byte *next_line(Byte *); // return pointer to next line B-o-l
197 static Byte *end_screen(void); // get pointer to last char on screen
198 static int count_lines(Byte *, Byte *); // count line from start to stop
199 static Byte *find_line(int); // find begining of line #li
200 static Byte *move_to_col(Byte *, int); // move "p" to column l
201 static int isblnk(Byte); // is the char a blank or tab
202 static void dot_left(void); // move dot left- dont leave line
203 static void dot_right(void); // move dot right- dont leave line
204 static void dot_begin(void); // move dot to B-o-l
205 static void dot_end(void); // move dot to E-o-l
206 static void dot_next(void); // move dot to next line B-o-l
207 static void dot_prev(void); // move dot to prev line B-o-l
208 static void dot_scroll(int, int); // move the screen up or down
209 static void dot_skip_over_ws(void); // move dot pat WS
210 static void dot_delete(void); // delete the char at 'dot'
211 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
212 static Byte *new_screen(int, int); // malloc virtual screen memory
213 static Byte *new_text(int); // malloc memory for text[] buffer
214 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
215 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
216 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
217 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
218 static Byte *skip_thing(Byte *, int, int, int); // skip some object
219 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
220 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
221 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
222 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
223 static void show_help(void); // display some help info
224 static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
225 static void rawmode(void); // set "raw" mode on tty
226 static void cookmode(void); // return to "cooked" mode on tty
227 static int mysleep(int); // sleep for 'h' 1/100 seconds
228 static Byte readit(void); // read (maybe cursor) key from stdin
229 static Byte get_one_char(void); // read 1 char from stdin
230 static int file_size(Byte *); // what is the byte size of "fn"
231 static int file_insert(Byte *, Byte *, int);
232 static int file_write(Byte *, Byte *, Byte *);
233 static void place_cursor(int, int);
234 static void screen_erase();
235 static void clear_to_eol(void);
236 static void clear_to_eos(void);
237 static void standout_start(void); // send "start reverse video" sequence
238 static void standout_end(void); // send "end reverse video" sequence
239 static void flash(int); // flash the terminal screen
240 static void beep(void); // beep the terminal
241 static void indicate_error(char); // use flash or beep to indicate error
242 static void show_status_line(void); // put a message on the bottom line
243 static void psb(char *, ...); // Print Status Buf
244 static void psbs(char *, ...); // Print Status Buf in standout mode
245 static void ni(Byte *); // display messages
246 static void edit_status(void); // show file status on status line
247 static void redraw(int); // force a full screen refresh
248 static void refresh(int); // update the terminal from screen[]
250 #ifdef BB_FEATURE_VI_SEARCH
251 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
252 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
253 #endif /* BB_FEATURE_VI_SEARCH */
254 #ifdef BB_FEATURE_VI_COLON
255 static void Hit_Return(void);
256 static Byte *get_address(Byte *, int *); // get colon addr, if present
257 static void colon(Byte *); // execute the "colon" mode cmds
258 #endif /* BB_FEATURE_VI_COLON */
259 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
260 static Byte *get_input_line(Byte *); // get input line- use "status line"
261 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
262 #ifdef BB_FEATURE_VI_USE_SIGNALS
263 static void winch_sig(int); // catch window size changes
264 static void suspend_sig(int); // catch ctrl-Z
265 static void alarm_sig(int); // catch alarm time-outs
266 static void catch_sig(int); // catch ctrl-C
267 static void core_sig(int); // catch a core dump signal
268 #endif /* BB_FEATURE_VI_USE_SIGNALS */
269 #ifdef BB_FEATURE_VI_DOT_CMD
270 static void start_new_cmd_q(Byte); // new queue for command
271 static void end_cmd_q(); // stop saving input chars
272 #else /* BB_FEATURE_VI_DOT_CMD */
274 #endif /* BB_FEATURE_VI_DOT_CMD */
275 #ifdef BB_FEATURE_VI_WIN_RESIZE
276 static void window_size_get(int); // find out what size the window is
277 #endif /* BB_FEATURE_VI_WIN_RESIZE */
278 #ifdef BB_FEATURE_VI_SETOPTS
279 static void showmatching(Byte *); // show the matching pair () [] {}
280 #endif /* BB_FEATURE_VI_SETOPTS */
281 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
282 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
283 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
284 #ifdef BB_FEATURE_VI_YANKMARK
285 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
286 static Byte what_reg(void); // what is letter of current YDreg
287 static void check_context(Byte); // remember context for '' command
288 static Byte *swap_context(Byte *); // goto new context for '' command
289 #endif /* BB_FEATURE_VI_YANKMARK */
290 #ifdef BB_FEATURE_VI_CRASHME
291 static void crash_dummy();
292 static void crash_test();
293 static int crashme = 0;
294 #endif /* BB_FEATURE_VI_CRASHME */
297 extern int vi_main(int argc, char **argv)
302 #ifdef BB_FEATURE_VI_YANKMARK
304 #endif /* BB_FEATURE_VI_YANKMARK */
306 SOs = "\033[7m"; // Terminal standout mode on
307 SOn = "\033[0m"; // Terminal standout mode off
308 #ifdef BB_FEATURE_VI_CRASHME
309 (void) srand((long) getpid());
310 #endif /* BB_FEATURE_VI_CRASHME */
311 status_buffer = (Byte *) malloc(200); // hold messages to user
312 #ifdef BB_FEATURE_VI_READONLY
314 if (strncmp(argv[0], "view", 4) == 0) {
317 #endif /* BB_FEATURE_VI_READONLY */
318 #ifdef BB_FEATURE_VI_SETOPTS
322 #endif /* BB_FEATURE_VI_SETOPTS */
323 #ifdef BB_FEATURE_VI_YANKMARK
324 for (i = 0; i < 28; i++) {
326 } // init the yank regs
327 #endif /* BB_FEATURE_VI_YANKMARK */
328 #ifdef BB_FEATURE_VI_DOT_CMD
329 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
330 #endif /* BB_FEATURE_VI_DOT_CMD */
332 // 1- process $HOME/.exrc file
333 // 2- process EXINIT variable from environment
334 // 3- process command line args
335 while ((c = getopt(argc, argv, "hCR")) != -1) {
337 #ifdef BB_FEATURE_VI_CRASHME
341 #endif /* BB_FEATURE_VI_CRASHME */
342 #ifdef BB_FEATURE_VI_READONLY
343 case 'R': // Read-only flag
346 #endif /* BB_FEATURE_VI_READONLY */
347 //case 'r': // recover flag- ignore- we don't use tmp file
348 //case 'x': // encryption flag- ignore
349 //case 'c': // execute command first
350 //case 'h': // help -- just use default
357 // The argv array can be used by the ":next" and ":rewind" commands
359 fn_start = optind; // remember first file name for :next and :rew
362 //----- This is the main file handling loop --------------
363 if (optind >= argc) {
364 editing = 1; // 0= exit, 1= one file, 2= multiple files
367 for (; optind < argc; optind++) {
368 editing = 1; // 0=exit, 1=one file, 2+ =many files
371 cfn = (Byte *) strdup(argv[optind]);
375 //-----------------------------------------------------------
380 static void edit_file(Byte * fn)
385 #ifdef BB_FEATURE_VI_USE_SIGNALS
388 #endif /* BB_FEATURE_VI_USE_SIGNALS */
389 #ifdef BB_FEATURE_VI_YANKMARK
390 static Byte *cur_line;
391 #endif /* BB_FEATURE_VI_YANKMARK */
396 #ifdef BB_FEATURE_VI_WIN_RESIZE
398 #endif /* BB_FEATURE_VI_WIN_RESIZE */
399 new_screen(rows, columns); // get memory for virtual screen
401 cnt = file_size(fn); // file size
402 size = 2 * cnt; // 200% of file size
403 new_text(size); // get a text[] buffer
404 screenbegin = dot = end = text;
406 file_insert(fn, text, cnt);
408 (void) char_insert(text, '\n'); // start empty buf with dummy line
410 file_modified = FALSE;
411 #ifdef BB_FEATURE_VI_YANKMARK
412 YDreg = 26; // default Yank/Delete reg
413 Ureg = 27; // hold orig line for "U" cmd
414 for (cnt = 0; cnt < 28; cnt++) {
417 mark[26] = mark[27] = text; // init "previous context"
418 #endif /* BB_FEATURE_VI_YANKMARK */
420 err_method = 1; // flash
421 last_forward_char = last_input_char = '\0';
426 #ifdef BB_FEATURE_VI_USE_SIGNALS
427 signal(SIGHUP, catch_sig);
428 signal(SIGINT, catch_sig);
429 signal(SIGALRM, alarm_sig);
430 signal(SIGTERM, catch_sig);
431 signal(SIGQUIT, core_sig);
432 signal(SIGILL, core_sig);
433 signal(SIGTRAP, core_sig);
434 signal(SIGIOT, core_sig);
435 signal(SIGABRT, core_sig);
436 signal(SIGFPE, core_sig);
437 signal(SIGBUS, core_sig);
438 signal(SIGSEGV, core_sig);
440 signal(SIGSYS, core_sig);
442 signal(SIGWINCH, winch_sig);
443 signal(SIGTSTP, suspend_sig);
444 sig = setjmp(restart);
448 msg = "(window resize)";
458 msg = "(I tried to touch invalid memory)";
462 psbs("-- caught signal %d %s--", sig, msg);
464 #endif /* BB_FEATURE_VI_USE_SIGNALS */
467 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
470 offset = 0; // no horizontal offset
472 #ifdef BB_FEATURE_VI_DOT_CMD
473 if (last_modifying_cmd != 0)
474 free(last_modifying_cmd);
475 if (ioq_start != NULL)
477 ioq = ioq_start = last_modifying_cmd = 0;
479 #endif /* BB_FEATURE_VI_DOT_CMD */
483 //------This is the main Vi cmd handling loop -----------------------
484 while (editing > 0) {
485 #ifdef BB_FEATURE_VI_CRASHME
487 if ((end - text) > 1) {
488 crash_dummy(); // generate a random command
492 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
496 #endif /* BB_FEATURE_VI_CRASHME */
497 last_input_char = c = get_one_char(); // get a cmd from user
498 #ifdef BB_FEATURE_VI_YANKMARK
499 // save a copy of the current line- for the 'U" command
500 if (begin_line(dot) != cur_line) {
501 cur_line = begin_line(dot);
502 text_yank(begin_line(dot), end_line(dot), Ureg);
504 #endif /* BB_FEATURE_VI_YANKMARK */
505 #ifdef BB_FEATURE_VI_DOT_CMD
506 // These are commands that change text[].
507 // Remember the input for the "." command
508 if (!adding2q && ioq_start == 0
509 && strchr((char *) modifying_cmds, c) != NULL) {
512 #endif /* BB_FEATURE_VI_DOT_CMD */
513 do_cmd(c); // execute the user command
515 // poll to see if there is input already waiting. if we are
516 // not able to display output fast enough to keep up, skip
517 // the display update until we catch up with input.
518 if (mysleep(0) == 0) {
519 // no input pending- so update output
523 #ifdef BB_FEATURE_VI_CRASHME
525 crash_test(); // test editor variables
526 #endif /* BB_FEATURE_VI_CRASHME */
528 //-------------------------------------------------------------------
530 place_cursor(rows, 0); // go to bottom of screen
531 clear_to_eol(); // Erase to end of line
535 static Byte readbuffer[BUFSIZ];
537 #ifdef BB_FEATURE_VI_CRASHME
538 static int totalcmds = 0;
539 static int Mp = 85; // Movement command Probability
540 static int Np = 90; // Non-movement command Probability
541 static int Dp = 96; // Delete command Probability
542 static int Ip = 97; // Insert command Probability
543 static int Yp = 98; // Yank command Probability
544 static int Pp = 99; // Put command Probability
545 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
546 char chars[20] = "\t012345 abcdABCD-=.$";
547 char *words[20] = { "this", "is", "a", "test",
548 "broadcast", "the", "emergency", "of",
549 "system", "quick", "brown", "fox",
550 "jumped", "over", "lazy", "dogs",
551 "back", "January", "Febuary", "March"
554 "You should have received a copy of the GNU General Public License\n",
555 "char c, cm, *cmd, *cmd1;\n",
556 "generate a command by percentages\n",
557 "Numbers may be typed as a prefix to some commands.\n",
558 "Quit, discarding changes!\n",
559 "Forced write, if permission originally not valid.\n",
560 "In general, any ex or ed command (such as substitute or delete).\n",
561 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
562 "Please get w/ me and I will go over it with you.\n",
563 "The following is a list of scheduled, committed changes.\n",
564 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
565 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
566 "Any question about transactions please contact Sterling Huxley.\n",
567 "I will try to get back to you by Friday, December 31.\n",
568 "This Change will be implemented on Friday.\n",
569 "Let me know if you have problems accessing this;\n",
570 "Sterling Huxley recently added you to the access list.\n",
571 "Would you like to go to lunch?\n",
572 "The last command will be automatically run.\n",
573 "This is too much english for a computer geek.\n",
575 char *multilines[20] = {
576 "You should have received a copy of the GNU General Public License\n",
577 "char c, cm, *cmd, *cmd1;\n",
578 "generate a command by percentages\n",
579 "Numbers may be typed as a prefix to some commands.\n",
580 "Quit, discarding changes!\n",
581 "Forced write, if permission originally not valid.\n",
582 "In general, any ex or ed command (such as substitute or delete).\n",
583 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
584 "Please get w/ me and I will go over it with you.\n",
585 "The following is a list of scheduled, committed changes.\n",
586 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
587 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
588 "Any question about transactions please contact Sterling Huxley.\n",
589 "I will try to get back to you by Friday, December 31.\n",
590 "This Change will be implemented on Friday.\n",
591 "Let me know if you have problems accessing this;\n",
592 "Sterling Huxley recently added you to the access list.\n",
593 "Would you like to go to lunch?\n",
594 "The last command will be automatically run.\n",
595 "This is too much english for a computer geek.\n",
598 // create a random command to execute
599 static void crash_dummy()
601 static int sleeptime; // how long to pause between commands
602 char c, cm, *cmd, *cmd1;
603 int i, cnt, thing, rbi, startrbi, percent;
605 // "dot" movement commands
606 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
608 // is there already a command running?
609 if (strlen((char *) readbuffer) > 0)
613 sleeptime = 0; // how long to pause between commands
614 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
615 // generate a command by percentages
616 percent = (int) lrand48() % 100; // get a number from 0-99
617 if (percent < Mp) { // Movement commands
618 // available commands
621 } else if (percent < Np) { // non-movement commands
622 cmd = "mz<>\'\""; // available commands
624 } else if (percent < Dp) { // Delete commands
625 cmd = "dx"; // available commands
627 } else if (percent < Ip) { // Inset commands
628 cmd = "iIaAsrJ"; // available commands
630 } else if (percent < Yp) { // Yank commands
631 cmd = "yY"; // available commands
633 } else if (percent < Pp) { // Put commands
634 cmd = "pP"; // available commands
637 // We do not know how to handle this command, try again
641 // randomly pick one of the available cmds from "cmd[]"
642 i = (int) lrand48() % strlen(cmd);
644 if (strchr(":\024", cm))
645 goto cd0; // dont allow these commands
646 readbuffer[rbi++] = cm; // put cmd into input buffer
648 // now we have the command-
649 // there are 1, 2, and multi char commands
650 // find out which and generate the rest of command as necessary
651 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
652 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
653 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
654 cmd1 = "abcdefghijklmnopqrstuvwxyz";
656 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
658 readbuffer[rbi++] = c; // add movement to input buffer
660 if (strchr("iIaAsc", cm)) { // multi-char commands
663 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
665 readbuffer[rbi++] = c; // add movement to input buffer
667 thing = (int) lrand48() % 4; // what thing to insert
668 cnt = (int) lrand48() % 10; // how many to insert
669 for (i = 0; i < cnt; i++) {
670 if (thing == 0) { // insert chars
671 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
672 } else if (thing == 1) { // insert words
673 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
674 strcat((char *) readbuffer, " ");
675 sleeptime = 0; // how fast to type
676 } else if (thing == 2) { // insert lines
677 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
678 sleeptime = 0; // how fast to type
679 } else { // insert multi-lines
680 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
681 sleeptime = 0; // how fast to type
684 strcat((char *) readbuffer, "\033");
689 (void) mysleep(sleeptime); // sleep 1/100 sec
692 // test to see if there are any errors
693 static void crash_test()
695 static time_t oldtim;
697 char d[2], buf[100], msg[BUFSIZ];
701 strcat((char *) msg, "end<text ");
704 strcat((char *) msg, "end>textend ");
707 strcat((char *) msg, "dot<text ");
710 strcat((char *) msg, "dot>end ");
712 if (screenbegin < text) {
713 strcat((char *) msg, "screenbegin<text ");
715 if (screenbegin > end - 1) {
716 strcat((char *) msg, "screenbegin>end-1 ");
719 if (strlen(msg) > 0) {
721 sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
722 write(1, buf, strlen(buf));
723 write(1, msg, strlen(msg));
724 write(1, "\n\n\n", 3);
725 write(1, "\033[7m[Hit return to continue]\033[0m", 32);
726 while (read(0, d, 1) > 0) {
727 if (d[0] == '\n' || d[0] == '\r')
732 tim = (time_t) time((time_t *) 0);
733 if (tim >= (oldtim + 3)) {
734 sprintf((char *) status_buffer,
735 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
736 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
741 #endif /* BB_FEATURE_VI_CRASHME */
743 //---------------------------------------------------------------------
744 //----- the Ascii Chart -----------------------------------------------
746 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
747 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
748 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
749 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
750 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
751 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
752 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
753 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
754 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
755 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
756 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
757 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
758 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
759 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
760 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
761 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
762 //---------------------------------------------------------------------
764 //----- Execute a Vi Command -----------------------------------
765 static void do_cmd(Byte c)
767 Byte c1, *p, *q, *msg, buf[9], *save_dot;
768 int cnt, i, j, dir, yf;
770 c1 = c; // quiet the compiler
771 cnt = yf = dir = 0; // quiet the compiler
772 p = q = save_dot = msg = buf; // quiet the compiler
773 memset(buf, '\0', 9); // clear buf
775 // we are 'R'eplacing the current *dot with new char
777 // don't Replace past E-o-l
778 cmd_mode = 1; // convert to insert
780 if (1 <= c && c <= 127) { // only ASCII chars
782 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
783 dot = char_insert(dot, c); // insert new char
789 // insert the char c at "dot"
790 if (1 <= c && c <= 127) {
791 dot = char_insert(dot, c); // only ASCII chars
806 case 0x14: // dc4 ctrl-T
807 #ifdef BB_FEATURE_VI_CRASHME
808 crashme = (crashme == 0) ? 1 : 0;
809 #endif /* BB_FEATURE_VI_CRASHME */
844 default: // unrecognised command
853 end_cmd_q(); // stop adding to q
854 case 0x00: // nul- ignore
856 case 2: // ctrl-B scroll up full screen
857 case VI_K_PAGEUP: // Cursor Key Page Up
858 dot_scroll(rows - 2, -1);
860 #ifdef BB_FEATURE_VI_USE_SIGNALS
861 case 0x03: // ctrl-C interrupt
864 case 26: // ctrl-Z suspend
865 suspend_sig(SIGTSTP);
867 #endif /* BB_FEATURE_VI_USE_SIGNALS */
868 case 4: // ctrl-D scroll down half screen
869 dot_scroll((rows - 2) / 2, 1);
871 case 5: // ctrl-E scroll down one line
874 case 6: // ctrl-F scroll down full screen
875 case VI_K_PAGEDOWN: // Cursor Key Page Down
876 dot_scroll(rows - 2, 1);
878 case 7: // ctrl-G show current status
881 case 'h': // h- move left
882 case VI_K_LEFT: // cursor key Left
883 case 8: // ^h- move left (This may be ERASE char)
884 case 127: // DEL- move left (This may be ERASE char)
890 case 10: // Newline ^J
891 case 'j': // j- goto next line, same col
892 case VI_K_DOWN: // cursor key Down
896 dot_next(); // go to next B-o-l
897 dot = move_to_col(dot, ccol + offset); // try stay in same col
899 case 12: // ctrl-L force redraw whole screen
900 place_cursor(0, 0); // put cursor in correct place
901 clear_to_eos(); // tel terminal to erase display
903 screen_erase(); // erase the internal screen buffer
904 refresh(TRUE); // this will redraw the entire display
906 case 13: // Carriage Return ^M
907 case '+': // +- goto next line
914 case 21: // ctrl-U scroll up half screen
915 dot_scroll((rows - 2) / 2, -1);
917 case 25: // ctrl-Y scroll up one line
923 cmd_mode = 0; // stop insrting
925 *status_buffer = '\0'; // clear status buffer
927 case ' ': // move right
928 case 'l': // move right
929 case VI_K_RIGHT: // Cursor Key Right
935 #ifdef BB_FEATURE_VI_YANKMARK
936 case '"': // "- name a register to use for Delete/Yank
945 case '\'': // '- goto a specific mark
952 if (text <= q && q < end) {
954 dot_begin(); // go to B-o-l
957 } else if (c1 == '\'') { // goto previous context
958 dot = swap_context(dot); // swap current and previous context
959 dot_begin(); // go to B-o-l
965 case 'm': // m- Mark a line
966 // this is really stupid. If there are any inserts or deletes
967 // between text[0] and dot then this mark will not point to the
968 // correct location! It could be off by many lines!
969 // Well..., at least its quick and dirty.
975 mark[(int) c1] = dot;
980 case 'P': // P- Put register before
981 case 'p': // p- put register after
984 psbs("Nothing in register %c", what_reg());
987 // are we putting whole lines or strings
988 if (strchr((char *) p, '\n') != NULL) {
990 dot_begin(); // putting lines- Put above
993 // are we putting after very last line?
994 if (end_line(dot) == (end - 1)) {
995 dot = end; // force dot to end of text[]
997 dot_next(); // next line, then put before
1002 dot_right(); // move to right, can move to NL
1004 dot = string_insert(dot, p); // insert the string
1005 end_cmd_q(); // stop adding to q
1008 case 'U': // U- Undo; replace current line with original version
1009 if (reg[Ureg] != 0) {
1010 p = begin_line(dot);
1012 p = text_hole_delete(p, q); // delete cur line
1013 p = string_insert(p, reg[Ureg]); // insert orig line
1018 #endif /* BB_FEATURE_VI_YANKMARK */
1019 case '$': // $- goto end of line
1020 case VI_K_END: // Cursor Key End
1024 dot = end_line(dot + 1);
1026 case '%': // %- find matching char of pair () [] {}
1027 for (q = dot; q < end && *q != '\n'; q++) {
1028 if (strchr("()[]{}", *q) != NULL) {
1029 // we found half of a pair
1030 p = find_pair(q, *q);
1042 case 'f': // f- forward to a user specified char
1043 last_forward_char = get_one_char(); // get the search char
1045 // dont seperate these two commands. 'f' depends on ';'
1047 //**** fall thru to ... 'i'
1048 case ';': // ;- look at rest of line for last forward char
1053 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1056 if (*q == last_forward_char)
1059 case '-': // -- goto prev line
1066 #ifdef BB_FEATURE_VI_DOT_CMD
1067 case '.': // .- repeat the last modifying command
1068 // Stuff the last_modifying_cmd back into stdin
1069 // and let it be re-executed.
1070 if (last_modifying_cmd != 0) {
1071 ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
1074 #endif /* BB_FEATURE_VI_DOT_CMD */
1075 #ifdef BB_FEATURE_VI_SEARCH
1076 case '?': // /- search for a pattern
1077 case '/': // /- search for a pattern
1080 q = get_input_line(buf); // get input line- use "status line"
1081 if (strlen((char *) q) == 1)
1082 goto dc3; // if no pat re-use old pat
1083 if (strlen((char *) q) > 1) { // new pat- save it and find
1084 // there is a new pat
1085 if (last_search_pattern != 0) {
1086 free(last_search_pattern);
1088 last_search_pattern = (Byte *) strdup((char *) q);
1089 goto dc3; // now find the pattern
1091 // user changed mind and erased the "/"- do nothing
1093 case 'N': // N- backward search for last pattern
1097 dir = BACK; // assume BACKWARD search
1099 if (last_search_pattern[0] == '?') {
1103 goto dc4; // now search for pattern
1105 case 'n': // n- repeat search for last pattern
1106 // search rest of text[] starting at next char
1107 // if search fails return orignal "p" not the "p+1" address
1112 if (last_search_pattern == 0) {
1113 msg = (Byte *) "No previous regular expression";
1116 if (last_search_pattern[0] == '/') {
1117 dir = FORWARD; // assume FORWARD search
1120 if (last_search_pattern[0] == '?') {
1125 q = char_search(p, last_search_pattern + 1, dir, FULL);
1127 dot = q; // good search, update "dot"
1131 // no pattern found between "dot" and "end"- continue at top
1136 q = char_search(p, last_search_pattern + 1, dir, FULL);
1137 if (q != NULL) { // found something
1138 dot = q; // found new pattern- goto it
1139 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1141 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1144 msg = (Byte *) "Pattern not found";
1149 case '{': // {- move backward paragraph
1150 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1151 if (q != NULL) { // found blank line
1152 dot = next_line(q); // move to next blank line
1155 case '}': // }- move forward paragraph
1156 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1157 if (q != NULL) { // found blank line
1158 dot = next_line(q); // move to next blank line
1161 #endif /* BB_FEATURE_VI_SEARCH */
1162 case '0': // 0- goto begining of line
1172 if (c == '0' && cmdcnt < 1) {
1173 dot_begin(); // this was a standalone zero
1175 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1178 case ':': // :- the colon mode commands
1179 #ifdef BB_FEATURE_VI_COLON
1180 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1181 colon(p); // execute the command
1182 #else /* BB_FEATURE_VI_COLON */
1183 *status_buffer = '\0'; // clear the status buffer
1184 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1185 clear_to_eol(); // clear the line
1186 write(1, ":", 1); // write out the : prompt
1187 for (cnt = 0; cnt < 8; cnt++) {
1188 c1 = get_one_char();
1189 if (c1 == '\n' || c1 == '\r') {
1193 buf[cnt + 1] = '\0';
1194 write(1, buf + cnt, 1); // echo the char
1196 cnt = strlen((char *) buf);
1197 if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
1198 strncasecmp((char *) buf, "q!", cnt) == 0) { // delete lines
1199 if (file_modified == TRUE && buf[1] != '!') {
1200 psbs("No write since last change (:quit! overrides)");
1204 } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
1205 strncasecmp((char *) buf, "wq", cnt) == 0) {
1206 cnt = file_write(cfn, text, end - 1);
1207 file_modified = FALSE;
1208 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1209 if (buf[1] == 'q') {
1212 } else { // unrecognised cmd
1215 #endif /* BB_FEATURE_VI_COLON */
1217 case '<': // <- Left shift something
1218 case '>': // >- Right shift something
1219 cnt = count_lines(text, dot); // remember what line we are on
1220 c1 = get_one_char(); // get the type of thing to delete
1221 find_range(&p, &q, c1);
1222 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1225 i = count_lines(p, q); // # of lines we are shifting
1226 for ( ; i > 0; i--, p = next_line(p)) {
1228 // shift left- remove tab or 8 spaces
1230 // shrink buffer 1 char
1231 (void) text_hole_delete(p, p);
1232 } else if (*p == ' ') {
1233 // we should be calculating columns, not just SPACE
1234 for (j = 0; *p == ' ' && j < tabstop; j++) {
1235 (void) text_hole_delete(p, p);
1238 } else if (c == '>') {
1239 // shift right -- add tab or 8 spaces
1240 (void) char_insert(p, '\t');
1243 dot = find_line(cnt); // what line were we on
1245 end_cmd_q(); // stop adding to q
1247 case 'A': // A- append at e-o-l
1248 dot_end(); // go to e-o-l
1249 //**** fall thru to ... 'a'
1250 case 'a': // a- append after current char
1255 case 'B': // B- back a blank-delimited Word
1256 case 'E': // E- end of a blank-delimited word
1257 case 'W': // W- forward a blank-delimited word
1264 if (c == 'W' || isspace(dot[dir])) {
1265 dot = skip_thing(dot, 1, dir, S_TO_WS);
1266 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1269 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1271 case 'C': // C- Change to e-o-l
1272 case 'D': // D- delete to e-o-l
1274 dot = dollar_line(dot); // move to before NL
1275 // copy text into a register and delete
1276 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1278 goto dc_i; // start inserting
1279 #ifdef BB_FEATURE_VI_DOT_CMD
1281 end_cmd_q(); // stop adding to q
1282 #endif /* BB_FEATURE_VI_DOT_CMD */
1284 case 'H': // H- goto top line on screen
1286 if (cmdcnt > (rows - 1)) {
1287 cmdcnt = (rows - 1);
1294 case 'I': // I- insert before first non-blank
1297 //**** fall thru to ... 'i'
1298 case 'i': // i- insert before current char
1299 case VI_K_INSERT: // Cursor Key Insert
1301 cmd_mode = 1; // start insrting
1302 psb("-- Insert --");
1304 case 'J': // J- join current and next lines together
1308 dot_end(); // move to NL
1309 if (dot < end - 1) { // make sure not last char in text[]
1310 *dot++ = ' '; // replace NL with space
1311 while (isblnk(*dot)) { // delete leading WS
1315 end_cmd_q(); // stop adding to q
1317 case 'L': // L- goto bottom line on screen
1319 if (cmdcnt > (rows - 1)) {
1320 cmdcnt = (rows - 1);
1328 case 'O': // O- open a empty line above
1330 p = begin_line(dot);
1331 if (p[-1] == '\n') {
1333 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1335 dot = char_insert(dot, '\n');
1338 dot = char_insert(dot, '\n'); // i\n\033
1343 case 'R': // R- continuous Replace char
1345 psb("-- Replace --");
1347 case 'X': // X- delete char before dot
1348 case 'x': // x- delete the current char
1349 case 's': // s- substitute the current char
1356 if (dot[dir] != '\n') {
1358 dot--; // delete prev char
1359 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1362 goto dc_i; // start insrting
1363 end_cmd_q(); // stop adding to q
1365 case 'Z': // Z- if modified, {write}; exit
1366 // ZZ means to save file (if necessary), then exit
1367 c1 = get_one_char();
1372 if (file_modified == TRUE
1373 #ifdef BB_FEATURE_VI_READONLY
1374 && readonly == FALSE
1375 #endif /* BB_FEATURE_VI_READONLY */
1377 cnt = file_write(cfn, text, end - 1);
1378 if (cnt == (end - 1 - text + 1)) {
1385 case '^': // ^- move to first non-blank on line
1389 case 'b': // b- back a word
1390 case 'e': // e- end of word
1397 if ((dot + dir) < text || (dot + dir) > end - 1)
1400 if (isspace(*dot)) {
1401 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1403 if (isalnum(*dot) || *dot == '_') {
1404 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1405 } else if (ispunct(*dot)) {
1406 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1409 case 'c': // c- change something
1410 case 'd': // d- delete something
1411 #ifdef BB_FEATURE_VI_YANKMARK
1412 case 'y': // y- yank something
1413 case 'Y': // Y- Yank a line
1414 #endif /* BB_FEATURE_VI_YANKMARK */
1415 yf = YANKDEL; // assume either "c" or "d"
1416 #ifdef BB_FEATURE_VI_YANKMARK
1417 if (c == 'y' || c == 'Y')
1419 #endif /* BB_FEATURE_VI_YANKMARK */
1422 c1 = get_one_char(); // get the type of thing to delete
1423 find_range(&p, &q, c1);
1424 if (c1 == 27) { // ESC- user changed mind and wants out
1425 c = c1 = 27; // Escape- do nothing
1426 } else if (strchr("wW", c1)) {
1428 // don't include trailing WS as part of word
1429 while (isblnk(*q)) {
1430 if (q <= text || q[-1] == '\n')
1435 dot = yank_delete(p, q, 0, yf); // delete word
1436 } else if (strchr("0bBeE$", c1)) {
1437 // single line copy text into a register and delete
1438 dot = yank_delete(p, q, 0, yf); // delete word
1439 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
1440 // multiple line copy text into a register and delete
1441 dot = yank_delete(p, q, 1, yf); // delete lines
1447 // could not recognize object
1448 c = c1 = 27; // error-
1452 // if CHANGING, not deleting, start inserting after the delete
1454 strcpy((char *) buf, "Change");
1455 goto dc_i; // start inserting
1458 strcpy((char *) buf, "Delete");
1460 #ifdef BB_FEATURE_VI_YANKMARK
1461 if (c == 'y' || c == 'Y') {
1462 strcpy((char *) buf, "Yank");
1465 q = p + strlen((char *) p);
1466 for (cnt = 0; p <= q; p++) {
1470 psb("%s %d lines (%d chars) using [%c]",
1471 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1472 #endif /* BB_FEATURE_VI_YANKMARK */
1473 end_cmd_q(); // stop adding to q
1476 case 'k': // k- goto prev line, same col
1477 case VI_K_UP: // cursor key Up
1482 dot = move_to_col(dot, ccol + offset); // try stay in same col
1484 case 'r': // r- replace the current char with user input
1485 c1 = get_one_char(); // get the replacement char
1488 file_modified = TRUE; // has the file been modified
1490 end_cmd_q(); // stop adding to q
1492 case 'w': // w- forward a word
1496 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1497 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1498 } else if (ispunct(*dot)) { // we are on PUNCT
1499 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1502 dot++; // move over word
1503 if (isspace(*dot)) {
1504 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1508 c1 = get_one_char(); // get the replacement char
1511 cnt = (rows - 2) / 2; // put dot at center
1513 cnt = rows - 2; // put dot at bottom
1514 screenbegin = begin_line(dot); // start dot at top
1515 dot_scroll(cnt, -1);
1517 case '|': // |- move to column "cmdcnt"
1518 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1520 case '~': // ~- flip the case of letters a-z -> A-Z
1524 if (islower(*dot)) {
1525 *dot = toupper(*dot);
1526 file_modified = TRUE; // has the file been modified
1527 } else if (isupper(*dot)) {
1528 *dot = tolower(*dot);
1529 file_modified = TRUE; // has the file been modified
1532 end_cmd_q(); // stop adding to q
1534 //----- The Cursor and Function Keys -----------------------------
1535 case VI_K_HOME: // Cursor Key Home
1538 // The Fn keys could point to do_macro which could translate them
1539 case VI_K_FUN1: // Function Key F1
1540 case VI_K_FUN2: // Function Key F2
1541 case VI_K_FUN3: // Function Key F3
1542 case VI_K_FUN4: // Function Key F4
1543 case VI_K_FUN5: // Function Key F5
1544 case VI_K_FUN6: // Function Key F6
1545 case VI_K_FUN7: // Function Key F7
1546 case VI_K_FUN8: // Function Key F8
1547 case VI_K_FUN9: // Function Key F9
1548 case VI_K_FUN10: // Function Key F10
1549 case VI_K_FUN11: // Function Key F11
1550 case VI_K_FUN12: // Function Key F12
1555 // if text[] just became empty, add back an empty line
1557 (void) char_insert(text, '\n'); // start empty buf with dummy line
1560 // it is OK for dot to exactly equal to end, otherwise check dot validity
1562 dot = bound_dot(dot); // make sure "dot" is valid
1564 #ifdef BB_FEATURE_VI_YANKMARK
1565 check_context(c); // update the current context
1566 #endif /* BB_FEATURE_VI_YANKMARK */
1569 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1570 cnt = dot - begin_line(dot);
1571 // Try to stay off of the Newline
1572 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1576 //----- The Colon commands -------------------------------------
1577 #ifdef BB_FEATURE_VI_COLON
1578 static Byte *get_address(Byte * p, int *addr) // get colon addr, if present
1583 #ifdef BB_FEATURE_VI_YANKMARK
1585 #endif /* BB_FEATURE_VI_YANKMARK */
1586 #ifdef BB_FEATURE_VI_SEARCH
1587 Byte *pat, buf[1024];
1588 #endif /* BB_FEATURE_VI_SEARCH */
1590 *addr = -1; // assume no addr
1591 if (*p == '.') { // the current line
1593 q = begin_line(dot);
1594 *addr = count_lines(text, q);
1595 #ifdef BB_FEATURE_VI_YANKMARK
1596 } else if (*p == '\'') { // is this a mark addr
1600 if (c >= 'a' && c <= 'z') {
1604 if (q != NULL) { // is mark valid
1605 *addr = count_lines(text, q); // count lines
1608 #endif /* BB_FEATURE_VI_YANKMARK */
1609 #ifdef BB_FEATURE_VI_SEARCH
1610 } else if (*p == '/') { // a search pattern
1612 for (p++; *p; p++) {
1618 pat = (Byte *) strdup((char *) buf); // save copy of pattern
1621 q = char_search(dot, pat, FORWARD, FULL);
1623 *addr = count_lines(text, q);
1626 #endif /* BB_FEATURE_VI_SEARCH */
1627 } else if (*p == '$') { // the last line in file
1629 q = begin_line(end - 1);
1630 *addr = count_lines(text, q);
1631 } else if (isdigit(*p)) { // specific line number
1632 sscanf((char *) p, "%d%n", addr, &st);
1634 } else { // I don't reconise this
1635 // unrecognised address- assume -1
1641 static void colon(Byte * buf)
1643 Byte c, *orig_buf, *buf1, *q, *r;
1644 Byte *fn, cmd[100], args[100];
1645 int i, l, li, ch, st, b, e;
1646 int useforce, forced;
1648 // :3154 // if (-e line 3154) goto it else stay put
1649 // :4,33w! foo // write a portion of buffer to file "foo"
1650 // :w // write all of buffer to current file
1652 // :q! // quit- dont care about modified file
1653 // :'a,'z!sort -u // filter block through sort
1654 // :'f // goto mark "f"
1655 // :'fl // list literal the mark "f" line
1656 // :.r bar // read file "bar" into buffer before dot
1657 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1658 // :/xyz/ // goto the "xyz" line
1659 // :s/find/replace/ // substitute pattern "find" with "replace"
1661 if (strlen((char *) buf) <= 0)
1664 buf++; // move past the ':'
1666 forced = useforce = FALSE;
1667 li = st = ch = i = 0;
1669 q = text; // assume 1,$ for the range
1671 li = count_lines(text, end - 1);
1672 fn = cfn; // default to current file
1674 // look for optional FIRST address(es) :. :1 :1,9 :'q,'a
1675 while (isblnk(*buf))
1678 // get FIRST addr, if present
1679 buf = get_address(buf, &b);
1680 while (isblnk(*buf))
1684 while (isblnk(*buf))
1686 // look for SECOND address
1687 buf = get_address(buf, &e);
1689 while (isblnk(*buf))
1692 // remember orig command line
1695 // get the COMMAND into cmd[]
1698 while (*buf != '\0') {
1704 // get any ARGuments
1705 while (isblnk(*buf))
1707 strcpy((char *) args, (char *) buf);
1708 if (cmd[strlen((char *) cmd) - 1] == '!') {
1710 cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
1713 // if there is only one addr, then the addr
1714 // is the line number of the single line the
1715 // user wants. So, reset the end
1716 // pointer to point at end of the "b" line
1717 q = find_line(b); // what line is #b
1722 // we were given two addrs. change the
1723 // end pointer to the addr given by user.
1724 r = find_line(e); // what line is #e
1728 // ------------ now look for the command ------------
1729 i = strlen((char *) cmd);
1730 if (i == 0) { // :123CR goto line #123
1732 dot = find_line(b); // what line is #b
1735 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1736 if (b < 0) { // no addr given- use defaults
1737 b = e = count_lines(text, dot);
1740 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1741 if (b < 0) { // no addr given- use defaults
1742 q = begin_line(dot); // assume .,. for the range
1745 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1747 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1748 // don't exit if the file been modified
1749 if (file_modified == TRUE && useforce != TRUE) {
1750 psbs("No write since last change (:edit! overrides)");
1754 if (strlen((char *) fn) <= 0) {
1755 // no file name given, re-edit current file
1760 cfn = (Byte *) strdup((char *) fn); // make this the current file
1761 // delete all the contents of text[]
1762 new_text(2 * file_size(fn));
1763 screenbegin = dot = end = text;
1766 ch = file_insert(fn, text, file_size(fn));
1768 file_modified = FALSE;
1769 #ifdef BB_FEATURE_VI_YANKMARK
1771 free(reg[Ureg]); // free orig line reg- for 'U'
1772 if (reg[YDreg] != 0)
1773 free(reg[YDreg]); // free default yank/delete register
1774 for (li = 0; li < 28; li++) {
1777 #endif /* BB_FEATURE_VI_YANKMARK */
1778 // how many lines in text[]?
1779 li = count_lines(text, end - 1);
1780 psb("\"%s\" %dL, %dC", cfn, li, ch);
1781 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1782 if (b != -1 || e != -1) {
1783 ni((Byte *) "No address allowed on this command");
1786 if (strlen((char *) args) > 0) {
1787 // user wants a new filename
1790 cfn = (Byte *) strdup((char *) args);
1792 // user wants file status info
1795 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1796 // print out values of all features
1797 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1798 clear_to_eol(); // clear the line
1803 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1804 if (b < 0) { // no addr given- use defaults
1805 q = begin_line(dot); // assume .,. for the range
1808 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1809 clear_to_eol(); // clear the line
1810 write(1, "\r\n", 2);
1811 for (; q <= r; q++) {
1817 } else if (*q < ' ') {
1825 #ifdef BB_FEATURE_VI_SET
1827 #endif /* BB_FEATURE_VI_SET */
1829 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1830 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1831 if (useforce == TRUE) {
1832 // force end of argv list
1839 // don't exit if the file been modified
1840 if (file_modified == TRUE) {
1841 psbs("No write since last change (:%s! overrides)",
1842 (*cmd == 'q' ? "quit" : "next"));
1845 // are there other file to edit
1846 if (*cmd == 'q' && optind < save_argc - 1) {
1847 psbs("%d more file to edit", (save_argc - optind - 1));
1850 if (*cmd == 'n' && optind >= save_argc - 1) {
1851 psbs("No more files to edit");
1855 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1857 if (strlen((char *) fn) <= 0) {
1858 psbs("No filename given");
1861 if (b < 0) { // no addr given- use defaults
1862 q = begin_line(dot); // assume "dot"
1864 // read after current line- unless user said ":0r foo"
1867 ch = file_insert(fn, q, file_size(fn));
1869 goto vc1; // nothing was inserted
1870 // how many lines in text[]?
1871 li = count_lines(q, q + ch - 1);
1872 psb("\"%s\" %dL, %dC", fn, li, ch);
1874 // if the insert is before "dot" then we need to update
1877 file_modified = TRUE;
1879 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
1880 if (file_modified == TRUE && useforce != TRUE) {
1881 psbs("No write since last change (:rewind! overrides)");
1883 // reset the filenames to edit
1884 optind = fn_start - 1;
1887 #ifdef BB_FEATURE_VI_SET
1888 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
1889 i = 0; // offset into args
1890 if (strlen((char *) args) == 0) {
1891 // print out values of all options
1892 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1893 clear_to_eol(); // clear the line
1894 printf("----------------------------------------\r\n");
1895 #ifdef BB_FEATURE_VI_SETOPTS
1898 printf("autoindent ");
1901 printf("ignorecase ");
1904 printf("showmatch ");
1905 printf("tabstop=%d ", tabstop);
1906 #endif /* BB_FEATURE_VI_SETOPTS */
1910 if (strncasecmp((char *) args, "no", 2) == 0)
1911 i = 2; // ":set noautoindent"
1912 #ifdef BB_FEATURE_VI_SETOPTS
1913 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
1914 strncasecmp((char *) args + i, "ai", 2) == 0) {
1915 autoindent = (i == 2) ? 0 : 1;
1917 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
1918 strncasecmp((char *) args + i, "ic", 2) == 0) {
1919 ignorecase = (i == 2) ? 0 : 1;
1921 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
1922 strncasecmp((char *) args + i, "sm", 2) == 0) {
1923 showmatch = (i == 2) ? 0 : 1;
1925 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
1926 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1927 if (ch > 0 && ch < columns - 1)
1930 #endif /* BB_FEATURE_VI_SETOPTS */
1931 #endif /* BB_FEATURE_VI_SET */
1932 #ifdef BB_FEATURE_VI_SEARCH
1933 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1937 // F points to the "find" pattern
1938 // R points to the "replace" pattern
1939 // replace the cmd line delimiters "/" with NULLs
1940 gflag = 0; // global replace flag
1941 c = orig_buf[1]; // what is the delimiter
1942 F = orig_buf + 2; // start of "find"
1943 R = (Byte *) strchr((char *) F, c); // middle delimiter
1944 *R++ = '\0'; // terminate "find"
1945 buf1 = (Byte *) strchr((char *) R, c);
1946 *buf1++ = '\0'; // terminate "replace"
1947 if (*buf1 == 'g') { // :s/foo/bar/g
1949 gflag++; // turn on gflag
1952 if (b < 0) { // maybe :s/foo/bar/
1953 q = begin_line(dot); // start with cur line
1954 b = count_lines(text, q); // cur line number
1957 e = b; // maybe :.s/foo/bar/
1958 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1959 ls = q; // orig line start
1961 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1963 // we found the "find" pattern- delete it
1964 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1965 // inset the "replace" patern
1966 (void) string_insert(buf1, R); // insert the string
1967 // check for "global" :s/foo/bar/g
1969 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1970 q = buf1 + strlen((char *) R);
1971 goto vc4; // don't let q move past cur line
1977 #endif /* BB_FEATURE_VI_SEARCH */
1978 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1979 psb("%s", vi_Version);
1980 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1981 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
1982 // is there a file name to write to?
1983 if (strlen((char *) args) > 0) {
1986 #ifdef BB_FEATURE_VI_READONLY
1987 if (readonly == TRUE) {
1988 psbs("\"%s\" File is read only", fn);
1991 #endif /* BB_FEATURE_VI_READONLY */
1992 // how many lines in text[]?
1993 li = count_lines(q, r);
1995 if (useforce == TRUE) {
1996 // if "fn" is not write-able, chmod u+w
1997 // sprintf(syscmd, "chmod u+w %s", fn);
2001 l = file_write(fn, q, r);
2002 if (useforce == TRUE && forced == TRUE) {
2004 // sprintf(syscmd, "chmod u-w %s", fn);
2008 psb("\"%s\" %dL, %dC", fn, li, l);
2009 if (q == text && r == end - 1 && l == ch)
2010 file_modified = FALSE;
2011 if (cmd[1] == 'q' && l == ch) {
2014 #ifdef BB_FEATURE_VI_READONLY
2016 #endif /* BB_FEATURE_VI_READONLY */
2017 #ifdef BB_FEATURE_VI_YANKMARK
2018 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2019 if (b < 0) { // no addr given- use defaults
2020 q = begin_line(dot); // assume .,. for the range
2023 text_yank(q, r, YDreg);
2024 li = count_lines(q, r);
2025 psb("Yank %d lines (%d chars) into [%c]",
2026 li, strlen((char *) reg[YDreg]), what_reg());
2027 #endif /* BB_FEATURE_VI_YANKMARK */
2033 dot = bound_dot(dot); // make sure "dot" is valid
2037 static void Hit_Return(void)
2041 standout_start(); // start reverse video
2042 write(1, "[Hit return to continue]", 24);
2043 standout_end(); // end reverse video
2044 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2046 redraw(TRUE); // force redraw all
2048 #endif /* BB_FEATURE_VI_COLON */
2050 //----- Synchronize the cursor to Dot --------------------------
2051 static void sync_cursor(Byte * d, int *row, int *col)
2053 Byte *beg_cur, *end_cur; // begin and end of "d" line
2054 Byte *beg_scr, *end_scr; // begin and end of screen
2058 beg_cur = begin_line(d); // first char of cur line
2059 end_cur = end_line(d); // last char of cur line
2061 beg_scr = end_scr = screenbegin; // first char of screen
2062 end_scr = end_screen(); // last char of screen
2064 if (beg_cur < screenbegin) {
2065 // "d" is before top line on screen
2066 // how many lines do we have to move
2067 cnt = count_lines(beg_cur, screenbegin);
2069 screenbegin = beg_cur;
2070 if (cnt > (rows - 1) / 2) {
2071 // we moved too many lines. put "dot" in middle of screen
2072 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2073 screenbegin = prev_line(screenbegin);
2076 } else if (beg_cur > end_scr) {
2077 // "d" is after bottom line on screen
2078 // how many lines do we have to move
2079 cnt = count_lines(end_scr, beg_cur);
2080 if (cnt > (rows - 1) / 2)
2081 goto sc1; // too many lines
2082 for (ro = 0; ro < cnt - 1; ro++) {
2083 // move screen begin the same amount
2084 screenbegin = next_line(screenbegin);
2085 // now, move the end of screen
2086 end_scr = next_line(end_scr);
2087 end_scr = end_line(end_scr);
2090 // "d" is on screen- find out which row
2092 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2098 // find out what col "d" is on
2100 do { // drive "co" to correct column
2101 if (*tp == '\n' || *tp == '\0')
2105 co += ((tabstop - 1) - (co % tabstop));
2106 } else if (*tp < ' ') {
2107 co++; // display as ^X, use 2 columns
2109 } while (tp++ < d && ++co);
2111 // "co" is the column where "dot" is.
2112 // The screen has "columns" columns.
2113 // The currently displayed columns are 0+offset -- columns+ofset
2114 // |-------------------------------------------------------------|
2116 // offset | |------- columns ----------------|
2118 // If "co" is already in this range then we do not have to adjust offset
2119 // but, we do have to subtract the "offset" bias from "co".
2120 // If "co" is outside this range then we have to change "offset".
2121 // If the first char of a line is a tab the cursor will try to stay
2122 // in column 7, but we have to set offset to 0.
2124 if (co < 0 + offset) {
2127 if (co >= columns + offset) {
2128 offset = co - columns + 1;
2130 // if the first char of the line is a tab, and "dot" is sitting on it
2131 // force offset to 0.
2132 if (d == beg_cur && *d == '\t') {
2141 //----- Text Movement Routines ---------------------------------
2142 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2144 while (p > text && p[-1] != '\n')
2145 p--; // go to cur line B-o-l
2149 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2151 while (p < end - 1 && *p != '\n')
2152 p++; // go to cur line E-o-l
2156 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2158 while (p < end - 1 && *p != '\n')
2159 p++; // go to cur line E-o-l
2160 // Try to stay off of the Newline
2161 if (*p == '\n' && (p - begin_line(p)) > 0)
2166 static Byte *prev_line(Byte * p) // return pointer first char prev line
2168 p = begin_line(p); // goto begining of cur line
2169 if (p[-1] == '\n' && p > text)
2170 p--; // step to prev line
2171 p = begin_line(p); // goto begining of prev line
2175 static Byte *next_line(Byte * p) // return pointer first char next line
2178 if (*p == '\n' && p < end - 1)
2179 p++; // step to next line
2183 //----- Text Information Routines ------------------------------
2184 static Byte *end_screen(void)
2189 // find new bottom line
2191 for (cnt = 0; cnt < rows - 2; cnt++)
2197 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2202 if (stop < start) { // start and stop are backwards- reverse them
2208 stop = end_line(stop); // get to end of this line
2209 for (q = start; q <= stop && q <= end - 1; q++) {
2216 static Byte *find_line(int li) // find begining of line #li
2220 for (q = text; li > 1; li--) {
2226 //----- Dot Movement Routines ----------------------------------
2227 static void dot_left(void)
2229 if (dot > text && dot[-1] != '\n')
2233 static void dot_right(void)
2235 if (dot < end - 1 && *dot != '\n')
2239 static void dot_begin(void)
2241 dot = begin_line(dot); // return pointer to first char cur line
2244 static void dot_end(void)
2246 dot = end_line(dot); // return pointer to last char cur line
2249 static Byte *move_to_col(Byte * p, int l)
2256 if (*p == '\n' || *p == '\0')
2260 co += ((tabstop - 1) - (co % tabstop));
2261 } else if (*p < ' ') {
2262 co++; // display as ^X, use 2 columns
2264 } while (++co <= l && p++ < end);
2268 static void dot_next(void)
2270 dot = next_line(dot);
2273 static void dot_prev(void)
2275 dot = prev_line(dot);
2278 static void dot_scroll(int cnt, int dir)
2282 for (; cnt > 0; cnt--) {
2285 // ctrl-Y scroll up one line
2286 screenbegin = prev_line(screenbegin);
2289 // ctrl-E scroll down one line
2290 screenbegin = next_line(screenbegin);
2293 // make sure "dot" stays on the screen so we dont scroll off
2294 if (dot < screenbegin)
2296 q = end_screen(); // find new bottom line
2298 dot = begin_line(q); // is dot is below bottom line?
2302 static void dot_skip_over_ws(void)
2305 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2309 static void dot_delete(void) // delete the char at 'dot'
2311 (void) text_hole_delete(dot, dot);
2314 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2316 if (p >= end && end > text) {
2318 indicate_error('1');
2322 indicate_error('2');
2327 //----- Helper Utility Routines --------------------------------
2329 //----------------------------------------------------------------
2330 //----- Char Routines --------------------------------------------
2331 /* Chars that are part of a word-
2332 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2333 * Chars that are Not part of a word (stoppers)
2334 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2335 * Chars that are WhiteSpace
2336 * TAB NEWLINE VT FF RETURN SPACE
2337 * DO NOT COUNT NEWLINE AS WHITESPACE
2340 static Byte *new_screen(int ro, int co)
2344 screensize = ro * co + 8;
2345 screen = (Byte *) malloc(screensize);
2349 static Byte *new_text(int size)
2352 size = 10240; // have a minimum size for new files
2357 text = (Byte *) malloc(size + 8);
2358 memset(text, '\0', size); // clear new text[]
2359 //text += 4; // leave some room for "oops"
2360 textend = text + size - 1;
2361 //textend -= 4; // leave some root for "oops"
2365 #ifdef BB_FEATURE_VI_SEARCH
2366 static int mycmp(Byte * s1, Byte * s2, int len)
2370 i = strncmp((char *) s1, (char *) s2, len);
2371 #ifdef BB_FEATURE_VI_SETOPTS
2373 i = strncasecmp((char *) s1, (char *) s2, len);
2375 #endif /* BB_FEATURE_VI_SETOPTS */
2379 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2381 #ifndef REGEX_SEARCH
2385 len = strlen((char *) pat);
2386 if (dir == FORWARD) {
2387 stop = end - 1; // assume range is p - end-1
2388 if (range == LIMITED)
2389 stop = next_line(p); // range is to next line
2390 for (start = p; start < stop; start++) {
2391 if (mycmp(start, pat, len) == 0) {
2395 } else if (dir == BACK) {
2396 stop = text; // assume range is text - p
2397 if (range == LIMITED)
2398 stop = prev_line(p); // range is to prev line
2399 for (start = p - len; start >= stop; start--) {
2400 if (mycmp(start, pat, len) == 0) {
2405 // pattern not found
2407 #else /*REGEX_SEARCH */
2409 struct re_pattern_buffer preg;
2413 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2419 // assume a LIMITED forward search
2427 // count the number of chars to search over, forward or backward
2431 // RANGE could be negative if we are searching backwards
2434 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2436 // The pattern was not compiled
2437 psbs("bad search pattern: \"%s\": %s", pat, q);
2438 i = 0; // return p if pattern not compiled
2448 // search for the compiled pattern, preg, in p[]
2449 // range < 0- search backward
2450 // range > 0- search forward
2452 // re_search() < 0 not found or error
2453 // re_search() > 0 index of found pattern
2454 // struct pattern char int int int struct reg
2455 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2456 i = re_search(&preg, q, size, 0, range, 0);
2459 i = 0; // return NULL if pattern not found
2462 if (dir == FORWARD) {
2468 #endif /*REGEX_SEARCH */
2470 #endif /* BB_FEATURE_VI_SEARCH */
2472 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2474 if (c == 22) { // Is this an ctrl-V?
2475 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2476 p--; // backup onto ^
2477 refresh(FALSE); // show the ^
2481 file_modified = TRUE; // has the file been modified
2482 } else if (c == 27) { // Is this an ESC?
2485 end_cmd_q(); // stop adding to q
2486 *status_buffer = '\0'; // clear the status buffer
2487 if (p[-1] != '\n') {
2490 } else if (c == erase_char) { // Is this a BS
2492 if (p[-1] != '\n') {
2494 p = text_hole_delete(p, p); // shrink buffer 1 char
2495 #ifdef BB_FEATURE_VI_DOT_CMD
2496 // also rmove char from last_modifying_cmd
2497 if (strlen((char *) last_modifying_cmd) > 0) {
2500 q = last_modifying_cmd;
2501 q[strlen((char *) q) - 1] = '\0'; // erase BS
2502 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2504 #endif /* BB_FEATURE_VI_DOT_CMD */
2507 // insert a char into text[]
2508 Byte *sp; // "save p"
2511 c = '\n'; // translate \r to \n
2512 sp = p; // remember addr of insert
2513 p = stupid_insert(p, c); // insert the char
2514 #ifdef BB_FEATURE_VI_SETOPTS
2515 if (showmatch && strchr(")]}", *sp) != NULL) {
2518 if (autoindent && c == '\n') { // auto indent the new line
2521 q = prev_line(p); // use prev line as templet
2522 for (; isblnk(*q); q++) {
2523 p = stupid_insert(p, *q); // insert the char
2526 #endif /* BB_FEATURE_VI_SETOPTS */
2531 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2533 p = text_hole_make(p, 1);
2536 file_modified = TRUE; // has the file been modified
2542 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2544 Byte *save_dot, *p, *q;
2550 if (strchr("cdy><", c)) {
2551 p = q = begin_line(p);
2552 for (cnt = 1; cnt < cmdcnt; cnt++) {
2556 } else if (strchr("$0bBeE", c)) {
2557 do_cmd(c); // execute movement cmd
2559 } else if (strchr("wW", c)) {
2560 do_cmd(c); // execute movement cmd
2562 dot--; // move back off of next word
2563 if (dot > text && *dot == '\n')
2564 dot--; // stay off NL
2566 } else if (strchr("H-k{", c)) {
2567 q = end_line(dot); // find NL
2568 do_cmd(c); // execute movement cmd
2571 } else if (strchr("L+j}\r\n", c)) {
2572 p = begin_line(dot);
2573 do_cmd(c); // execute movement cmd
2574 dot_end(); // find NL
2577 c = 27; // error- return an ESC char
2590 static int st_test(Byte * p, int type, int dir, Byte * tested)
2600 if (type == S_BEFORE_WS) {
2602 test = ((!isspace(c)) || c == '\n');
2604 if (type == S_TO_WS) {
2606 test = ((!isspace(c)) || c == '\n');
2608 if (type == S_OVER_WS) {
2610 test = ((isspace(c)));
2612 if (type == S_END_PUNCT) {
2614 test = ((ispunct(c)));
2616 if (type == S_END_ALNUM) {
2618 test = ((isalnum(c)) || c == '_');
2624 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2628 while (st_test(p, type, dir, &c)) {
2629 // make sure we limit search to correct number of lines
2630 if (c == '\n' && --linecnt < 1)
2632 if (dir >= 0 && p >= end - 1)
2634 if (dir < 0 && p <= text)
2636 p += dir; // move to next char
2641 // find matching char of pair () [] {}
2642 static Byte *find_pair(Byte * p, Byte c)
2649 dir = 1; // assume forward
2673 for (q = p + dir; text <= q && q < end; q += dir) {
2674 // look for match, count levels of pairs (( ))
2676 level++; // increase pair levels
2678 level--; // reduce pair level
2680 break; // found matching pair
2683 q = NULL; // indicate no match
2687 #ifdef BB_FEATURE_VI_SETOPTS
2688 // show the matching char of a pair, () [] {}
2689 static void showmatching(Byte * p)
2693 // we found half of a pair
2694 q = find_pair(p, *p); // get loc of matching char
2696 indicate_error('3'); // no matching char
2698 // "q" now points to matching pair
2699 save_dot = dot; // remember where we are
2700 dot = q; // go to new loc
2701 refresh(FALSE); // let the user see it
2702 (void) mysleep(40); // give user some time
2703 dot = save_dot; // go back to old loc
2707 #endif /* BB_FEATURE_VI_SETOPTS */
2709 // open a hole in text[]
2710 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2719 cnt = end - src; // the rest of buffer
2720 if (memmove(dest, src, cnt) != dest) {
2721 psbs("can't create room for new characters");
2723 memset(p, ' ', size); // clear new hole
2724 end = end + size; // adjust the new END
2725 file_modified = TRUE; // has the file been modified
2730 // close a hole in text[]
2731 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2736 // move forwards, from beginning
2740 if (q < p) { // they are backward- swap them
2744 hole_size = q - p + 1;
2746 if (src < text || src > end)
2748 if (dest < text || dest >= end)
2751 goto thd_atend; // just delete the end of the buffer
2752 if (memmove(dest, src, cnt) != dest) {
2753 psbs("can't delete the character");
2756 end = end - hole_size; // adjust the new END
2758 dest = end - 1; // make sure dest in below end-1
2760 dest = end = text; // keep pointers valid
2761 file_modified = TRUE; // has the file been modified
2766 // copy text into register, then delete text.
2767 // if dist <= 0, do not include, or go past, a NewLine
2769 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2773 // make sure start <= stop
2775 // they are backwards, reverse them
2781 // we can not cross NL boundaries
2785 // dont go past a NewLine
2786 for (; p + 1 <= stop; p++) {
2788 stop = p; // "stop" just before NewLine
2794 #ifdef BB_FEATURE_VI_YANKMARK
2795 text_yank(start, stop, YDreg);
2796 #endif /* BB_FEATURE_VI_YANKMARK */
2797 if (yf == YANKDEL) {
2798 p = text_hole_delete(start, stop);
2803 static void show_help(void)
2805 printf("These features are available:\n");
2806 #ifdef BB_FEATURE_VI_SEARCH
2807 printf("\tPattern searches with / and ?\n");
2808 #endif /* BB_FEATURE_VI_SEARCH */
2809 #ifdef BB_FEATURE_VI_DOT_CMD
2810 printf("\tLast command repeat with \'.\'\n");
2811 #endif /* BB_FEATURE_VI_DOT_CMD */
2812 #ifdef BB_FEATURE_VI_YANKMARK
2813 printf("\tLine marking with 'x\n");
2814 printf("\tNamed buffers with \"x\n");
2815 #endif /* BB_FEATURE_VI_YANKMARK */
2816 #ifdef BB_FEATURE_VI_READONLY
2817 printf("\tReadonly with -R command line arg\n");
2818 #endif /* BB_FEATURE_VI_READONLY */
2819 #ifdef BB_FEATURE_VI_SET
2820 printf("\tSome colon mode commands with \':\'\n");
2821 #endif /* BB_FEATURE_VI_SET */
2822 #ifdef BB_FEATURE_VI_SETOPTS
2823 printf("\tSettable options with \":set\"\n");
2824 #endif /* BB_FEATURE_VI_SETOPTS */
2825 #ifdef BB_FEATURE_VI_USE_SIGNALS
2826 printf("\tSignal catching- ^C\n");
2827 printf("\tJob suspend and resume with ^Z\n");
2828 #endif /* BB_FEATURE_VI_USE_SIGNALS */
2829 #ifdef BB_FEATURE_VI_WIN_RESIZE
2830 printf("\tAdapt to window re-sizes\n");
2831 #endif /* BB_FEATURE_VI_WIN_RESIZE */
2834 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2839 strcpy((char *) buf, ""); // init buf
2840 if (strlen((char *) s) <= 0)
2841 s = (Byte *) "(NULL)";
2842 for (; *s > '\0'; s++) {
2845 strcat((char *) buf, SOs);
2849 strcat((char *) buf, "^");
2853 strcat((char *) buf, (char *) b);
2855 strcat((char *) buf, SOn);
2857 strcat((char *) buf, "$");
2862 #ifdef BB_FEATURE_VI_DOT_CMD
2863 static void start_new_cmd_q(Byte c)
2866 if (last_modifying_cmd != 0)
2867 free(last_modifying_cmd);
2868 // get buffer for new cmd
2869 last_modifying_cmd = (Byte *) malloc(BUFSIZ);
2870 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2871 // if there is a current cmd count put it in the buffer first
2873 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2874 // save char c onto queue
2875 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2880 static void end_cmd_q()
2882 #ifdef BB_FEATURE_VI_YANKMARK
2883 YDreg = 26; // go back to default Yank/Delete reg
2884 #endif /* BB_FEATURE_VI_YANKMARK */
2888 #endif /* BB_FEATURE_VI_DOT_CMD */
2890 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
2891 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2895 i = strlen((char *) s);
2896 p = text_hole_make(p, i);
2897 strncpy((char *) p, (char *) s, i);
2898 for (cnt = 0; *s != '\0'; s++) {
2902 #ifdef BB_FEATURE_VI_YANKMARK
2903 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2904 #endif /* BB_FEATURE_VI_YANKMARK */
2907 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
2909 #ifdef BB_FEATURE_VI_YANKMARK
2910 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2915 if (q < p) { // they are backwards- reverse them
2922 if (t != 0) { // if already a yank register
2925 t = (Byte *) malloc(cnt + 1); // get a new register
2926 memset(t, '\0', cnt + 1); // clear new text[]
2927 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2932 static Byte what_reg(void)
2938 c = 'D'; // default to D-reg
2939 if (0 <= YDreg && YDreg <= 25)
2940 c = 'a' + (Byte) YDreg;
2948 static void check_context(Byte cmd)
2950 // A context is defined to be "modifying text"
2951 // Any modifying command establishes a new context.
2953 if (dot < context_start || dot > context_end) {
2954 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2955 // we are trying to modify text[]- make this the current context
2956 mark[27] = mark[26]; // move cur to prev
2957 mark[26] = dot; // move local to cur
2958 context_start = prev_line(prev_line(dot));
2959 context_end = next_line(next_line(dot));
2960 //loiter= start_loiter= now;
2966 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2970 // the current context is in mark[26]
2971 // the previous context is in mark[27]
2972 // only swap context if other context is valid
2973 if (text <= mark[27] && mark[27] <= end - 1) {
2975 mark[27] = mark[26];
2977 p = mark[26]; // where we are going- previous context
2978 context_start = prev_line(prev_line(prev_line(p)));
2979 context_end = next_line(next_line(next_line(p)));
2983 #endif /* BB_FEATURE_VI_YANKMARK */
2985 static int isblnk(Byte c) // is the char a blank or tab
2987 return (c == ' ' || c == '\t');
2990 //----- Set terminal attributes --------------------------------
2991 static void rawmode(void)
2993 tcgetattr(0, &term_orig);
2994 term_vi = term_orig;
2995 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2996 term_vi.c_iflag &= (~IXON & ~ICRNL);
2997 term_vi.c_cc[VMIN] = 1;
2998 term_vi.c_cc[VTIME] = 0;
2999 erase_char = term_vi.c_cc[VERASE];
3000 tcsetattr(0, TCSANOW, &term_vi);
3003 static void cookmode(void)
3005 tcsetattr(0, TCSANOW, &term_orig);
3008 #ifdef BB_FEATURE_VI_WIN_RESIZE
3009 //----- See what the window size currently is --------------------
3010 static void window_size_get(int sig)
3014 i = ioctl(0, TIOCGWINSZ, &winsize);
3017 winsize.ws_row = 24;
3018 winsize.ws_col = 80;
3020 if (winsize.ws_row <= 1) {
3021 winsize.ws_row = 24;
3023 if (winsize.ws_col <= 1) {
3024 winsize.ws_col = 80;
3026 rows = (int) winsize.ws_row;
3027 columns = (int) winsize.ws_col;
3029 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3031 //----- Come here when we get a window resize signal ---------
3032 #ifdef BB_FEATURE_VI_USE_SIGNALS
3033 static void winch_sig(int sig)
3035 signal(SIGWINCH, winch_sig);
3036 #ifdef BB_FEATURE_VI_WIN_RESIZE
3038 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3039 new_screen(rows, columns); // get memory for virtual screen
3040 redraw(TRUE); // re-draw the screen
3043 //----- Come here when we get a continue signal -------------------
3044 static void cont_sig(int sig)
3046 rawmode(); // terminal to "raw"
3047 *status_buffer = '\0'; // clear the status buffer
3048 redraw(TRUE); // re-draw the screen
3050 signal(SIGTSTP, suspend_sig);
3051 signal(SIGCONT, SIG_DFL);
3052 kill(getpid(), SIGCONT);
3055 //----- Come here when we get a Suspend signal -------------------
3056 static void suspend_sig(int sig)
3058 place_cursor(rows, 0); // go to bottom of screen
3059 clear_to_eol(); // Erase to end of line
3060 cookmode(); // terminal to "cooked"
3062 signal(SIGCONT, cont_sig);
3063 signal(SIGTSTP, SIG_DFL);
3064 kill(getpid(), SIGTSTP);
3067 //----- Come here when we get a signal --------------------
3068 static void catch_sig(int sig)
3070 signal(SIGHUP, catch_sig);
3071 signal(SIGINT, catch_sig);
3072 signal(SIGTERM, catch_sig);
3073 longjmp(restart, sig);
3076 static void alarm_sig(int sig)
3078 signal(SIGALRM, catch_sig);
3079 longjmp(restart, sig);
3082 //----- Come here when we get a core dump signal -----------------
3083 static void core_sig(int sig)
3085 signal(SIGQUIT, core_sig);
3086 signal(SIGILL, core_sig);
3087 signal(SIGTRAP, core_sig);
3088 signal(SIGIOT, core_sig);
3089 signal(SIGABRT, core_sig);
3090 signal(SIGFPE, core_sig);
3091 signal(SIGBUS, core_sig);
3092 signal(SIGSEGV, core_sig);
3094 signal(SIGSYS, core_sig);
3097 dot = bound_dot(dot); // make sure "dot" is valid
3099 longjmp(restart, sig);
3101 #endif /* BB_FEATURE_VI_USE_SIGNALS */
3103 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3105 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3109 tv.tv_usec = hund * 10000;
3110 select(1, &rfds, NULL, NULL, &tv);
3111 return (FD_ISSET(0, &rfds));
3114 //----- IO Routines --------------------------------------------
3115 static Byte readit(void) // read (maybe cursor) key from stdin
3118 int i, bufsiz, cnt, cmdindex;
3124 static struct esc_cmds esccmds[] = {
3125 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3126 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3127 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3128 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3129 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3130 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3131 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3132 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3133 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3134 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3135 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3136 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3137 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3138 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3139 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3140 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3141 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3142 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3143 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3144 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3145 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3146 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3147 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3148 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3149 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3150 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3151 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3152 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3153 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3154 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3155 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3158 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3160 (void) alarm(0); // turn alarm OFF while we wait for input
3161 // get input from User- are there already input chars in Q?
3162 bufsiz = strlen((char *) readbuffer);
3165 // the Q is empty, wait for a typed char
3166 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3169 goto ri0; // interrupted sys call
3172 if (errno == EFAULT)
3174 if (errno == EINVAL)
3181 readbuffer[bufsiz] = '\0';
3183 // return char if it is not part of ESC sequence
3184 if (readbuffer[0] != 27)
3187 // This is an ESC char. Is this Esc sequence?
3188 // Could be bare Esc key. See if there are any
3189 // more chars to read after the ESC. This would
3190 // be a Function or Cursor Key sequence.
3194 tv.tv_usec = 10000; // Wait 1/100 seconds- 1 Sec=1000000
3196 // keep reading while there are input chars and room in buffer
3197 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3198 // read the rest of the ESC string
3199 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3202 readbuffer[bufsiz] = '\0'; // Terminate the string
3205 // Maybe cursor or function key?
3206 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3207 cnt = strlen((char *) esccmds[cmdindex].seq);
3208 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3210 // is a Cursor key- put derived value back into Q
3211 readbuffer[0] = esccmds[cmdindex].val;
3212 // squeeze out the ESC sequence
3213 for (i = 1; i < cnt; i++) {
3214 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3215 readbuffer[BUFSIZ - 1] = '\0';
3222 // remove one char from Q
3223 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3224 readbuffer[BUFSIZ - 1] = '\0';
3225 (void) alarm(3); // we are done waiting for input, turn alarm ON
3229 //----- IO Routines --------------------------------------------
3230 static Byte get_one_char()
3234 #ifdef BB_FEATURE_VI_DOT_CMD
3235 // ! adding2q && ioq == 0 read()
3236 // ! adding2q && ioq != 0 *ioq
3237 // adding2q *last_modifying_cmd= read()
3239 // we are not adding to the q.
3240 // but, we may be reading from a q
3242 // there is no current q, read from STDIN
3243 c = readit(); // get the users input
3245 // there is a queue to get chars from first
3248 // the end of the q, read from STDIN
3250 ioq_start = ioq = 0;
3251 c = readit(); // get the users input
3255 // adding STDIN chars to q
3256 c = readit(); // get the users input
3257 if (last_modifying_cmd != 0) {
3258 // add new char to q
3259 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3262 #else /* BB_FEATURE_VI_DOT_CMD */
3263 c = readit(); // get the users input
3264 #endif /* BB_FEATURE_VI_DOT_CMD */
3265 return (c); // return the char, where ever it came from
3268 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
3269 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3274 static Byte *obufp = NULL;
3276 strcpy((char *) buf, (char *) prompt);
3277 *status_buffer = '\0'; // clear the status buffer
3278 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
3279 clear_to_eol(); // clear the line
3280 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3282 for (i = strlen((char *) buf); i < 500;) {
3283 c = get_one_char(); // read user input
3284 if (c == '\n' || c == '\r')
3285 break; // is this end of input
3286 if (c == erase_char) { // user wants to erase prev char
3287 i--; // backup to prev char
3288 buf[i] = '\0'; // erase the char
3289 buf[i + 1] = '\0'; // null terminate buffer
3290 write(1, "
\b \b", 3); // erase char on screen
3291 if (i <= 0) { // user backs up before b-o-l, exit
3295 buf[i] = c; // save char in buffer
3296 buf[i + 1] = '\0'; // make sure buffer is null terminated
3297 write(1, buf + i, 1); // echo the char back to user
3304 obufp = (Byte *) strdup((char *) buf);
3307 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
3309 static int file_size(Byte * fn) // what is the byte size of "fn"
3317 sr = stat((char *) fn, &st_buf); // see if file exists
3319 cnt = (int) st_buf.st_size;
3324 static int file_insert(Byte * fn, Byte * p, int size)
3331 psbs("No filename given");
3334 sr = stat((char *) fn, &st_buf); // see if file exists
3336 psbs("\"%s\" %s", fn, "count not stat file");
3339 if (size <= 0 || (int) st_buf.st_size <= 0) {
3340 psbs("The file size (%d) is too small", size);
3341 if ((int) st_buf.st_size <= 0)
3342 psbs("\"%s\" is empty", fn);
3345 // There is a file with content
3346 fd = open((char *) fn, O_RDWR);
3348 psbs("\"%s\" %s", fn, "could not open file");
3351 p = text_hole_make(p, size);
3352 cnt = read(fd, p, size);
3356 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3357 psbs("could not read file \"%s\"", fn);
3358 } else if (cnt < size) {
3359 // There was a partial read, shrink unused space text[]
3360 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3361 psbs("could not read all of file \"%s\"", fn);
3364 file_modified = TRUE;
3369 static int file_write(Byte * fn, Byte * first, Byte * last)
3371 int fd, cnt, charcnt;
3374 psbs("No current filename");
3378 // FIXIT- use the correct umask()
3379 fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
3382 cnt = last - first + 1;
3383 charcnt = write(fd, first, cnt);
3384 if (charcnt == cnt) {
3386 //file_modified= FALSE; // the file has not been modified
3394 //----- Terminal Drawing ---------------------------------------
3395 // The terminal is made up of 'rows' line of 'columns' columns.
3396 // classicly this would be 24 x 80.
3397 // screen coordinates
3403 // 23,0 ... 23,79 status line
3406 //----- Move the cursor to row x col (count from 0, not 1) -------
3407 static void place_cursor(int row, int col)
3420 sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
3421 l = strlen((char *) buf);
3425 //----- Erase from cursor to end of line -----------------------
3426 static void clear_to_eol()
3428 write(1, "\033[0K", 4); // Erase from cursor to end of line
3431 //----- Erase from cursor to end of screen -----------------------
3432 static void clear_to_eos()
3434 write(1, "\033[0J", 4); // Erase from cursor to end of screen
3437 //----- Start standout mode ------------------------------------
3438 static void standout_start() // send "start reverse video" sequence
3440 write(1, "\033[7m", 4); // Start reverse video mode
3443 //----- End standout mode --------------------------------------
3444 static void standout_end() // send "end reverse video" sequence
3446 write(1, "\033[0m", 4); // End reverse video mode
3449 //----- Flash the screen --------------------------------------
3450 static void flash(int h)
3452 standout_start(); // send "start reverse video" sequence
3455 standout_end(); // send "end reverse video" sequence
3461 write(1, "\007", 1); // send out a bell character
3464 static void indicate_error(char c)
3466 #ifdef BB_FEATURE_VI_CRASHME
3468 return; // generate a random command
3469 #endif /* BB_FEATURE_VI_CRASHME */
3470 if (err_method == 0) {
3477 //----- Screen[] Routines --------------------------------------
3478 //----- Erase the Screen[] memory ------------------------------
3479 static void screen_erase()
3483 for (i = 0; i < screensize; i++) {
3488 //----- Draw the status line at bottom of the screen -------------
3489 static void show_status_line(void)
3493 cnt = strlen((char *) status_buffer);
3494 place_cursor(rows - 1, 0); // put cursor on status line
3496 write(1, status_buffer, cnt);
3499 place_cursor(crow, ccol); // put cursor back in correct place
3502 //----- format the status buffer, the bottom line of screen ------
3503 // print status buffer, with STANDOUT mode
3504 static void psbs(char *format, ...)
3508 va_start(args, format);
3509 strcpy((char *) status_buffer, "\033[7m"); // Terminal standout mode on
3510 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3512 strcat((char *) status_buffer, "\033[0m"); // Terminal standout mode off
3518 // print status buffer
3519 static void psb(char *format, ...)
3523 va_start(args, format);
3524 vsprintf((char *) status_buffer, format, args);
3529 static void ni(Byte * s) // display messages
3533 print_literal(buf, s);
3534 psbs("\'%s\' is not implemented", buf);
3537 static void edit_status(void) // show file status on status line
3539 int cur, tot, percent;
3541 cur = count_lines(text, dot);
3542 tot = count_lines(text, end - 1);
3543 // current line percent
3544 // ------------- ~~ ----------
3547 percent = (100 * cur) / tot;
3553 #ifdef BB_FEATURE_VI_READONLY
3555 #endif /* BB_FEATURE_VI_READONLY */
3556 "%s line %d of %d --%d%%-- (%dx%d)",
3557 (cfn != 0 ? (char *) cfn : "No file"),
3558 #ifdef BB_FEATURE_VI_READONLY
3559 (readonly == TRUE ? " [Read only]" : ""),
3560 #endif /* BB_FEATURE_VI_READONLY */
3561 (file_modified == TRUE ? " [modified]" : ""),
3562 cur, tot, percent, rows, columns);
3565 //----- Force refresh of all Lines -----------------------------
3566 static void redraw(int full_screen)
3568 place_cursor(0, 0); // put cursor in correct place
3569 clear_to_eos(); // tel terminal to erase display
3570 screen_erase(); // erase the internal screen buffer
3571 refresh(full_screen); // this will redraw the entire display
3574 //----- Refresh the changed screen lines -----------------------
3575 // Copy the source line from text[] into the buffer and note
3576 // if the current screenline is different from the new buffer.
3577 // If they differ then that line needs redrawing on the terminal.
3579 static void refresh(int full_screen)
3581 static int old_offset;
3582 int li, co, changed;
3583 Byte c, buf[MAX_SCR_COLS];
3584 Byte *tp, *sp; // pointer into text[] and screen[]
3586 #ifdef BB_FEATURE_VI_WIN_RESIZE
3588 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3589 sync_cursor(dot, &crow, &ccol);
3590 tp = screenbegin; // index into text[] of top line
3591 // compare text[] to screen[] and mark screen[] lines that need updating
3592 for (li = 0; li < rows - 1; li++) {
3593 // format current text line into buf with "columns" wide
3594 for (co = 0; co < columns + offset;) {
3595 c = ' '; // assume blank
3596 if (li > 0 && co == 0) {
3597 c = '~'; // not first line, assume Tilde
3599 // are there chars in text[]
3600 // and have we gone past the end
3601 if (text < end && tp < end) {
3606 if (c < ' ' || c > '~') {
3610 for (; (co % tabstop) != (tabstop - 1); co++) {
3615 c |= '@'; // make it visible
3616 c &= 0x7f; // get rid of hi bit
3619 // the co++ is done here so that the column will
3620 // not be overwritten when we blank-out the rest of line
3625 if (co >= columns + offset) {
3626 // skip to the end of the current text[] line
3627 while (tp < end && *tp++ != '\n');
3629 // try to keep the cursor near it's current position
3630 // remember how many chars in this row- where the cursor sits
3631 // blank out the rest of the buffer
3632 while (co < MAX_SCR_COLS - 1) {
3635 buf[co++] = 0; // NULL terminate the buffer
3637 // if necessary, update virtual screen[] and terminal from buf[]
3638 changed = FALSE; // assume no change
3639 sp = &screen[li * columns]; // start of screen line
3640 for (co = 0; co < columns; co++) {
3641 if (sp[co] != buf[co + offset]) {
3642 sp[co] = buf[co + offset];
3643 changed = TRUE; // mark for redraw
3646 // if horz offset has changed, force a redraw
3647 if (offset != old_offset)
3650 // write all marked screen lines out to terminal
3651 if (changed == TRUE) {
3652 place_cursor(li, 0); // put cursor in correct place
3653 clear_to_eol(); // Erase to end of line
3654 if (full_screen == FALSE) {
3655 // don't redraw every column on terminal
3656 // look backwards for last non-blank
3657 for (co = columns + offset; co >= 0; co--) {
3664 // redraw every column on terminal
3667 // write line out to terminal
3668 write(1, buf + offset, co);
3672 place_cursor(crow, ccol);
3673 if (offset != old_offset)
3674 old_offset = offset;