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.3 2001/04/04 19:33:32 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)
301 #ifdef BB_FEATURE_VI_YANKMARK
303 #endif /* BB_FEATURE_VI_YANKMARK */
305 SOs = "\033[7m"; // Terminal standout mode on
306 SOn = "\033[0m"; // Terminal standout mode off
307 #ifdef BB_FEATURE_VI_CRASHME
308 (void) srand((long) getpid());
309 #endif /* BB_FEATURE_VI_CRASHME */
310 status_buffer = (Byte *) malloc(200); // hold messages to user
311 #ifdef BB_FEATURE_VI_READONLY
313 if (strncmp(argv[0], "view", 4) == 0) {
316 #endif /* BB_FEATURE_VI_READONLY */
317 #ifdef BB_FEATURE_VI_SETOPTS
321 #endif /* BB_FEATURE_VI_SETOPTS */
322 #ifdef BB_FEATURE_VI_YANKMARK
323 for (i = 0; i < 28; i++) {
325 } // init the yank regs
326 #endif /* BB_FEATURE_VI_YANKMARK */
327 #ifdef BB_FEATURE_VI_DOT_CMD
328 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
329 #endif /* BB_FEATURE_VI_DOT_CMD */
331 // 1- process $HOME/.exrc file
332 // 2- process EXINIT variable from environment
333 // 3- process command line args
334 while ((c = getopt(argc, argv, "hCR")) != -1) {
336 #ifdef BB_FEATURE_VI_CRASHME
340 #endif /* BB_FEATURE_VI_CRASHME */
341 #ifdef BB_FEATURE_VI_READONLY
342 case 'R': // Read-only flag
345 #endif /* BB_FEATURE_VI_READONLY */
346 //case 'r': // recover flag- ignore- we don't use tmp file
347 //case 'x': // encryption flag- ignore
348 //case 'c': // execute command first
349 //case 'h': // help -- just use default
356 // The argv array can be used by the ":next" and ":rewind" commands
358 fn_start = optind; // remember first file name for :next and :rew
361 //----- This is the main file handling loop --------------
362 if (optind >= argc) {
363 editing = 1; // 0= exit, 1= one file, 2= multiple files
366 for (; optind < argc; optind++) {
367 editing = 1; // 0=exit, 1=one file, 2+ =many files
370 cfn = (Byte *) strdup(argv[optind]);
374 //-----------------------------------------------------------
379 static void edit_file(Byte * fn)
384 #ifdef BB_FEATURE_VI_USE_SIGNALS
387 #endif /* BB_FEATURE_VI_USE_SIGNALS */
388 #ifdef BB_FEATURE_VI_YANKMARK
389 static Byte *cur_line;
390 #endif /* BB_FEATURE_VI_YANKMARK */
395 #ifdef BB_FEATURE_VI_WIN_RESIZE
397 #endif /* BB_FEATURE_VI_WIN_RESIZE */
398 new_screen(rows, columns); // get memory for virtual screen
400 cnt = file_size(fn); // file size
401 size = 2 * cnt; // 200% of file size
402 new_text(size); // get a text[] buffer
403 screenbegin = dot = end = text;
405 file_insert(fn, text, cnt);
407 (void) char_insert(text, '\n'); // start empty buf with dummy line
409 file_modified = FALSE;
410 #ifdef BB_FEATURE_VI_YANKMARK
411 YDreg = 26; // default Yank/Delete reg
412 Ureg = 27; // hold orig line for "U" cmd
413 for (cnt = 0; cnt < 28; cnt++) {
416 mark[26] = mark[27] = text; // init "previous context"
417 #endif /* BB_FEATURE_VI_YANKMARK */
419 err_method = 1; // flash
420 last_forward_char = last_input_char = '\0';
425 #ifdef BB_FEATURE_VI_USE_SIGNALS
426 signal(SIGHUP, catch_sig);
427 signal(SIGINT, catch_sig);
428 signal(SIGALRM, alarm_sig);
429 signal(SIGTERM, catch_sig);
430 signal(SIGQUIT, core_sig);
431 signal(SIGILL, core_sig);
432 signal(SIGTRAP, core_sig);
433 signal(SIGIOT, core_sig);
434 signal(SIGABRT, core_sig);
435 signal(SIGFPE, core_sig);
436 signal(SIGBUS, core_sig);
437 signal(SIGSEGV, core_sig);
439 signal(SIGSYS, core_sig);
441 signal(SIGWINCH, winch_sig);
442 signal(SIGTSTP, suspend_sig);
443 sig = setjmp(restart);
447 msg = "(window resize)";
457 msg = "(I tried to touch invalid memory)";
461 psbs("-- caught signal %d %s--", sig, msg);
463 #endif /* BB_FEATURE_VI_USE_SIGNALS */
466 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
469 offset = 0; // no horizontal offset
471 #ifdef BB_FEATURE_VI_DOT_CMD
472 if (last_modifying_cmd != 0)
473 free(last_modifying_cmd);
474 if (ioq_start != NULL)
476 ioq = ioq_start = last_modifying_cmd = 0;
478 #endif /* BB_FEATURE_VI_DOT_CMD */
482 //------This is the main Vi cmd handling loop -----------------------
483 while (editing > 0) {
484 #ifdef BB_FEATURE_VI_CRASHME
486 if ((end - text) > 1) {
487 crash_dummy(); // generate a random command
491 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
495 #endif /* BB_FEATURE_VI_CRASHME */
496 last_input_char = c = get_one_char(); // get a cmd from user
497 #ifdef BB_FEATURE_VI_YANKMARK
498 // save a copy of the current line- for the 'U" command
499 if (begin_line(dot) != cur_line) {
500 cur_line = begin_line(dot);
501 text_yank(begin_line(dot), end_line(dot), Ureg);
503 #endif /* BB_FEATURE_VI_YANKMARK */
504 #ifdef BB_FEATURE_VI_DOT_CMD
505 // These are commands that change text[].
506 // Remember the input for the "." command
507 if (!adding2q && ioq_start == 0
508 && strchr((char *) modifying_cmds, c) != NULL) {
511 #endif /* BB_FEATURE_VI_DOT_CMD */
512 do_cmd(c); // execute the user command
514 // poll to see if there is input already waiting. if we are
515 // not able to display output fast enough to keep up, skip
516 // the display update until we catch up with input.
517 if (mysleep(0) == 0) {
518 // no input pending- so update output
522 #ifdef BB_FEATURE_VI_CRASHME
524 crash_test(); // test editor variables
525 #endif /* BB_FEATURE_VI_CRASHME */
527 //-------------------------------------------------------------------
529 place_cursor(rows, 0); // go to bottom of screen
530 clear_to_eol(); // Erase to end of line
534 static Byte readbuffer[BUFSIZ];
536 #ifdef BB_FEATURE_VI_CRASHME
537 static int totalcmds = 0;
538 static int Mp = 85; // Movement command Probability
539 static int Np = 90; // Non-movement command Probability
540 static int Dp = 96; // Delete command Probability
541 static int Ip = 97; // Insert command Probability
542 static int Yp = 98; // Yank command Probability
543 static int Pp = 99; // Put command Probability
544 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
545 char chars[20] = "\t012345 abcdABCD-=.$";
546 char *words[20] = { "this", "is", "a", "test",
547 "broadcast", "the", "emergency", "of",
548 "system", "quick", "brown", "fox",
549 "jumped", "over", "lazy", "dogs",
550 "back", "January", "Febuary", "March"
553 "You should have received a copy of the GNU General Public License\n",
554 "char c, cm, *cmd, *cmd1;\n",
555 "generate a command by percentages\n",
556 "Numbers may be typed as a prefix to some commands.\n",
557 "Quit, discarding changes!\n",
558 "Forced write, if permission originally not valid.\n",
559 "In general, any ex or ed command (such as substitute or delete).\n",
560 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
561 "Please get w/ me and I will go over it with you.\n",
562 "The following is a list of scheduled, committed changes.\n",
563 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
564 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
565 "Any question about transactions please contact Sterling Huxley.\n",
566 "I will try to get back to you by Friday, December 31.\n",
567 "This Change will be implemented on Friday.\n",
568 "Let me know if you have problems accessing this;\n",
569 "Sterling Huxley recently added you to the access list.\n",
570 "Would you like to go to lunch?\n",
571 "The last command will be automatically run.\n",
572 "This is too much english for a computer geek.\n",
574 char *multilines[20] = {
575 "You should have received a copy of the GNU General Public License\n",
576 "char c, cm, *cmd, *cmd1;\n",
577 "generate a command by percentages\n",
578 "Numbers may be typed as a prefix to some commands.\n",
579 "Quit, discarding changes!\n",
580 "Forced write, if permission originally not valid.\n",
581 "In general, any ex or ed command (such as substitute or delete).\n",
582 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
583 "Please get w/ me and I will go over it with you.\n",
584 "The following is a list of scheduled, committed changes.\n",
585 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
586 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
587 "Any question about transactions please contact Sterling Huxley.\n",
588 "I will try to get back to you by Friday, December 31.\n",
589 "This Change will be implemented on Friday.\n",
590 "Let me know if you have problems accessing this;\n",
591 "Sterling Huxley recently added you to the access list.\n",
592 "Would you like to go to lunch?\n",
593 "The last command will be automatically run.\n",
594 "This is too much english for a computer geek.\n",
597 // create a random command to execute
598 static void crash_dummy()
600 static int sleeptime; // how long to pause between commands
601 char c, cm, *cmd, *cmd1;
602 int i, cnt, thing, rbi, startrbi, percent;
604 // "dot" movement commands
605 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
607 // is there already a command running?
608 if (strlen((char *) readbuffer) > 0)
612 sleeptime = 0; // how long to pause between commands
613 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
614 // generate a command by percentages
615 percent = (int) lrand48() % 100; // get a number from 0-99
616 if (percent < Mp) { // Movement commands
617 // available commands
620 } else if (percent < Np) { // non-movement commands
621 cmd = "mz<>\'\""; // available commands
623 } else if (percent < Dp) { // Delete commands
624 cmd = "dx"; // available commands
626 } else if (percent < Ip) { // Inset commands
627 cmd = "iIaAsrJ"; // available commands
629 } else if (percent < Yp) { // Yank commands
630 cmd = "yY"; // available commands
632 } else if (percent < Pp) { // Put commands
633 cmd = "pP"; // available commands
636 // We do not know how to handle this command, try again
640 // randomly pick one of the available cmds from "cmd[]"
641 i = (int) lrand48() % strlen(cmd);
643 if (strchr(":\024", cm))
644 goto cd0; // dont allow these commands
645 readbuffer[rbi++] = cm; // put cmd into input buffer
647 // now we have the command-
648 // there are 1, 2, and multi char commands
649 // find out which and generate the rest of command as necessary
650 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
651 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
652 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
653 cmd1 = "abcdefghijklmnopqrstuvwxyz";
655 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
657 readbuffer[rbi++] = c; // add movement to input buffer
659 if (strchr("iIaAsc", cm)) { // multi-char commands
662 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
664 readbuffer[rbi++] = c; // add movement to input buffer
666 thing = (int) lrand48() % 4; // what thing to insert
667 cnt = (int) lrand48() % 10; // how many to insert
668 for (i = 0; i < cnt; i++) {
669 if (thing == 0) { // insert chars
670 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
671 } else if (thing == 1) { // insert words
672 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
673 strcat((char *) readbuffer, " ");
674 sleeptime = 0; // how fast to type
675 } else if (thing == 2) { // insert lines
676 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
677 sleeptime = 0; // how fast to type
678 } else { // insert multi-lines
679 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
680 sleeptime = 0; // how fast to type
683 strcat((char *) readbuffer, "\033");
688 (void) mysleep(sleeptime); // sleep 1/100 sec
691 // test to see if there are any errors
692 static void crash_test()
694 static time_t oldtim;
696 char d[2], buf[100], msg[BUFSIZ];
700 strcat((char *) msg, "end<text ");
703 strcat((char *) msg, "end>textend ");
706 strcat((char *) msg, "dot<text ");
709 strcat((char *) msg, "dot>end ");
711 if (screenbegin < text) {
712 strcat((char *) msg, "screenbegin<text ");
714 if (screenbegin > end - 1) {
715 strcat((char *) msg, "screenbegin>end-1 ");
718 if (strlen(msg) > 0) {
720 sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
721 write(1, buf, strlen(buf));
722 write(1, msg, strlen(msg));
723 write(1, "\n\n\n", 3);
724 write(1, "\033[7m[Hit return to continue]\033[0m", 32);
725 while (read(0, d, 1) > 0) {
726 if (d[0] == '\n' || d[0] == '\r')
731 tim = (time_t) time((time_t *) 0);
732 if (tim >= (oldtim + 3)) {
733 sprintf((char *) status_buffer,
734 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
735 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
740 #endif /* BB_FEATURE_VI_CRASHME */
742 //---------------------------------------------------------------------
743 //----- the Ascii Chart -----------------------------------------------
745 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
746 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
747 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
748 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
749 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
750 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
751 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
752 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
753 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
754 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
755 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
756 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
757 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
758 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
759 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
760 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
761 //---------------------------------------------------------------------
763 //----- Execute a Vi Command -----------------------------------
764 static void do_cmd(Byte c)
766 Byte c1, *p, *q, *msg, buf[9], *save_dot;
767 int cnt, i, j, dir, yf;
769 c1 = c; // quiet the compiler
770 cnt = yf = dir = 0; // quiet the compiler
771 p = q = save_dot = msg = buf; // quiet the compiler
772 memset(buf, '\0', 9); // clear buf
774 // we are 'R'eplacing the current *dot with new char
776 // don't Replace past E-o-l
777 cmd_mode = 1; // convert to insert
779 if (1 <= c && c <= 127) { // only ASCII chars
781 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
782 dot = char_insert(dot, c); // insert new char
788 // insert the char c at "dot"
789 if (1 <= c && c <= 127) {
790 dot = char_insert(dot, c); // only ASCII chars
805 case 0x14: // dc4 ctrl-T
806 #ifdef BB_FEATURE_VI_CRASHME
807 crashme = (crashme == 0) ? 1 : 0;
808 #endif /* BB_FEATURE_VI_CRASHME */
843 default: // unrecognised command
852 end_cmd_q(); // stop adding to q
853 case 0x00: // nul- ignore
855 case 2: // ctrl-B scroll up full screen
856 case VI_K_PAGEUP: // Cursor Key Page Up
857 dot_scroll(rows - 2, -1);
859 #ifdef BB_FEATURE_VI_USE_SIGNALS
860 case 0x03: // ctrl-C interrupt
863 case 26: // ctrl-Z suspend
864 suspend_sig(SIGTSTP);
866 #endif /* BB_FEATURE_VI_USE_SIGNALS */
867 case 4: // ctrl-D scroll down half screen
868 dot_scroll((rows - 2) / 2, 1);
870 case 5: // ctrl-E scroll down one line
873 case 6: // ctrl-F scroll down full screen
874 case VI_K_PAGEDOWN: // Cursor Key Page Down
875 dot_scroll(rows - 2, 1);
877 case 7: // ctrl-G show current status
880 case 'h': // h- move left
881 case VI_K_LEFT: // cursor key Left
882 case 8: // ^h- move left (This may be ERASE char)
883 case 127: // DEL- move left (This may be ERASE char)
889 case 10: // Newline ^J
890 case 'j': // j- goto next line, same col
891 case VI_K_DOWN: // cursor key Down
895 dot_next(); // go to next B-o-l
896 dot = move_to_col(dot, ccol + offset); // try stay in same col
898 case 12: // ctrl-L force redraw whole screen
899 place_cursor(0, 0); // put cursor in correct place
900 clear_to_eos(); // tel terminal to erase display
902 screen_erase(); // erase the internal screen buffer
903 refresh(TRUE); // this will redraw the entire display
905 case 13: // Carriage Return ^M
906 case '+': // +- goto next line
913 case 21: // ctrl-U scroll up half screen
914 dot_scroll((rows - 2) / 2, -1);
916 case 25: // ctrl-Y scroll up one line
922 cmd_mode = 0; // stop insrting
924 *status_buffer = '\0'; // clear status buffer
926 case ' ': // move right
927 case 'l': // move right
928 case VI_K_RIGHT: // Cursor Key Right
934 #ifdef BB_FEATURE_VI_YANKMARK
935 case '"': // "- name a register to use for Delete/Yank
944 case '\'': // '- goto a specific mark
951 if (text <= q && q < end) {
953 dot_begin(); // go to B-o-l
956 } else if (c1 == '\'') { // goto previous context
957 dot = swap_context(dot); // swap current and previous context
958 dot_begin(); // go to B-o-l
964 case 'm': // m- Mark a line
965 // this is really stupid. If there are any inserts or deletes
966 // between text[0] and dot then this mark will not point to the
967 // correct location! It could be off by many lines!
968 // Well..., at least its quick and dirty.
974 mark[(int) c1] = dot;
979 case 'P': // P- Put register before
980 case 'p': // p- put register after
983 psbs("Nothing in register %c", what_reg());
986 // are we putting whole lines or strings
987 if (strchr((char *) p, '\n') != NULL) {
989 dot_begin(); // putting lines- Put above
992 // are we putting after very last line?
993 if (end_line(dot) == (end - 1)) {
994 dot = end; // force dot to end of text[]
996 dot_next(); // next line, then put before
1001 dot_right(); // move to right, can move to NL
1003 dot = string_insert(dot, p); // insert the string
1004 end_cmd_q(); // stop adding to q
1007 case 'U': // U- Undo; replace current line with original version
1008 if (reg[Ureg] != 0) {
1009 p = begin_line(dot);
1011 p = text_hole_delete(p, q); // delete cur line
1012 p = string_insert(p, reg[Ureg]); // insert orig line
1017 #endif /* BB_FEATURE_VI_YANKMARK */
1018 case '$': // $- goto end of line
1019 case VI_K_END: // Cursor Key End
1023 dot = end_line(dot + 1);
1025 case '%': // %- find matching char of pair () [] {}
1026 for (q = dot; q < end && *q != '\n'; q++) {
1027 if (strchr("()[]{}", *q) != NULL) {
1028 // we found half of a pair
1029 p = find_pair(q, *q);
1041 case 'f': // f- forward to a user specified char
1042 last_forward_char = get_one_char(); // get the search char
1044 // dont seperate these two commands. 'f' depends on ';'
1046 //**** fall thru to ... 'i'
1047 case ';': // ;- look at rest of line for last forward char
1052 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1055 if (*q == last_forward_char)
1058 case '-': // -- goto prev line
1065 #ifdef BB_FEATURE_VI_DOT_CMD
1066 case '.': // .- repeat the last modifying command
1067 // Stuff the last_modifying_cmd back into stdin
1068 // and let it be re-executed.
1069 if (last_modifying_cmd != 0) {
1070 ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
1073 #endif /* BB_FEATURE_VI_DOT_CMD */
1074 #ifdef BB_FEATURE_VI_SEARCH
1075 case '?': // /- search for a pattern
1076 case '/': // /- search for a pattern
1079 q = get_input_line(buf); // get input line- use "status line"
1080 if (strlen((char *) q) == 1)
1081 goto dc3; // if no pat re-use old pat
1082 if (strlen((char *) q) > 1) { // new pat- save it and find
1083 // there is a new pat
1084 if (last_search_pattern != 0) {
1085 free(last_search_pattern);
1087 last_search_pattern = (Byte *) strdup((char *) q);
1088 goto dc3; // now find the pattern
1090 // user changed mind and erased the "/"- do nothing
1092 case 'N': // N- backward search for last pattern
1096 dir = BACK; // assume BACKWARD search
1098 if (last_search_pattern[0] == '?') {
1102 goto dc4; // now search for pattern
1104 case 'n': // n- repeat search for last pattern
1105 // search rest of text[] starting at next char
1106 // if search fails return orignal "p" not the "p+1" address
1111 if (last_search_pattern == 0) {
1112 msg = (Byte *) "No previous regular expression";
1115 if (last_search_pattern[0] == '/') {
1116 dir = FORWARD; // assume FORWARD search
1119 if (last_search_pattern[0] == '?') {
1124 q = char_search(p, last_search_pattern + 1, dir, FULL);
1126 dot = q; // good search, update "dot"
1130 // no pattern found between "dot" and "end"- continue at top
1135 q = char_search(p, last_search_pattern + 1, dir, FULL);
1136 if (q != NULL) { // found something
1137 dot = q; // found new pattern- goto it
1138 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1140 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1143 msg = (Byte *) "Pattern not found";
1148 case '{': // {- move backward paragraph
1149 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1150 if (q != NULL) { // found blank line
1151 dot = next_line(q); // move to next blank line
1154 case '}': // }- move forward paragraph
1155 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1156 if (q != NULL) { // found blank line
1157 dot = next_line(q); // move to next blank line
1160 #endif /* BB_FEATURE_VI_SEARCH */
1161 case '0': // 0- goto begining of line
1171 if (c == '0' && cmdcnt < 1) {
1172 dot_begin(); // this was a standalone zero
1174 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1177 case ':': // :- the colon mode commands
1178 #ifdef BB_FEATURE_VI_COLON
1179 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1180 colon(p); // execute the command
1181 #else /* BB_FEATURE_VI_COLON */
1182 *status_buffer = '\0'; // clear the status buffer
1183 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1184 clear_to_eol(); // clear the line
1185 write(1, ":", 1); // write out the : prompt
1186 for (cnt = 0; cnt < 8; cnt++) {
1187 c1 = get_one_char();
1188 if (c1 == '\n' || c1 == '\r') {
1192 buf[cnt + 1] = '\0';
1193 write(1, buf + cnt, 1); // echo the char
1195 cnt = strlen((char *) buf);
1196 if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
1197 strncasecmp((char *) buf, "q!", cnt) == 0) { // delete lines
1198 if (file_modified == TRUE && buf[1] != '!') {
1199 psbs("No write since last change (:quit! overrides)");
1203 } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
1204 strncasecmp((char *) buf, "wq", cnt) == 0) {
1205 cnt = file_write(cfn, text, end - 1);
1206 file_modified = FALSE;
1207 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1208 if (buf[1] == 'q') {
1211 } else { // unrecognised cmd
1214 #endif /* BB_FEATURE_VI_COLON */
1216 case '<': // <- Left shift something
1217 case '>': // >- Right shift something
1218 cnt = count_lines(text, dot); // remember what line we are on
1219 c1 = get_one_char(); // get the type of thing to delete
1220 find_range(&p, &q, c1);
1221 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1224 i = count_lines(p, q); // # of lines we are shifting
1225 for ( ; i > 0; i--, p = next_line(p)) {
1227 // shift left- remove tab or 8 spaces
1229 // shrink buffer 1 char
1230 (void) text_hole_delete(p, p);
1231 } else if (*p == ' ') {
1232 // we should be calculating columns, not just SPACE
1233 for (j = 0; *p == ' ' && j < tabstop; j++) {
1234 (void) text_hole_delete(p, p);
1237 } else if (c == '>') {
1238 // shift right -- add tab or 8 spaces
1239 (void) char_insert(p, '\t');
1242 dot = find_line(cnt); // what line were we on
1244 end_cmd_q(); // stop adding to q
1246 case 'A': // A- append at e-o-l
1247 dot_end(); // go to e-o-l
1248 //**** fall thru to ... 'a'
1249 case 'a': // a- append after current char
1254 case 'B': // B- back a blank-delimited Word
1255 case 'E': // E- end of a blank-delimited word
1256 case 'W': // W- forward a blank-delimited word
1263 if (c == 'W' || isspace(dot[dir])) {
1264 dot = skip_thing(dot, 1, dir, S_TO_WS);
1265 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1268 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1270 case 'C': // C- Change to e-o-l
1271 case 'D': // D- delete to e-o-l
1273 dot = dollar_line(dot); // move to before NL
1274 // copy text into a register and delete
1275 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1277 goto dc_i; // start inserting
1278 #ifdef BB_FEATURE_VI_DOT_CMD
1280 end_cmd_q(); // stop adding to q
1281 #endif /* BB_FEATURE_VI_DOT_CMD */
1283 case 'H': // H- goto top line on screen
1285 if (cmdcnt > (rows - 1)) {
1286 cmdcnt = (rows - 1);
1293 case 'I': // I- insert before first non-blank
1296 //**** fall thru to ... 'i'
1297 case 'i': // i- insert before current char
1298 case VI_K_INSERT: // Cursor Key Insert
1300 cmd_mode = 1; // start insrting
1301 psb("-- Insert --");
1303 case 'J': // J- join current and next lines together
1307 dot_end(); // move to NL
1308 if (dot < end - 1) { // make sure not last char in text[]
1309 *dot++ = ' '; // replace NL with space
1310 while (isblnk(*dot)) { // delete leading WS
1314 end_cmd_q(); // stop adding to q
1316 case 'L': // L- goto bottom line on screen
1318 if (cmdcnt > (rows - 1)) {
1319 cmdcnt = (rows - 1);
1327 case 'O': // O- open a empty line above
1329 p = begin_line(dot);
1330 if (p[-1] == '\n') {
1332 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1334 dot = char_insert(dot, '\n');
1337 dot = char_insert(dot, '\n'); // i\n\033
1342 case 'R': // R- continuous Replace char
1344 psb("-- Replace --");
1346 case 'X': // X- delete char before dot
1347 case 'x': // x- delete the current char
1348 case 's': // s- substitute the current char
1355 if (dot[dir] != '\n') {
1357 dot--; // delete prev char
1358 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1361 goto dc_i; // start insrting
1362 end_cmd_q(); // stop adding to q
1364 case 'Z': // Z- if modified, {write}; exit
1365 // ZZ means to save file (if necessary), then exit
1366 c1 = get_one_char();
1371 if (file_modified == TRUE
1372 #ifdef BB_FEATURE_VI_READONLY
1373 && readonly == FALSE
1374 #endif /* BB_FEATURE_VI_READONLY */
1376 cnt = file_write(cfn, text, end - 1);
1377 if (cnt == (end - 1 - text + 1)) {
1384 case '^': // ^- move to first non-blank on line
1388 case 'b': // b- back a word
1389 case 'e': // e- end of word
1396 if ((dot + dir) < text || (dot + dir) > end - 1)
1399 if (isspace(*dot)) {
1400 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1402 if (isalnum(*dot) || *dot == '_') {
1403 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1404 } else if (ispunct(*dot)) {
1405 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1408 case 'c': // c- change something
1409 case 'd': // d- delete something
1410 #ifdef BB_FEATURE_VI_YANKMARK
1411 case 'y': // y- yank something
1412 case 'Y': // Y- Yank a line
1413 #endif /* BB_FEATURE_VI_YANKMARK */
1414 yf = YANKDEL; // assume either "c" or "d"
1415 #ifdef BB_FEATURE_VI_YANKMARK
1416 if (c == 'y' || c == 'Y')
1418 #endif /* BB_FEATURE_VI_YANKMARK */
1421 c1 = get_one_char(); // get the type of thing to delete
1422 find_range(&p, &q, c1);
1423 if (c1 == 27) { // ESC- user changed mind and wants out
1424 c = c1 = 27; // Escape- do nothing
1425 } else if (strchr("wW", c1)) {
1427 // don't include trailing WS as part of word
1428 while (isblnk(*q)) {
1429 if (q <= text || q[-1] == '\n')
1434 dot = yank_delete(p, q, 0, yf); // delete word
1435 } else if (strchr("0bBeE$", c1)) {
1436 // single line copy text into a register and delete
1437 dot = yank_delete(p, q, 0, yf); // delete word
1438 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
1439 // multiple line copy text into a register and delete
1440 dot = yank_delete(p, q, 1, yf); // delete lines
1446 // could not recognize object
1447 c = c1 = 27; // error-
1451 // if CHANGING, not deleting, start inserting after the delete
1453 strcpy((char *) buf, "Change");
1454 goto dc_i; // start inserting
1457 strcpy((char *) buf, "Delete");
1459 #ifdef BB_FEATURE_VI_YANKMARK
1460 if (c == 'y' || c == 'Y') {
1461 strcpy((char *) buf, "Yank");
1464 q = p + strlen((char *) p);
1465 for (cnt = 0; p <= q; p++) {
1469 psb("%s %d lines (%d chars) using [%c]",
1470 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1471 #endif /* BB_FEATURE_VI_YANKMARK */
1472 end_cmd_q(); // stop adding to q
1475 case 'k': // k- goto prev line, same col
1476 case VI_K_UP: // cursor key Up
1481 dot = move_to_col(dot, ccol + offset); // try stay in same col
1483 case 'r': // r- replace the current char with user input
1484 c1 = get_one_char(); // get the replacement char
1487 file_modified = TRUE; // has the file been modified
1489 end_cmd_q(); // stop adding to q
1491 case 'w': // w- forward a word
1495 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1496 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1497 } else if (ispunct(*dot)) { // we are on PUNCT
1498 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1501 dot++; // move over word
1502 if (isspace(*dot)) {
1503 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1507 c1 = get_one_char(); // get the replacement char
1510 cnt = (rows - 2) / 2; // put dot at center
1512 cnt = rows - 2; // put dot at bottom
1513 screenbegin = begin_line(dot); // start dot at top
1514 dot_scroll(cnt, -1);
1516 case '|': // |- move to column "cmdcnt"
1517 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1519 case '~': // ~- flip the case of letters a-z -> A-Z
1523 if (islower(*dot)) {
1524 *dot = toupper(*dot);
1525 file_modified = TRUE; // has the file been modified
1526 } else if (isupper(*dot)) {
1527 *dot = tolower(*dot);
1528 file_modified = TRUE; // has the file been modified
1531 end_cmd_q(); // stop adding to q
1533 //----- The Cursor and Function Keys -----------------------------
1534 case VI_K_HOME: // Cursor Key Home
1537 // The Fn keys could point to do_macro which could translate them
1538 case VI_K_FUN1: // Function Key F1
1539 case VI_K_FUN2: // Function Key F2
1540 case VI_K_FUN3: // Function Key F3
1541 case VI_K_FUN4: // Function Key F4
1542 case VI_K_FUN5: // Function Key F5
1543 case VI_K_FUN6: // Function Key F6
1544 case VI_K_FUN7: // Function Key F7
1545 case VI_K_FUN8: // Function Key F8
1546 case VI_K_FUN9: // Function Key F9
1547 case VI_K_FUN10: // Function Key F10
1548 case VI_K_FUN11: // Function Key F11
1549 case VI_K_FUN12: // Function Key F12
1554 // if text[] just became empty, add back an empty line
1556 (void) char_insert(text, '\n'); // start empty buf with dummy line
1559 // it is OK for dot to exactly equal to end, otherwise check dot validity
1561 dot = bound_dot(dot); // make sure "dot" is valid
1563 #ifdef BB_FEATURE_VI_YANKMARK
1564 check_context(c); // update the current context
1565 #endif /* BB_FEATURE_VI_YANKMARK */
1568 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1569 cnt = dot - begin_line(dot);
1570 // Try to stay off of the Newline
1571 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1575 //----- The Colon commands -------------------------------------
1576 #ifdef BB_FEATURE_VI_COLON
1577 static Byte *get_address(Byte * p, int *addr) // get colon addr, if present
1582 #ifdef BB_FEATURE_VI_YANKMARK
1584 #endif /* BB_FEATURE_VI_YANKMARK */
1585 #ifdef BB_FEATURE_VI_SEARCH
1586 Byte *pat, buf[1024];
1587 #endif /* BB_FEATURE_VI_SEARCH */
1589 *addr = -1; // assume no addr
1590 if (*p == '.') { // the current line
1592 q = begin_line(dot);
1593 *addr = count_lines(text, q);
1594 #ifdef BB_FEATURE_VI_YANKMARK
1595 } else if (*p == '\'') { // is this a mark addr
1599 if (c >= 'a' && c <= 'z') {
1603 if (q != NULL) { // is mark valid
1604 *addr = count_lines(text, q); // count lines
1607 #endif /* BB_FEATURE_VI_YANKMARK */
1608 #ifdef BB_FEATURE_VI_SEARCH
1609 } else if (*p == '/') { // a search pattern
1611 for (p++; *p; p++) {
1617 pat = (Byte *) strdup((char *) buf); // save copy of pattern
1620 q = char_search(dot, pat, FORWARD, FULL);
1622 *addr = count_lines(text, q);
1625 #endif /* BB_FEATURE_VI_SEARCH */
1626 } else if (*p == '$') { // the last line in file
1628 q = begin_line(end - 1);
1629 *addr = count_lines(text, q);
1630 } else if (isdigit(*p)) { // specific line number
1631 sscanf((char *) p, "%d%n", addr, &st);
1633 } else { // I don't reconise this
1634 // unrecognised address- assume -1
1640 static void colon(Byte * buf)
1642 Byte c, *orig_buf, *buf1, *q, *r;
1643 Byte *fn, cmd[100], args[100];
1644 int i, l, li, ch, st, b, e;
1645 int useforce, forced;
1647 // :3154 // if (-e line 3154) goto it else stay put
1648 // :4,33w! foo // write a portion of buffer to file "foo"
1649 // :w // write all of buffer to current file
1651 // :q! // quit- dont care about modified file
1652 // :'a,'z!sort -u // filter block through sort
1653 // :'f // goto mark "f"
1654 // :'fl // list literal the mark "f" line
1655 // :.r bar // read file "bar" into buffer before dot
1656 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1657 // :/xyz/ // goto the "xyz" line
1658 // :s/find/replace/ // substitute pattern "find" with "replace"
1660 if (strlen((char *) buf) <= 0)
1663 buf++; // move past the ':'
1665 forced = useforce = FALSE;
1666 li = st = ch = i = 0;
1668 q = text; // assume 1,$ for the range
1670 li = count_lines(text, end - 1);
1671 fn = cfn; // default to current file
1673 // look for optional FIRST address(es) :. :1 :1,9 :'q,'a
1674 while (isblnk(*buf))
1677 // get FIRST addr, if present
1678 buf = get_address(buf, &b);
1679 while (isblnk(*buf))
1683 while (isblnk(*buf))
1685 // look for SECOND address
1686 buf = get_address(buf, &e);
1688 while (isblnk(*buf))
1691 // remember orig command line
1694 // get the COMMAND into cmd[]
1697 while (*buf != '\0') {
1703 // get any ARGuments
1704 while (isblnk(*buf))
1706 strcpy((char *) args, (char *) buf);
1707 if (cmd[strlen((char *) cmd) - 1] == '!') {
1709 cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
1712 // if there is only one addr, then the addr
1713 // is the line number of the single line the
1714 // user wants. So, reset the end
1715 // pointer to point at end of the "b" line
1716 q = find_line(b); // what line is #b
1721 // we were given two addrs. change the
1722 // end pointer to the addr given by user.
1723 r = find_line(e); // what line is #e
1727 // ------------ now look for the command ------------
1728 i = strlen((char *) cmd);
1729 if (i == 0) { // :123CR goto line #123
1731 dot = find_line(b); // what line is #b
1734 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1735 if (b < 0) { // no addr given- use defaults
1736 b = e = count_lines(text, dot);
1739 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1740 if (b < 0) { // no addr given- use defaults
1741 q = begin_line(dot); // assume .,. for the range
1744 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1746 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1747 // don't exit if the file been modified
1748 if (file_modified == TRUE && useforce != TRUE) {
1749 psbs("No write since last change (:edit! overrides)");
1753 if (strlen((char *) fn) <= 0) {
1754 // no file name given, re-edit current file
1759 cfn = (Byte *) strdup((char *) fn); // make this the current file
1760 // delete all the contents of text[]
1761 new_text(2 * file_size(fn));
1762 screenbegin = dot = end = text;
1765 ch = file_insert(fn, text, file_size(fn));
1767 file_modified = FALSE;
1768 #ifdef BB_FEATURE_VI_YANKMARK
1770 free(reg[Ureg]); // free orig line reg- for 'U'
1771 if (reg[YDreg] != 0)
1772 free(reg[YDreg]); // free default yank/delete register
1773 for (li = 0; li < 28; li++) {
1776 #endif /* BB_FEATURE_VI_YANKMARK */
1777 // how many lines in text[]?
1778 li = count_lines(text, end - 1);
1779 psb("\"%s\" %dL, %dC", cfn, li, ch);
1780 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1781 if (b != -1 || e != -1) {
1782 ni((Byte *) "No address allowed on this command");
1785 if (strlen((char *) args) > 0) {
1786 // user wants a new filename
1789 cfn = (Byte *) strdup((char *) args);
1791 // user wants file status info
1794 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1795 // print out values of all features
1796 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1797 clear_to_eol(); // clear the line
1802 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1803 if (b < 0) { // no addr given- use defaults
1804 q = begin_line(dot); // assume .,. for the range
1807 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1808 clear_to_eol(); // clear the line
1809 write(1, "\r\n", 2);
1810 for (; q <= r; q++) {
1816 } else if (*q < ' ') {
1824 #ifdef BB_FEATURE_VI_SET
1826 #endif /* BB_FEATURE_VI_SET */
1828 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1829 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1830 if (useforce == TRUE) {
1831 // force end of argv list
1838 // don't exit if the file been modified
1839 if (file_modified == TRUE) {
1840 psbs("No write since last change (:%s! overrides)",
1841 (*cmd == 'q' ? "quit" : "next"));
1844 // are there other file to edit
1845 if (*cmd == 'q' && optind < save_argc - 1) {
1846 psbs("%d more file to edit", (save_argc - optind - 1));
1849 if (*cmd == 'n' && optind >= save_argc - 1) {
1850 psbs("No more files to edit");
1854 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1856 if (strlen((char *) fn) <= 0) {
1857 psbs("No filename given");
1860 if (b < 0) { // no addr given- use defaults
1861 q = begin_line(dot); // assume "dot"
1863 // read after current line- unless user said ":0r foo"
1866 ch = file_insert(fn, q, file_size(fn));
1868 goto vc1; // nothing was inserted
1869 // how many lines in text[]?
1870 li = count_lines(q, q + ch - 1);
1871 psb("\"%s\" %dL, %dC", fn, li, ch);
1873 // if the insert is before "dot" then we need to update
1876 file_modified = TRUE;
1878 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
1879 if (file_modified == TRUE && useforce != TRUE) {
1880 psbs("No write since last change (:rewind! overrides)");
1882 // reset the filenames to edit
1883 optind = fn_start - 1;
1886 #ifdef BB_FEATURE_VI_SET
1887 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
1888 i = 0; // offset into args
1889 if (strlen((char *) args) == 0) {
1890 // print out values of all options
1891 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1892 clear_to_eol(); // clear the line
1893 printf("----------------------------------------\r\n");
1894 #ifdef BB_FEATURE_VI_SETOPTS
1897 printf("autoindent ");
1900 printf("ignorecase ");
1903 printf("showmatch ");
1904 printf("tabstop=%d ", tabstop);
1905 #endif /* BB_FEATURE_VI_SETOPTS */
1909 if (strncasecmp((char *) args, "no", 2) == 0)
1910 i = 2; // ":set noautoindent"
1911 #ifdef BB_FEATURE_VI_SETOPTS
1912 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
1913 strncasecmp((char *) args + i, "ai", 2) == 0) {
1914 autoindent = (i == 2) ? 0 : 1;
1916 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
1917 strncasecmp((char *) args + i, "ic", 2) == 0) {
1918 ignorecase = (i == 2) ? 0 : 1;
1920 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
1921 strncasecmp((char *) args + i, "sm", 2) == 0) {
1922 showmatch = (i == 2) ? 0 : 1;
1924 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
1925 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1926 if (ch > 0 && ch < columns - 1)
1929 #endif /* BB_FEATURE_VI_SETOPTS */
1930 #endif /* BB_FEATURE_VI_SET */
1931 #ifdef BB_FEATURE_VI_SEARCH
1932 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1936 // F points to the "find" pattern
1937 // R points to the "replace" pattern
1938 // replace the cmd line delimiters "/" with NULLs
1939 gflag = 0; // global replace flag
1940 c = orig_buf[1]; // what is the delimiter
1941 F = orig_buf + 2; // start of "find"
1942 R = (Byte *) strchr((char *) F, c); // middle delimiter
1943 *R++ = '\0'; // terminate "find"
1944 buf1 = (Byte *) strchr((char *) R, c);
1945 *buf1++ = '\0'; // terminate "replace"
1946 if (*buf1 == 'g') { // :s/foo/bar/g
1948 gflag++; // turn on gflag
1951 if (b < 0) { // maybe :s/foo/bar/
1952 q = begin_line(dot); // start with cur line
1953 b = count_lines(text, q); // cur line number
1956 e = b; // maybe :.s/foo/bar/
1957 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1958 ls = q; // orig line start
1960 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1962 // we found the "find" pattern- delete it
1963 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1964 // inset the "replace" patern
1965 (void) string_insert(buf1, R); // insert the string
1966 // check for "global" :s/foo/bar/g
1968 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1969 q = buf1 + strlen((char *) R);
1970 goto vc4; // don't let q move past cur line
1976 #endif /* BB_FEATURE_VI_SEARCH */
1977 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1978 psb("%s", vi_Version);
1979 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1980 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
1981 // is there a file name to write to?
1982 if (strlen((char *) args) > 0) {
1985 #ifdef BB_FEATURE_VI_READONLY
1986 if (readonly == TRUE) {
1987 psbs("\"%s\" File is read only", fn);
1990 #endif /* BB_FEATURE_VI_READONLY */
1991 // how many lines in text[]?
1992 li = count_lines(q, r);
1994 if (useforce == TRUE) {
1995 // if "fn" is not write-able, chmod u+w
1996 // sprintf(syscmd, "chmod u+w %s", fn);
2000 l = file_write(fn, q, r);
2001 if (useforce == TRUE && forced == TRUE) {
2003 // sprintf(syscmd, "chmod u-w %s", fn);
2007 psb("\"%s\" %dL, %dC", fn, li, l);
2008 if (q == text && r == end - 1 && l == ch)
2009 file_modified = FALSE;
2010 if (cmd[1] == 'q' && l == ch) {
2013 #ifdef BB_FEATURE_VI_READONLY
2015 #endif /* BB_FEATURE_VI_READONLY */
2016 #ifdef BB_FEATURE_VI_YANKMARK
2017 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2018 if (b < 0) { // no addr given- use defaults
2019 q = begin_line(dot); // assume .,. for the range
2022 text_yank(q, r, YDreg);
2023 li = count_lines(q, r);
2024 psb("Yank %d lines (%d chars) into [%c]",
2025 li, strlen((char *) reg[YDreg]), what_reg());
2026 #endif /* BB_FEATURE_VI_YANKMARK */
2032 dot = bound_dot(dot); // make sure "dot" is valid
2036 static void Hit_Return(void)
2040 standout_start(); // start reverse video
2041 write(1, "[Hit return to continue]", 24);
2042 standout_end(); // end reverse video
2043 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2045 redraw(TRUE); // force redraw all
2047 #endif /* BB_FEATURE_VI_COLON */
2049 //----- Synchronize the cursor to Dot --------------------------
2050 static void sync_cursor(Byte * d, int *row, int *col)
2052 Byte *beg_cur, *end_cur; // begin and end of "d" line
2053 Byte *beg_scr, *end_scr; // begin and end of screen
2057 beg_cur = begin_line(d); // first char of cur line
2058 end_cur = end_line(d); // last char of cur line
2060 beg_scr = end_scr = screenbegin; // first char of screen
2061 end_scr = end_screen(); // last char of screen
2063 if (beg_cur < screenbegin) {
2064 // "d" is before top line on screen
2065 // how many lines do we have to move
2066 cnt = count_lines(beg_cur, screenbegin);
2068 screenbegin = beg_cur;
2069 if (cnt > (rows - 1) / 2) {
2070 // we moved too many lines. put "dot" in middle of screen
2071 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2072 screenbegin = prev_line(screenbegin);
2075 } else if (beg_cur > end_scr) {
2076 // "d" is after bottom line on screen
2077 // how many lines do we have to move
2078 cnt = count_lines(end_scr, beg_cur);
2079 if (cnt > (rows - 1) / 2)
2080 goto sc1; // too many lines
2081 for (ro = 0; ro < cnt - 1; ro++) {
2082 // move screen begin the same amount
2083 screenbegin = next_line(screenbegin);
2084 // now, move the end of screen
2085 end_scr = next_line(end_scr);
2086 end_scr = end_line(end_scr);
2089 // "d" is on screen- find out which row
2091 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2097 // find out what col "d" is on
2099 do { // drive "co" to correct column
2100 if (*tp == '\n' || *tp == '\0')
2104 co += ((tabstop - 1) - (co % tabstop));
2105 } else if (*tp < ' ') {
2106 co++; // display as ^X, use 2 columns
2108 } while (tp++ < d && ++co);
2110 // "co" is the column where "dot" is.
2111 // The screen has "columns" columns.
2112 // The currently displayed columns are 0+offset -- columns+ofset
2113 // |-------------------------------------------------------------|
2115 // offset | |------- columns ----------------|
2117 // If "co" is already in this range then we do not have to adjust offset
2118 // but, we do have to subtract the "offset" bias from "co".
2119 // If "co" is outside this range then we have to change "offset".
2120 // If the first char of a line is a tab the cursor will try to stay
2121 // in column 7, but we have to set offset to 0.
2123 if (co < 0 + offset) {
2126 if (co >= columns + offset) {
2127 offset = co - columns + 1;
2129 // if the first char of the line is a tab, and "dot" is sitting on it
2130 // force offset to 0.
2131 if (d == beg_cur && *d == '\t') {
2140 //----- Text Movement Routines ---------------------------------
2141 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2143 while (p > text && p[-1] != '\n')
2144 p--; // go to cur line B-o-l
2148 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2150 while (p < end - 1 && *p != '\n')
2151 p++; // go to cur line E-o-l
2155 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2157 while (p < end - 1 && *p != '\n')
2158 p++; // go to cur line E-o-l
2159 // Try to stay off of the Newline
2160 if (*p == '\n' && (p - begin_line(p)) > 0)
2165 static Byte *prev_line(Byte * p) // return pointer first char prev line
2167 p = begin_line(p); // goto begining of cur line
2168 if (p[-1] == '\n' && p > text)
2169 p--; // step to prev line
2170 p = begin_line(p); // goto begining of prev line
2174 static Byte *next_line(Byte * p) // return pointer first char next line
2177 if (*p == '\n' && p < end - 1)
2178 p++; // step to next line
2182 //----- Text Information Routines ------------------------------
2183 static Byte *end_screen(void)
2188 // find new bottom line
2190 for (cnt = 0; cnt < rows - 2; cnt++)
2196 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2201 if (stop < start) { // start and stop are backwards- reverse them
2207 stop = end_line(stop); // get to end of this line
2208 for (q = start; q <= stop && q <= end - 1; q++) {
2215 static Byte *find_line(int li) // find begining of line #li
2219 for (q = text; li > 1; li--) {
2225 //----- Dot Movement Routines ----------------------------------
2226 static void dot_left(void)
2228 if (dot > text && dot[-1] != '\n')
2232 static void dot_right(void)
2234 if (dot < end - 1 && *dot != '\n')
2238 static void dot_begin(void)
2240 dot = begin_line(dot); // return pointer to first char cur line
2243 static void dot_end(void)
2245 dot = end_line(dot); // return pointer to last char cur line
2248 static Byte *move_to_col(Byte * p, int l)
2255 if (*p == '\n' || *p == '\0')
2259 co += ((tabstop - 1) - (co % tabstop));
2260 } else if (*p < ' ') {
2261 co++; // display as ^X, use 2 columns
2263 } while (++co <= l && p++ < end);
2267 static void dot_next(void)
2269 dot = next_line(dot);
2272 static void dot_prev(void)
2274 dot = prev_line(dot);
2277 static void dot_scroll(int cnt, int dir)
2281 for (; cnt > 0; cnt--) {
2284 // ctrl-Y scroll up one line
2285 screenbegin = prev_line(screenbegin);
2288 // ctrl-E scroll down one line
2289 screenbegin = next_line(screenbegin);
2292 // make sure "dot" stays on the screen so we dont scroll off
2293 if (dot < screenbegin)
2295 q = end_screen(); // find new bottom line
2297 dot = begin_line(q); // is dot is below bottom line?
2301 static void dot_skip_over_ws(void)
2304 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2308 static void dot_delete(void) // delete the char at 'dot'
2310 (void) text_hole_delete(dot, dot);
2313 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2315 if (p >= end && end > text) {
2317 indicate_error('1');
2321 indicate_error('2');
2326 //----- Helper Utility Routines --------------------------------
2328 //----------------------------------------------------------------
2329 //----- Char Routines --------------------------------------------
2330 /* Chars that are part of a word-
2331 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2332 * Chars that are Not part of a word (stoppers)
2333 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2334 * Chars that are WhiteSpace
2335 * TAB NEWLINE VT FF RETURN SPACE
2336 * DO NOT COUNT NEWLINE AS WHITESPACE
2339 static Byte *new_screen(int ro, int co)
2343 screensize = ro * co + 8;
2344 screen = (Byte *) malloc(screensize);
2348 static Byte *new_text(int size)
2351 size = 10240; // have a minimum size for new files
2356 text = (Byte *) malloc(size + 8);
2357 memset(text, '\0', size); // clear new text[]
2358 //text += 4; // leave some room for "oops"
2359 textend = text + size - 1;
2360 //textend -= 4; // leave some root for "oops"
2364 #ifdef BB_FEATURE_VI_SEARCH
2365 static int mycmp(Byte * s1, Byte * s2, int len)
2369 i = strncmp((char *) s1, (char *) s2, len);
2370 #ifdef BB_FEATURE_VI_SETOPTS
2372 i = strncasecmp((char *) s1, (char *) s2, len);
2374 #endif /* BB_FEATURE_VI_SETOPTS */
2378 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2380 #ifndef REGEX_SEARCH
2384 len = strlen((char *) pat);
2385 if (dir == FORWARD) {
2386 stop = end - 1; // assume range is p - end-1
2387 if (range == LIMITED)
2388 stop = next_line(p); // range is to next line
2389 for (start = p; start < stop; start++) {
2390 if (mycmp(start, pat, len) == 0) {
2394 } else if (dir == BACK) {
2395 stop = text; // assume range is text - p
2396 if (range == LIMITED)
2397 stop = prev_line(p); // range is to prev line
2398 for (start = p - len; start >= stop; start--) {
2399 if (mycmp(start, pat, len) == 0) {
2404 // pattern not found
2406 #else /*REGEX_SEARCH */
2408 struct re_pattern_buffer preg;
2412 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2418 // assume a LIMITED forward search
2426 // count the number of chars to search over, forward or backward
2430 // RANGE could be negative if we are searching backwards
2433 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2435 // The pattern was not compiled
2436 psbs("bad search pattern: \"%s\": %s", pat, q);
2437 i = 0; // return p if pattern not compiled
2447 // search for the compiled pattern, preg, in p[]
2448 // range < 0- search backward
2449 // range > 0- search forward
2451 // re_search() < 0 not found or error
2452 // re_search() > 0 index of found pattern
2453 // struct pattern char int int int struct reg
2454 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2455 i = re_search(&preg, q, size, 0, range, 0);
2458 i = 0; // return NULL if pattern not found
2461 if (dir == FORWARD) {
2467 #endif /*REGEX_SEARCH */
2469 #endif /* BB_FEATURE_VI_SEARCH */
2471 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2473 if (c == 22) { // Is this an ctrl-V?
2474 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2475 p--; // backup onto ^
2476 refresh(FALSE); // show the ^
2480 file_modified = TRUE; // has the file been modified
2481 } else if (c == 27) { // Is this an ESC?
2484 end_cmd_q(); // stop adding to q
2485 *status_buffer = '\0'; // clear the status buffer
2486 if (p[-1] != '\n') {
2489 } else if (c == erase_char) { // Is this a BS
2491 if (p[-1] != '\n') {
2493 p = text_hole_delete(p, p); // shrink buffer 1 char
2494 #ifdef BB_FEATURE_VI_DOT_CMD
2495 // also rmove char from last_modifying_cmd
2496 if (strlen((char *) last_modifying_cmd) > 0) {
2499 q = last_modifying_cmd;
2500 q[strlen((char *) q) - 1] = '\0'; // erase BS
2501 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2503 #endif /* BB_FEATURE_VI_DOT_CMD */
2506 // insert a char into text[]
2507 Byte *sp; // "save p"
2510 c = '\n'; // translate \r to \n
2511 sp = p; // remember addr of insert
2512 p = stupid_insert(p, c); // insert the char
2513 #ifdef BB_FEATURE_VI_SETOPTS
2514 if (showmatch && strchr(")]}", *sp) != NULL) {
2517 if (autoindent && c == '\n') { // auto indent the new line
2520 q = prev_line(p); // use prev line as templet
2521 for (; isblnk(*q); q++) {
2522 p = stupid_insert(p, *q); // insert the char
2525 #endif /* BB_FEATURE_VI_SETOPTS */
2530 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2532 p = text_hole_make(p, 1);
2535 file_modified = TRUE; // has the file been modified
2541 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2543 Byte *save_dot, *p, *q;
2549 if (strchr("cdy><", c)) {
2550 p = q = begin_line(p);
2551 for (cnt = 1; cnt < cmdcnt; cnt++) {
2555 } else if (strchr("$0bBeE", c)) {
2556 do_cmd(c); // execute movement cmd
2558 } else if (strchr("wW", c)) {
2559 do_cmd(c); // execute movement cmd
2561 dot--; // move back off of next word
2562 if (dot > text && *dot == '\n')
2563 dot--; // stay off NL
2565 } else if (strchr("H-k{", c)) {
2566 q = end_line(dot); // find NL
2567 do_cmd(c); // execute movement cmd
2570 } else if (strchr("L+j}\r\n", c)) {
2571 p = begin_line(dot);
2572 do_cmd(c); // execute movement cmd
2573 dot_end(); // find NL
2576 c = 27; // error- return an ESC char
2589 static int st_test(Byte * p, int type, int dir, Byte * tested)
2599 if (type == S_BEFORE_WS) {
2601 test = ((!isspace(c)) || c == '\n');
2603 if (type == S_TO_WS) {
2605 test = ((!isspace(c)) || c == '\n');
2607 if (type == S_OVER_WS) {
2609 test = ((isspace(c)));
2611 if (type == S_END_PUNCT) {
2613 test = ((ispunct(c)));
2615 if (type == S_END_ALNUM) {
2617 test = ((isalnum(c)) || c == '_');
2623 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2627 while (st_test(p, type, dir, &c)) {
2628 // make sure we limit search to correct number of lines
2629 if (c == '\n' && --linecnt < 1)
2631 if (dir >= 0 && p >= end - 1)
2633 if (dir < 0 && p <= text)
2635 p += dir; // move to next char
2640 // find matching char of pair () [] {}
2641 static Byte *find_pair(Byte * p, Byte c)
2648 dir = 1; // assume forward
2672 for (q = p + dir; text <= q && q < end; q += dir) {
2673 // look for match, count levels of pairs (( ))
2675 level++; // increase pair levels
2677 level--; // reduce pair level
2679 break; // found matching pair
2682 q = NULL; // indicate no match
2686 #ifdef BB_FEATURE_VI_SETOPTS
2687 // show the matching char of a pair, () [] {}
2688 static void showmatching(Byte * p)
2692 // we found half of a pair
2693 q = find_pair(p, *p); // get loc of matching char
2695 indicate_error('3'); // no matching char
2697 // "q" now points to matching pair
2698 save_dot = dot; // remember where we are
2699 dot = q; // go to new loc
2700 refresh(FALSE); // let the user see it
2701 (void) mysleep(40); // give user some time
2702 dot = save_dot; // go back to old loc
2706 #endif /* BB_FEATURE_VI_SETOPTS */
2708 // open a hole in text[]
2709 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2718 cnt = end - src; // the rest of buffer
2719 if (memmove(dest, src, cnt) != dest) {
2720 psbs("can't create room for new characters");
2722 memset(p, ' ', size); // clear new hole
2723 end = end + size; // adjust the new END
2724 file_modified = TRUE; // has the file been modified
2729 // close a hole in text[]
2730 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2735 // move forwards, from beginning
2739 if (q < p) { // they are backward- swap them
2743 hole_size = q - p + 1;
2745 if (src < text || src > end)
2747 if (dest < text || dest >= end)
2750 goto thd_atend; // just delete the end of the buffer
2751 if (memmove(dest, src, cnt) != dest) {
2752 psbs("can't delete the character");
2755 end = end - hole_size; // adjust the new END
2757 dest = end - 1; // make sure dest in below end-1
2759 dest = end = text; // keep pointers valid
2760 file_modified = TRUE; // has the file been modified
2765 // copy text into register, then delete text.
2766 // if dist <= 0, do not include, or go past, a NewLine
2768 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2772 // make sure start <= stop
2774 // they are backwards, reverse them
2780 // we can not cross NL boundaries
2784 // dont go past a NewLine
2785 for (; p + 1 <= stop; p++) {
2787 stop = p; // "stop" just before NewLine
2793 #ifdef BB_FEATURE_VI_YANKMARK
2794 text_yank(start, stop, YDreg);
2795 #endif /* BB_FEATURE_VI_YANKMARK */
2796 if (yf == YANKDEL) {
2797 p = text_hole_delete(start, stop);
2802 static void show_help(void)
2804 printf("These features are available:\n");
2805 #ifdef BB_FEATURE_VI_SEARCH
2806 printf("\tPattern searches with / and ?\n");
2807 #endif /* BB_FEATURE_VI_SEARCH */
2808 #ifdef BB_FEATURE_VI_DOT_CMD
2809 printf("\tLast command repeat with \'.\'\n");
2810 #endif /* BB_FEATURE_VI_DOT_CMD */
2811 #ifdef BB_FEATURE_VI_YANKMARK
2812 printf("\tLine marking with 'x\n");
2813 printf("\tNamed buffers with \"x\n");
2814 #endif /* BB_FEATURE_VI_YANKMARK */
2815 #ifdef BB_FEATURE_VI_READONLY
2816 printf("\tReadonly with -R command line arg\n");
2817 #endif /* BB_FEATURE_VI_READONLY */
2818 #ifdef BB_FEATURE_VI_SET
2819 printf("\tSome colon mode commands with \':\'\n");
2820 #endif /* BB_FEATURE_VI_SET */
2821 #ifdef BB_FEATURE_VI_SETOPTS
2822 printf("\tSettable options with \":set\"\n");
2823 #endif /* BB_FEATURE_VI_SETOPTS */
2824 #ifdef BB_FEATURE_VI_USE_SIGNALS
2825 printf("\tSignal catching- ^C\n");
2826 printf("\tJob suspend and resume with ^Z\n");
2827 #endif /* BB_FEATURE_VI_USE_SIGNALS */
2828 #ifdef BB_FEATURE_VI_WIN_RESIZE
2829 printf("\tAdapt to window re-sizes\n");
2830 #endif /* BB_FEATURE_VI_WIN_RESIZE */
2833 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2838 strcpy((char *) buf, ""); // init buf
2839 if (strlen((char *) s) <= 0)
2840 s = (Byte *) "(NULL)";
2841 for (; *s > '\0'; s++) {
2844 strcat((char *) buf, SOs);
2848 strcat((char *) buf, "^");
2852 strcat((char *) buf, (char *) b);
2854 strcat((char *) buf, SOn);
2856 strcat((char *) buf, "$");
2861 #ifdef BB_FEATURE_VI_DOT_CMD
2862 static void start_new_cmd_q(Byte c)
2865 if (last_modifying_cmd != 0)
2866 free(last_modifying_cmd);
2867 // get buffer for new cmd
2868 last_modifying_cmd = (Byte *) malloc(BUFSIZ);
2869 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2870 // if there is a current cmd count put it in the buffer first
2872 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2873 // save char c onto queue
2874 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2879 static void end_cmd_q()
2881 #ifdef BB_FEATURE_VI_YANKMARK
2882 YDreg = 26; // go back to default Yank/Delete reg
2883 #endif /* BB_FEATURE_VI_YANKMARK */
2887 #endif /* BB_FEATURE_VI_DOT_CMD */
2889 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
2890 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2894 i = strlen((char *) s);
2895 p = text_hole_make(p, i);
2896 strncpy((char *) p, (char *) s, i);
2897 for (cnt = 0; *s != '\0'; s++) {
2901 #ifdef BB_FEATURE_VI_YANKMARK
2902 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2903 #endif /* BB_FEATURE_VI_YANKMARK */
2906 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
2908 #ifdef BB_FEATURE_VI_YANKMARK
2909 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2914 if (q < p) { // they are backwards- reverse them
2921 if (t != 0) { // if already a yank register
2924 t = (Byte *) malloc(cnt + 1); // get a new register
2925 memset(t, '\0', cnt + 1); // clear new text[]
2926 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2931 static Byte what_reg(void)
2937 c = 'D'; // default to D-reg
2938 if (0 <= YDreg && YDreg <= 25)
2939 c = 'a' + (Byte) YDreg;
2947 static void check_context(Byte cmd)
2949 // A context is defined to be "modifying text"
2950 // Any modifying command establishes a new context.
2952 if (dot < context_start || dot > context_end) {
2953 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2954 // we are trying to modify text[]- make this the current context
2955 mark[27] = mark[26]; // move cur to prev
2956 mark[26] = dot; // move local to cur
2957 context_start = prev_line(prev_line(dot));
2958 context_end = next_line(next_line(dot));
2959 //loiter= start_loiter= now;
2965 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2969 // the current context is in mark[26]
2970 // the previous context is in mark[27]
2971 // only swap context if other context is valid
2972 if (text <= mark[27] && mark[27] <= end - 1) {
2974 mark[27] = mark[26];
2976 p = mark[26]; // where we are going- previous context
2977 context_start = prev_line(prev_line(prev_line(p)));
2978 context_end = next_line(next_line(next_line(p)));
2982 #endif /* BB_FEATURE_VI_YANKMARK */
2984 static int isblnk(Byte c) // is the char a blank or tab
2986 return (c == ' ' || c == '\t');
2989 //----- Set terminal attributes --------------------------------
2990 static void rawmode(void)
2992 tcgetattr(0, &term_orig);
2993 term_vi = term_orig;
2994 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2995 term_vi.c_iflag &= (~IXON & ~ICRNL);
2996 term_vi.c_cc[VMIN] = 1;
2997 term_vi.c_cc[VTIME] = 0;
2998 erase_char = term_vi.c_cc[VERASE];
2999 tcsetattr(0, TCSANOW, &term_vi);
3002 static void cookmode(void)
3004 tcsetattr(0, TCSANOW, &term_orig);
3007 #ifdef BB_FEATURE_VI_WIN_RESIZE
3008 //----- See what the window size currently is --------------------
3009 static void window_size_get(int sig)
3013 i = ioctl(0, TIOCGWINSZ, &winsize);
3016 winsize.ws_row = 24;
3017 winsize.ws_col = 80;
3019 if (winsize.ws_row <= 1) {
3020 winsize.ws_row = 24;
3022 if (winsize.ws_col <= 1) {
3023 winsize.ws_col = 80;
3025 rows = (int) winsize.ws_row;
3026 columns = (int) winsize.ws_col;
3028 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3030 //----- Come here when we get a window resize signal ---------
3031 #ifdef BB_FEATURE_VI_USE_SIGNALS
3032 static void winch_sig(int sig)
3034 signal(SIGWINCH, winch_sig);
3035 #ifdef BB_FEATURE_VI_WIN_RESIZE
3037 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3038 new_screen(rows, columns); // get memory for virtual screen
3039 redraw(TRUE); // re-draw the screen
3042 //----- Come here when we get a continue signal -------------------
3043 static void cont_sig(int sig)
3045 rawmode(); // terminal to "raw"
3046 *status_buffer = '\0'; // clear the status buffer
3047 redraw(TRUE); // re-draw the screen
3049 signal(SIGTSTP, suspend_sig);
3050 signal(SIGCONT, SIG_DFL);
3051 kill(getpid(), SIGCONT);
3054 //----- Come here when we get a Suspend signal -------------------
3055 static void suspend_sig(int sig)
3057 place_cursor(rows, 0); // go to bottom of screen
3058 clear_to_eol(); // Erase to end of line
3059 cookmode(); // terminal to "cooked"
3061 signal(SIGCONT, cont_sig);
3062 signal(SIGTSTP, SIG_DFL);
3063 kill(getpid(), SIGTSTP);
3066 //----- Come here when we get a signal --------------------
3067 static void catch_sig(int sig)
3069 signal(SIGHUP, catch_sig);
3070 signal(SIGINT, catch_sig);
3071 signal(SIGTERM, catch_sig);
3072 longjmp(restart, sig);
3075 static void alarm_sig(int sig)
3077 signal(SIGALRM, catch_sig);
3078 longjmp(restart, sig);
3081 //----- Come here when we get a core dump signal -----------------
3082 static void core_sig(int sig)
3084 signal(SIGQUIT, core_sig);
3085 signal(SIGILL, core_sig);
3086 signal(SIGTRAP, core_sig);
3087 signal(SIGIOT, core_sig);
3088 signal(SIGABRT, core_sig);
3089 signal(SIGFPE, core_sig);
3090 signal(SIGBUS, core_sig);
3091 signal(SIGSEGV, core_sig);
3093 signal(SIGSYS, core_sig);
3096 dot = bound_dot(dot); // make sure "dot" is valid
3098 longjmp(restart, sig);
3100 #endif /* BB_FEATURE_VI_USE_SIGNALS */
3102 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3104 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3108 tv.tv_usec = hund * 10000;
3109 select(1, &rfds, NULL, NULL, &tv);
3110 return (FD_ISSET(0, &rfds));
3113 //----- IO Routines --------------------------------------------
3114 static Byte readit(void) // read (maybe cursor) key from stdin
3117 int i, bufsiz, cnt, cmdindex;
3123 static struct esc_cmds esccmds[] = {
3124 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3125 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3126 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3127 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3128 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3129 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3130 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3131 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3132 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3133 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3134 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3135 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3136 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3137 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3138 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3139 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3140 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3141 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3142 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3143 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3144 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3145 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3146 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3147 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3148 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3149 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3150 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3151 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3152 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3153 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3154 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3157 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3159 (void) alarm(0); // turn alarm OFF while we wait for input
3160 // get input from User- are there already input chars in Q?
3161 bufsiz = strlen((char *) readbuffer);
3164 // the Q is empty, wait for a typed char
3165 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3168 goto ri0; // interrupted sys call
3171 if (errno == EFAULT)
3173 if (errno == EINVAL)
3180 readbuffer[bufsiz] = '\0';
3182 // return char if it is not part of ESC sequence
3183 if (readbuffer[0] != 27)
3186 // This is an ESC char. Is this Esc sequence?
3187 // Could be bare Esc key. See if there are any
3188 // more chars to read after the ESC. This would
3189 // be a Function or Cursor Key sequence.
3193 tv.tv_usec = 10000; // Wait 1/100 seconds- 1 Sec=1000000
3195 // keep reading while there are input chars and room in buffer
3196 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3197 // read the rest of the ESC string
3198 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3201 readbuffer[bufsiz] = '\0'; // Terminate the string
3204 // Maybe cursor or function key?
3205 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3206 cnt = strlen((char *) esccmds[cmdindex].seq);
3207 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3209 // is a Cursor key- put derived value back into Q
3210 readbuffer[0] = esccmds[cmdindex].val;
3211 // squeeze out the ESC sequence
3212 for (i = 1; i < cnt; i++) {
3213 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3214 readbuffer[BUFSIZ - 1] = '\0';
3221 // remove one char from Q
3222 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3223 readbuffer[BUFSIZ - 1] = '\0';
3224 (void) alarm(3); // we are done waiting for input, turn alarm ON
3228 //----- IO Routines --------------------------------------------
3229 static Byte get_one_char()
3233 #ifdef BB_FEATURE_VI_DOT_CMD
3234 // ! adding2q && ioq == 0 read()
3235 // ! adding2q && ioq != 0 *ioq
3236 // adding2q *last_modifying_cmd= read()
3238 // we are not adding to the q.
3239 // but, we may be reading from a q
3241 // there is no current q, read from STDIN
3242 c = readit(); // get the users input
3244 // there is a queue to get chars from first
3247 // the end of the q, read from STDIN
3249 ioq_start = ioq = 0;
3250 c = readit(); // get the users input
3254 // adding STDIN chars to q
3255 c = readit(); // get the users input
3256 if (last_modifying_cmd != 0) {
3257 // add new char to q
3258 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3261 #else /* BB_FEATURE_VI_DOT_CMD */
3262 c = readit(); // get the users input
3263 #endif /* BB_FEATURE_VI_DOT_CMD */
3264 return (c); // return the char, where ever it came from
3267 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
3268 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3273 static Byte *obufp = NULL;
3275 strcpy((char *) buf, (char *) prompt);
3276 *status_buffer = '\0'; // clear the status buffer
3277 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
3278 clear_to_eol(); // clear the line
3279 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3281 for (i = strlen((char *) buf); i < 500;) {
3282 c = get_one_char(); // read user input
3283 if (c == '\n' || c == '\r')
3284 break; // is this end of input
3285 if (c == erase_char) { // user wants to erase prev char
3286 i--; // backup to prev char
3287 buf[i] = '\0'; // erase the char
3288 buf[i + 1] = '\0'; // null terminate buffer
3289 write(1, "
\b \b", 3); // erase char on screen
3290 if (i <= 0) { // user backs up before b-o-l, exit
3294 buf[i] = c; // save char in buffer
3295 buf[i + 1] = '\0'; // make sure buffer is null terminated
3296 write(1, buf + i, 1); // echo the char back to user
3303 obufp = (Byte *) strdup((char *) buf);
3306 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
3308 static int file_size(Byte * fn) // what is the byte size of "fn"
3316 sr = stat((char *) fn, &st_buf); // see if file exists
3318 cnt = (int) st_buf.st_size;
3323 static int file_insert(Byte * fn, Byte * p, int size)
3330 psbs("No filename given");
3333 sr = stat((char *) fn, &st_buf); // see if file exists
3335 psbs("\"%s\" %s", fn, "count not stat file");
3338 if (size <= 0 || (int) st_buf.st_size <= 0) {
3339 psbs("The file size (%d) is too small", size);
3340 if ((int) st_buf.st_size <= 0)
3341 psbs("\"%s\" is empty", fn);
3344 // There is a file with content
3345 fd = open((char *) fn, O_RDWR);
3347 psbs("\"%s\" %s", fn, "could not open file");
3350 p = text_hole_make(p, size);
3351 cnt = read(fd, p, size);
3355 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3356 psbs("could not read file \"%s\"", fn);
3357 } else if (cnt < size) {
3358 // There was a partial read, shrink unused space text[]
3359 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3360 psbs("could not read all of file \"%s\"", fn);
3363 file_modified = TRUE;
3368 static int file_write(Byte * fn, Byte * first, Byte * last)
3370 int fd, cnt, charcnt;
3373 psbs("No current filename");
3377 // FIXIT- use the correct umask()
3378 fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
3381 cnt = last - first + 1;
3382 charcnt = write(fd, first, cnt);
3383 if (charcnt == cnt) {
3385 //file_modified= FALSE; // the file has not been modified
3393 //----- Terminal Drawing ---------------------------------------
3394 // The terminal is made up of 'rows' line of 'columns' columns.
3395 // classicly this would be 24 x 80.
3396 // screen coordinates
3402 // 23,0 ... 23,79 status line
3405 //----- Move the cursor to row x col (count from 0, not 1) -------
3406 static void place_cursor(int row, int col)
3419 sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
3420 l = strlen((char *) buf);
3424 //----- Erase from cursor to end of line -----------------------
3425 static void clear_to_eol()
3427 write(1, "\033[0K", 4); // Erase from cursor to end of line
3430 //----- Erase from cursor to end of screen -----------------------
3431 static void clear_to_eos()
3433 write(1, "\033[0J", 4); // Erase from cursor to end of screen
3436 //----- Start standout mode ------------------------------------
3437 static void standout_start() // send "start reverse video" sequence
3439 write(1, "\033[7m", 4); // Start reverse video mode
3442 //----- End standout mode --------------------------------------
3443 static void standout_end() // send "end reverse video" sequence
3445 write(1, "\033[0m", 4); // End reverse video mode
3448 //----- Flash the screen --------------------------------------
3449 static void flash(int h)
3451 standout_start(); // send "start reverse video" sequence
3454 standout_end(); // send "end reverse video" sequence
3460 write(1, "\007", 1); // send out a bell character
3463 static void indicate_error(char c)
3465 #ifdef BB_FEATURE_VI_CRASHME
3467 return; // generate a random command
3468 #endif /* BB_FEATURE_VI_CRASHME */
3469 if (err_method == 0) {
3476 //----- Screen[] Routines --------------------------------------
3477 //----- Erase the Screen[] memory ------------------------------
3478 static void screen_erase()
3482 for (i = 0; i < screensize; i++) {
3487 //----- Draw the status line at bottom of the screen -------------
3488 static void show_status_line(void)
3492 cnt = strlen((char *) status_buffer);
3493 place_cursor(rows - 1, 0); // put cursor on status line
3495 write(1, status_buffer, cnt);
3498 place_cursor(crow, ccol); // put cursor back in correct place
3501 //----- format the status buffer, the bottom line of screen ------
3502 // print status buffer, with STANDOUT mode
3503 static void psbs(char *format, ...)
3507 va_start(args, format);
3508 strcpy((char *) status_buffer, "\033[7m"); // Terminal standout mode on
3509 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3511 strcat((char *) status_buffer, "\033[0m"); // Terminal standout mode off
3517 // print status buffer
3518 static void psb(char *format, ...)
3522 va_start(args, format);
3523 vsprintf((char *) status_buffer, format, args);
3528 static void ni(Byte * s) // display messages
3532 print_literal(buf, s);
3533 psbs("\'%s\' is not implemented", buf);
3536 static void edit_status(void) // show file status on status line
3538 int cur, tot, percent;
3540 cur = count_lines(text, dot);
3541 tot = count_lines(text, end - 1);
3542 // current line percent
3543 // ------------- ~~ ----------
3546 percent = (100 * cur) / tot;
3552 #ifdef BB_FEATURE_VI_READONLY
3554 #endif /* BB_FEATURE_VI_READONLY */
3555 "%s line %d of %d --%d%%-- (%dx%d)",
3556 (cfn != 0 ? (char *) cfn : "No file"),
3557 #ifdef BB_FEATURE_VI_READONLY
3558 (readonly == TRUE ? " [Read only]" : ""),
3559 #endif /* BB_FEATURE_VI_READONLY */
3560 (file_modified == TRUE ? " [modified]" : ""),
3561 cur, tot, percent, rows, columns);
3564 //----- Force refresh of all Lines -----------------------------
3565 static void redraw(int full_screen)
3567 place_cursor(0, 0); // put cursor in correct place
3568 clear_to_eos(); // tel terminal to erase display
3569 screen_erase(); // erase the internal screen buffer
3570 refresh(full_screen); // this will redraw the entire display
3573 //----- Refresh the changed screen lines -----------------------
3574 // Copy the source line from text[] into the buffer and note
3575 // if the current screenline is different from the new buffer.
3576 // If they differ then that line needs redrawing on the terminal.
3578 static void refresh(int full_screen)
3580 static int old_offset;
3581 int li, co, changed;
3582 Byte c, buf[MAX_SCR_COLS];
3583 Byte *tp, *sp; // pointer into text[] and screen[]
3585 #ifdef BB_FEATURE_VI_WIN_RESIZE
3587 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3588 sync_cursor(dot, &crow, &ccol);
3589 tp = screenbegin; // index into text[] of top line
3590 // compare text[] to screen[] and mark screen[] lines that need updating
3591 for (li = 0; li < rows - 1; li++) {
3592 // format current text line into buf with "columns" wide
3593 for (co = 0; co < columns + offset;) {
3594 c = ' '; // assume blank
3595 if (li > 0 && co == 0) {
3596 c = '~'; // not first line, assume Tilde
3598 // are there chars in text[]
3599 // and have we gone past the end
3600 if (text < end && tp < end) {
3605 if (c < ' ' || c > '~') {
3609 for (; (co % tabstop) != (tabstop - 1); co++) {
3614 c |= '@'; // make it visible
3615 c &= 0x7f; // get rid of hi bit
3618 // the co++ is done here so that the column will
3619 // not be overwritten when we blank-out the rest of line
3624 if (co >= columns + offset) {
3625 // skip to the end of the current text[] line
3626 while (tp < end && *tp++ != '\n');
3628 // try to keep the cursor near it's current position
3629 // remember how many chars in this row- where the cursor sits
3630 // blank out the rest of the buffer
3631 while (co < MAX_SCR_COLS - 1) {
3634 buf[co++] = 0; // NULL terminate the buffer
3636 // if necessary, update virtual screen[] and terminal from buf[]
3637 changed = FALSE; // assume no change
3638 sp = &screen[li * columns]; // start of screen line
3639 for (co = 0; co < columns; co++) {
3640 if (sp[co] != buf[co + offset]) {
3641 sp[co] = buf[co + offset];
3642 changed = TRUE; // mark for redraw
3645 // if horz offset has changed, force a redraw
3646 if (offset != old_offset)
3649 // write all marked screen lines out to terminal
3650 if (changed == TRUE) {
3651 place_cursor(li, 0); // put cursor in correct place
3652 clear_to_eol(); // Erase to end of line
3653 if (full_screen == FALSE) {
3654 // don't redraw every column on terminal
3655 // look backwards for last non-blank
3656 for (co = columns + offset; co >= 0; co--) {
3663 // redraw every column on terminal
3666 // write line out to terminal
3667 write(1, buf + offset, co);
3671 place_cursor(crow, ccol);
3672 if (offset != old_offset)
3673 old_offset = offset;