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.5 2001/04/26 15:56:47 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
35 * $HOME/.exrc and ./.exrc
36 * add magic to search /foo.*bar
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 * More intelligence in refresh()
43 * ":r !cmd" and "!cmd" to filter text through an external command
44 * A true "undo" facility
45 * An "ex" line oriented mode- maybe using "cmdedit"
48 //---- Feature -------------- Bytes to immplement
51 #define BB_FEATURE_VI_COLON // 4288
52 #define BB_FEATURE_VI_YANKMARK // 1408
53 #define BB_FEATURE_VI_SEARCH // 1088
54 #define BB_FEATURE_VI_USE_SIGNALS // 1056
55 #define BB_FEATURE_VI_DOT_CMD // 576
56 #define BB_FEATURE_VI_READONLY // 128
57 #define BB_FEATURE_VI_SETOPTS // 576
58 #define BB_FEATURE_VI_SET // 224
59 #define BB_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
60 // To test editor using CRASHME:
62 // To stop testing, wait until all to text[] is deleted, or
63 // Ctrl-Z and kill -9 %1
64 // while in the editor Ctrl-T will toggle the crashme function on and off.
65 //#define BB_FEATURE_VI_CRASHME // randomly pick commands to execute
66 #endif /* STANDALONE */
70 #endif /* STANDALONE */
76 #include <sys/ioctl.h>
78 #include <sys/types.h>
92 #define FALSE ((int)0)
94 #define MAX_SCR_COLS BUFSIZ
96 // Misc. non-Ascii keys that report an escape sequence
97 #define VI_K_UP 128 // cursor key Up
98 #define VI_K_DOWN 129 // cursor key Down
99 #define VI_K_RIGHT 130 // Cursor Key Right
100 #define VI_K_LEFT 131 // cursor key Left
101 #define VI_K_HOME 132 // Cursor Key Home
102 #define VI_K_END 133 // Cursor Key End
103 #define VI_K_INSERT 134 // Cursor Key Insert
104 #define VI_K_PAGEUP 135 // Cursor Key Page Up
105 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
106 #define VI_K_FUN1 137 // Function Key F1
107 #define VI_K_FUN2 138 // Function Key F2
108 #define VI_K_FUN3 139 // Function Key F3
109 #define VI_K_FUN4 140 // Function Key F4
110 #define VI_K_FUN5 141 // Function Key F5
111 #define VI_K_FUN6 142 // Function Key F6
112 #define VI_K_FUN7 143 // Function Key F7
113 #define VI_K_FUN8 144 // Function Key F8
114 #define VI_K_FUN9 145 // Function Key F9
115 #define VI_K_FUN10 146 // Function Key F10
116 #define VI_K_FUN11 147 // Function Key F11
117 #define VI_K_FUN12 148 // Function Key F12
119 static const int YANKONLY = FALSE;
120 static const int YANKDEL = TRUE;
121 static const int FORWARD = 1; // code depends on "1" for array index
122 static const int BACK = -1; // code depends on "-1" for array index
123 static const int LIMITED = 0; // how much of text[] in char_search
124 static const int FULL = 1; // how much of text[] in char_search
126 static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
127 static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
128 static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
129 static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
130 static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
132 typedef unsigned char Byte;
135 static int editing; // >0 while we are editing a file
136 static int cmd_mode; // 0=command 1=insert
137 static int file_modified; // buffer contents changed
138 static int err_method; // indicate error with beep or flash
139 static int fn_start; // index of first cmd line file name
140 static int save_argc; // how many file names on cmd line
141 static int cmdcnt; // repetition count
142 static fd_set rfds; // use select() for small sleeps
143 static struct timeval tv; // use select() for small sleeps
144 static char erase_char; // the users erase character
145 static int rows, columns; // the terminal screen is this size
146 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
147 static char *SOs, *SOn;
148 static Byte *status_buffer; // mesages to the user
149 static Byte last_input_char; // last char read from user
150 static Byte last_forward_char; // last char searched for with 'f'
151 static Byte *cfn; // previous, current, and next file name
152 static Byte *text, *end, *textend; // pointers to the user data in memory
153 static Byte *screen; // pointer to the virtual screen buffer
154 static int screensize; // and its size
155 static Byte *screenbegin; // index into text[], of top line on the screen
156 static Byte *dot; // where all the action takes place
158 static struct termios term_orig, term_vi; // remember what the cooked mode was
160 #ifdef BB_FEATURE_VI_USE_SIGNALS
161 static jmp_buf restart; // catch_sig()
162 #endif /* BB_FEATURE_VI_USE_SIGNALS */
163 #ifdef BB_FEATURE_VI_WIN_RESIZE
164 static struct winsize winsize; // remember the window size
165 #endif /* BB_FEATURE_VI_WIN_RESIZE */
166 #ifdef BB_FEATURE_VI_DOT_CMD
167 static int adding2q; // are we currently adding user input to q
168 static Byte *last_modifying_cmd; // last modifying cmd for "."
169 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
170 #endif /* BB_FEATURE_VI_DOT_CMD */
171 #if defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
172 static Byte *modifying_cmds; // cmds that modify text[]
173 #endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
174 #ifdef BB_FEATURE_VI_READONLY
176 #endif /* BB_FEATURE_VI_READONLY */
177 #ifdef BB_FEATURE_VI_SETOPTS
178 static int autoindent;
179 static int showmatch;
180 static int ignorecase;
181 #endif /* BB_FEATURE_VI_SETOPTS */
182 #ifdef BB_FEATURE_VI_YANKMARK
183 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
184 static int YDreg, Ureg; // default delete register and orig line for "U"
185 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
186 static Byte *context_start, *context_end;
187 #endif /* BB_FEATURE_VI_YANKMARK */
188 #ifdef BB_FEATURE_VI_SEARCH
189 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
190 #endif /* BB_FEATURE_VI_SEARCH */
193 static void edit_file(Byte *); // edit one file
194 static void do_cmd(Byte); // execute a command
195 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
196 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
197 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
198 static Byte *dollar_line(Byte *); // return pointer to just before NL
199 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
200 static Byte *next_line(Byte *); // return pointer to next line B-o-l
201 static Byte *end_screen(void); // get pointer to last char on screen
202 static int count_lines(Byte *, Byte *); // count line from start to stop
203 static Byte *find_line(int); // find begining of line #li
204 static Byte *move_to_col(Byte *, int); // move "p" to column l
205 static int isblnk(Byte); // is the char a blank or tab
206 static void dot_left(void); // move dot left- dont leave line
207 static void dot_right(void); // move dot right- dont leave line
208 static void dot_begin(void); // move dot to B-o-l
209 static void dot_end(void); // move dot to E-o-l
210 static void dot_next(void); // move dot to next line B-o-l
211 static void dot_prev(void); // move dot to prev line B-o-l
212 static void dot_scroll(int, int); // move the screen up or down
213 static void dot_skip_over_ws(void); // move dot pat WS
214 static void dot_delete(void); // delete the char at 'dot'
215 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
216 static Byte *new_screen(int, int); // malloc virtual screen memory
217 static Byte *new_text(int); // malloc memory for text[] buffer
218 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
219 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
220 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
221 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
222 static Byte *skip_thing(Byte *, int, int, int); // skip some object
223 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
224 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
225 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
226 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
227 static void show_help(void); // display some help info
228 static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
229 static void rawmode(void); // set "raw" mode on tty
230 static void cookmode(void); // return to "cooked" mode on tty
231 static int mysleep(int); // sleep for 'h' 1/100 seconds
232 static Byte readit(void); // read (maybe cursor) key from stdin
233 static Byte get_one_char(void); // read 1 char from stdin
234 static int file_size(Byte *); // what is the byte size of "fn"
235 static int file_insert(Byte *, Byte *, int);
236 static int file_write(Byte *, Byte *, Byte *);
237 static void place_cursor(int, int);
238 static void screen_erase();
239 static void clear_to_eol(void);
240 static void clear_to_eos(void);
241 static void standout_start(void); // send "start reverse video" sequence
242 static void standout_end(void); // send "end reverse video" sequence
243 static void flash(int); // flash the terminal screen
244 static void beep(void); // beep the terminal
245 static void indicate_error(char); // use flash or beep to indicate error
246 static void show_status_line(void); // put a message on the bottom line
247 static void psb(char *, ...); // Print Status Buf
248 static void psbs(char *, ...); // Print Status Buf in standout mode
249 static void ni(Byte *); // display messages
250 static void edit_status(void); // show file status on status line
251 static void redraw(int); // force a full screen refresh
252 static void refresh(int); // update the terminal from screen[]
254 #ifdef BB_FEATURE_VI_SEARCH
255 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
256 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
257 #endif /* BB_FEATURE_VI_SEARCH */
258 #ifdef BB_FEATURE_VI_COLON
259 static void Hit_Return(void);
260 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
261 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
262 static void colon(Byte *); // execute the "colon" mode cmds
263 #endif /* BB_FEATURE_VI_COLON */
264 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
265 static Byte *get_input_line(Byte *); // get input line- use "status line"
266 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
267 #ifdef BB_FEATURE_VI_USE_SIGNALS
268 static void winch_sig(int); // catch window size changes
269 static void suspend_sig(int); // catch ctrl-Z
270 static void alarm_sig(int); // catch alarm time-outs
271 static void catch_sig(int); // catch ctrl-C
272 static void core_sig(int); // catch a core dump signal
273 #endif /* BB_FEATURE_VI_USE_SIGNALS */
274 #ifdef BB_FEATURE_VI_DOT_CMD
275 static void start_new_cmd_q(Byte); // new queue for command
276 static void end_cmd_q(); // stop saving input chars
277 #else /* BB_FEATURE_VI_DOT_CMD */
279 #endif /* BB_FEATURE_VI_DOT_CMD */
280 #ifdef BB_FEATURE_VI_WIN_RESIZE
281 static void window_size_get(int); // find out what size the window is
282 #endif /* BB_FEATURE_VI_WIN_RESIZE */
283 #ifdef BB_FEATURE_VI_SETOPTS
284 static void showmatching(Byte *); // show the matching pair () [] {}
285 #endif /* BB_FEATURE_VI_SETOPTS */
286 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
287 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
288 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
289 #ifdef BB_FEATURE_VI_YANKMARK
290 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
291 static Byte what_reg(void); // what is letter of current YDreg
292 static void check_context(Byte); // remember context for '' command
293 static Byte *swap_context(Byte *); // goto new context for '' command
294 #endif /* BB_FEATURE_VI_YANKMARK */
295 #ifdef BB_FEATURE_VI_CRASHME
296 static void crash_dummy();
297 static void crash_test();
298 static int crashme = 0;
299 #endif /* BB_FEATURE_VI_CRASHME */
302 extern int vi_main(int argc, char **argv)
306 #ifdef BB_FEATURE_VI_YANKMARK
308 #endif /* BB_FEATURE_VI_YANKMARK */
310 SOs = "\033[7m"; // Terminal standout mode on
311 SOn = "\033[0m"; // Terminal standout mode off
312 #ifdef BB_FEATURE_VI_CRASHME
313 (void) srand((long) getpid());
314 #endif /* BB_FEATURE_VI_CRASHME */
315 status_buffer = (Byte *) malloc(200); // hold messages to user
316 #ifdef BB_FEATURE_VI_READONLY
318 if (strncmp(argv[0], "view", 4) == 0) {
321 #endif /* BB_FEATURE_VI_READONLY */
322 #ifdef BB_FEATURE_VI_SETOPTS
326 #endif /* BB_FEATURE_VI_SETOPTS */
327 #ifdef BB_FEATURE_VI_YANKMARK
328 for (i = 0; i < 28; i++) {
330 } // init the yank regs
331 #endif /* BB_FEATURE_VI_YANKMARK */
332 #ifdef BB_FEATURE_VI_DOT_CMD
333 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
334 #endif /* BB_FEATURE_VI_DOT_CMD */
336 // 1- process $HOME/.exrc file
337 // 2- process EXINIT variable from environment
338 // 3- process command line args
339 while ((c = getopt(argc, argv, "hCR")) != -1) {
341 #ifdef BB_FEATURE_VI_CRASHME
345 #endif /* BB_FEATURE_VI_CRASHME */
346 #ifdef BB_FEATURE_VI_READONLY
347 case 'R': // Read-only flag
350 #endif /* BB_FEATURE_VI_READONLY */
351 //case 'r': // recover flag- ignore- we don't use tmp file
352 //case 'x': // encryption flag- ignore
353 //case 'c': // execute command first
354 //case 'h': // help -- just use default
361 // The argv array can be used by the ":next" and ":rewind" commands
363 fn_start = optind; // remember first file name for :next and :rew
366 //----- This is the main file handling loop --------------
367 if (optind >= argc) {
368 editing = 1; // 0= exit, 1= one file, 2= multiple files
371 for (; optind < argc; optind++) {
372 editing = 1; // 0=exit, 1=one file, 2+ =many files
375 cfn = (Byte *) strdup(argv[optind]);
379 //-----------------------------------------------------------
384 static void edit_file(Byte * fn)
389 #ifdef BB_FEATURE_VI_USE_SIGNALS
392 #endif /* BB_FEATURE_VI_USE_SIGNALS */
393 #ifdef BB_FEATURE_VI_YANKMARK
394 static Byte *cur_line;
395 #endif /* BB_FEATURE_VI_YANKMARK */
400 #ifdef BB_FEATURE_VI_WIN_RESIZE
402 #endif /* BB_FEATURE_VI_WIN_RESIZE */
403 new_screen(rows, columns); // get memory for virtual screen
406 cnt = file_size(fn); // file size
407 size = 2 * cnt; // 200% of file size
408 new_text(size); // get a text[] buffer
409 screenbegin = dot = end = text;
411 ch= file_insert(fn, text, cnt);
414 (void) char_insert(text, '\n'); // start empty buf with dummy line
416 file_modified = FALSE;
417 #ifdef BB_FEATURE_VI_YANKMARK
418 YDreg = 26; // default Yank/Delete reg
419 Ureg = 27; // hold orig line for "U" cmd
420 for (cnt = 0; cnt < 28; cnt++) {
423 mark[26] = mark[27] = text; // init "previous context"
424 #endif /* BB_FEATURE_VI_YANKMARK */
426 err_method = 1; // flash
427 last_forward_char = last_input_char = '\0';
432 #ifdef BB_FEATURE_VI_USE_SIGNALS
433 signal(SIGHUP, catch_sig);
434 signal(SIGINT, catch_sig);
435 signal(SIGALRM, alarm_sig);
436 signal(SIGTERM, catch_sig);
437 signal(SIGQUIT, core_sig);
438 signal(SIGILL, core_sig);
439 signal(SIGTRAP, core_sig);
440 signal(SIGIOT, core_sig);
441 signal(SIGABRT, core_sig);
442 signal(SIGFPE, core_sig);
443 signal(SIGBUS, core_sig);
444 signal(SIGSEGV, core_sig);
446 signal(SIGSYS, core_sig);
448 signal(SIGWINCH, winch_sig);
449 signal(SIGTSTP, suspend_sig);
450 sig = setjmp(restart);
454 msg = "(window resize)";
464 msg = "(I tried to touch invalid memory)";
468 psbs("-- caught signal %d %s--", sig, msg);
469 screenbegin = dot = text;
471 #endif /* BB_FEATURE_VI_USE_SIGNALS */
474 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
477 offset = 0; // no horizontal offset
479 #ifdef BB_FEATURE_VI_DOT_CMD
480 if (last_modifying_cmd != 0)
481 free(last_modifying_cmd);
482 if (ioq_start != NULL)
484 ioq = ioq_start = last_modifying_cmd = 0;
486 #endif /* BB_FEATURE_VI_DOT_CMD */
490 //------This is the main Vi cmd handling loop -----------------------
491 while (editing > 0) {
492 #ifdef BB_FEATURE_VI_CRASHME
494 if ((end - text) > 1) {
495 crash_dummy(); // generate a random command
499 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
503 #endif /* BB_FEATURE_VI_CRASHME */
504 last_input_char = c = get_one_char(); // get a cmd from user
505 #ifdef BB_FEATURE_VI_YANKMARK
506 // save a copy of the current line- for the 'U" command
507 if (begin_line(dot) != cur_line) {
508 cur_line = begin_line(dot);
509 text_yank(begin_line(dot), end_line(dot), Ureg);
511 #endif /* BB_FEATURE_VI_YANKMARK */
512 #ifdef BB_FEATURE_VI_DOT_CMD
513 // These are commands that change text[].
514 // Remember the input for the "." command
515 if (!adding2q && ioq_start == 0
516 && strchr((char *) modifying_cmds, c) != NULL) {
519 #endif /* BB_FEATURE_VI_DOT_CMD */
520 do_cmd(c); // execute the user command
522 // poll to see if there is input already waiting. if we are
523 // not able to display output fast enough to keep up, skip
524 // the display update until we catch up with input.
525 if (mysleep(0) == 0) {
526 // no input pending- so update output
530 #ifdef BB_FEATURE_VI_CRASHME
532 crash_test(); // test editor variables
533 #endif /* BB_FEATURE_VI_CRASHME */
535 //-------------------------------------------------------------------
537 place_cursor(rows, 0); // go to bottom of screen
538 clear_to_eol(); // Erase to end of line
542 static Byte readbuffer[BUFSIZ];
544 #ifdef BB_FEATURE_VI_CRASHME
545 static int totalcmds = 0;
546 static int Mp = 85; // Movement command Probability
547 static int Np = 90; // Non-movement command Probability
548 static int Dp = 96; // Delete command Probability
549 static int Ip = 97; // Insert command Probability
550 static int Yp = 98; // Yank command Probability
551 static int Pp = 99; // Put command Probability
552 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
553 char chars[20] = "\t012345 abcdABCD-=.$";
554 char *words[20] = { "this", "is", "a", "test",
555 "broadcast", "the", "emergency", "of",
556 "system", "quick", "brown", "fox",
557 "jumped", "over", "lazy", "dogs",
558 "back", "January", "Febuary", "March"
561 "You should have received a copy of the GNU General Public License\n",
562 "char c, cm, *cmd, *cmd1;\n",
563 "generate a command by percentages\n",
564 "Numbers may be typed as a prefix to some commands.\n",
565 "Quit, discarding changes!\n",
566 "Forced write, if permission originally not valid.\n",
567 "In general, any ex or ed command (such as substitute or delete).\n",
568 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
569 "Please get w/ me and I will go over it with you.\n",
570 "The following is a list of scheduled, committed changes.\n",
571 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
572 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
573 "Any question about transactions please contact Sterling Huxley.\n",
574 "I will try to get back to you by Friday, December 31.\n",
575 "This Change will be implemented on Friday.\n",
576 "Let me know if you have problems accessing this;\n",
577 "Sterling Huxley recently added you to the access list.\n",
578 "Would you like to go to lunch?\n",
579 "The last command will be automatically run.\n",
580 "This is too much english for a computer geek.\n",
582 char *multilines[20] = {
583 "You should have received a copy of the GNU General Public License\n",
584 "char c, cm, *cmd, *cmd1;\n",
585 "generate a command by percentages\n",
586 "Numbers may be typed as a prefix to some commands.\n",
587 "Quit, discarding changes!\n",
588 "Forced write, if permission originally not valid.\n",
589 "In general, any ex or ed command (such as substitute or delete).\n",
590 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
591 "Please get w/ me and I will go over it with you.\n",
592 "The following is a list of scheduled, committed changes.\n",
593 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
594 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
595 "Any question about transactions please contact Sterling Huxley.\n",
596 "I will try to get back to you by Friday, December 31.\n",
597 "This Change will be implemented on Friday.\n",
598 "Let me know if you have problems accessing this;\n",
599 "Sterling Huxley recently added you to the access list.\n",
600 "Would you like to go to lunch?\n",
601 "The last command will be automatically run.\n",
602 "This is too much english for a computer geek.\n",
605 // create a random command to execute
606 static void crash_dummy()
608 static int sleeptime; // how long to pause between commands
609 char c, cm, *cmd, *cmd1;
610 int i, cnt, thing, rbi, startrbi, percent;
612 // "dot" movement commands
613 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
615 // is there already a command running?
616 if (strlen((char *) readbuffer) > 0)
620 sleeptime = 0; // how long to pause between commands
621 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
622 // generate a command by percentages
623 percent = (int) lrand48() % 100; // get a number from 0-99
624 if (percent < Mp) { // Movement commands
625 // available commands
628 } else if (percent < Np) { // non-movement commands
629 cmd = "mz<>\'\""; // available commands
631 } else if (percent < Dp) { // Delete commands
632 cmd = "dx"; // available commands
634 } else if (percent < Ip) { // Inset commands
635 cmd = "iIaAsrJ"; // available commands
637 } else if (percent < Yp) { // Yank commands
638 cmd = "yY"; // available commands
640 } else if (percent < Pp) { // Put commands
641 cmd = "pP"; // available commands
644 // We do not know how to handle this command, try again
648 // randomly pick one of the available cmds from "cmd[]"
649 i = (int) lrand48() % strlen(cmd);
651 if (strchr(":\024", cm))
652 goto cd0; // dont allow these commands
653 readbuffer[rbi++] = cm; // put cmd into input buffer
655 // now we have the command-
656 // there are 1, 2, and multi char commands
657 // find out which and generate the rest of command as necessary
658 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
659 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
660 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
661 cmd1 = "abcdefghijklmnopqrstuvwxyz";
663 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
665 readbuffer[rbi++] = c; // add movement to input buffer
667 if (strchr("iIaAsc", cm)) { // multi-char commands
670 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
672 readbuffer[rbi++] = c; // add movement to input buffer
674 thing = (int) lrand48() % 4; // what thing to insert
675 cnt = (int) lrand48() % 10; // how many to insert
676 for (i = 0; i < cnt; i++) {
677 if (thing == 0) { // insert chars
678 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
679 } else if (thing == 1) { // insert words
680 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
681 strcat((char *) readbuffer, " ");
682 sleeptime = 0; // how fast to type
683 } else if (thing == 2) { // insert lines
684 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
685 sleeptime = 0; // how fast to type
686 } else { // insert multi-lines
687 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
688 sleeptime = 0; // how fast to type
691 strcat((char *) readbuffer, "\033");
696 (void) mysleep(sleeptime); // sleep 1/100 sec
699 // test to see if there are any errors
700 static void crash_test()
702 static time_t oldtim;
704 char d[2], buf[BUFSIZ], msg[BUFSIZ];
708 strcat((char *) msg, "end<text ");
711 strcat((char *) msg, "end>textend ");
714 strcat((char *) msg, "dot<text ");
717 strcat((char *) msg, "dot>end ");
719 if (screenbegin < text) {
720 strcat((char *) msg, "screenbegin<text ");
722 if (screenbegin > end - 1) {
723 strcat((char *) msg, "screenbegin>end-1 ");
726 if (strlen(msg) > 0) {
728 sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
729 write(1, buf, strlen(buf));
730 write(1, msg, strlen(msg));
731 write(1, "\n\n\n", 3);
732 write(1, "\033[7m[Hit return to continue]\033[0m", 32);
733 while (read(0, d, 1) > 0) {
734 if (d[0] == '\n' || d[0] == '\r')
739 tim = (time_t) time((time_t *) 0);
740 if (tim >= (oldtim + 3)) {
741 sprintf((char *) status_buffer,
742 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
743 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
748 #endif /* BB_FEATURE_VI_CRASHME */
750 //---------------------------------------------------------------------
751 //----- the Ascii Chart -----------------------------------------------
753 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
754 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
755 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
756 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
757 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
758 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
759 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
760 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
761 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
762 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
763 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
764 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
765 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
766 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
767 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
768 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
769 //---------------------------------------------------------------------
771 //----- Execute a Vi Command -----------------------------------
772 static void do_cmd(Byte c)
774 Byte c1, *p, *q, *msg, buf[9], *save_dot;
775 int cnt, i, j, dir, yf;
777 c1 = c; // quiet the compiler
778 cnt = yf = dir = 0; // quiet the compiler
779 p = q = save_dot = msg = buf; // quiet the compiler
780 memset(buf, '\0', 9); // clear buf
782 // we are 'R'eplacing the current *dot with new char
784 // don't Replace past E-o-l
785 cmd_mode = 1; // convert to insert
787 if (1 <= c && c <= 127) { // only ASCII chars
789 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
790 dot = char_insert(dot, c); // insert new char
796 // hitting "Insert" twice means "R" replace mode
797 if (c == VI_K_INSERT) goto dc5;
798 // insert the char c at "dot"
799 if (1 <= c && c <= 127) {
800 dot = char_insert(dot, c); // only ASCII chars
814 #ifdef BB_FEATURE_VI_CRASHME
815 case 0x14: // dc4 ctrl-T
816 crashme = (crashme == 0) ? 1 : 0;
818 #endif /* BB_FEATURE_VI_CRASHME */
848 //case 'u': // u- FIXME- there is no undo
850 default: // unrecognised command
859 end_cmd_q(); // stop adding to q
860 case 0x00: // nul- ignore
862 case 2: // ctrl-B scroll up full screen
863 case VI_K_PAGEUP: // Cursor Key Page Up
864 dot_scroll(rows - 2, -1);
866 #ifdef BB_FEATURE_VI_USE_SIGNALS
867 case 0x03: // ctrl-C interrupt
870 case 26: // ctrl-Z suspend
871 suspend_sig(SIGTSTP);
873 #endif /* BB_FEATURE_VI_USE_SIGNALS */
874 case 4: // ctrl-D scroll down half screen
875 dot_scroll((rows - 2) / 2, 1);
877 case 5: // ctrl-E scroll down one line
880 case 6: // ctrl-F scroll down full screen
881 case VI_K_PAGEDOWN: // Cursor Key Page Down
882 dot_scroll(rows - 2, 1);
884 case 7: // ctrl-G show current status
887 case 'h': // h- move left
888 case VI_K_LEFT: // cursor key Left
889 case 8: // ctrl-H- move left (This may be ERASE char)
890 case 127: // DEL- move left (This may be ERASE char)
896 case 10: // Newline ^J
897 case 'j': // j- goto next line, same col
898 case VI_K_DOWN: // cursor key Down
902 dot_next(); // go to next B-o-l
903 dot = move_to_col(dot, ccol + offset); // try stay in same col
905 case 12: // ctrl-L force redraw whole screen
906 case 18: // ctrl-R force redraw
907 place_cursor(0, 0); // put cursor in correct place
908 clear_to_eos(); // tel terminal to erase display
910 screen_erase(); // erase the internal screen buffer
911 refresh(TRUE); // this will redraw the entire display
913 case 13: // Carriage Return ^M
914 case '+': // +- goto next line
921 case 21: // ctrl-U scroll up half screen
922 dot_scroll((rows - 2) / 2, -1);
924 case 25: // ctrl-Y scroll up one line
930 cmd_mode = 0; // stop insrting
932 *status_buffer = '\0'; // clear status buffer
934 case ' ': // move right
935 case 'l': // move right
936 case VI_K_RIGHT: // Cursor Key Right
942 #ifdef BB_FEATURE_VI_YANKMARK
943 case '"': // "- name a register to use for Delete/Yank
952 case '\'': // '- goto a specific mark
959 if (text <= q && q < end) {
961 dot_begin(); // go to B-o-l
964 } else if (c1 == '\'') { // goto previous context
965 dot = swap_context(dot); // swap current and previous context
966 dot_begin(); // go to B-o-l
972 case 'm': // m- Mark a line
973 // this is really stupid. If there are any inserts or deletes
974 // between text[0] and dot then this mark will not point to the
975 // correct location! It could be off by many lines!
976 // Well..., at least its quick and dirty.
982 mark[(int) c1] = dot;
987 case 'P': // P- Put register before
988 case 'p': // p- put register after
991 psbs("Nothing in register %c", what_reg());
994 // are we putting whole lines or strings
995 if (strchr((char *) p, '\n') != NULL) {
997 dot_begin(); // putting lines- Put above
1000 // are we putting after very last line?
1001 if (end_line(dot) == (end - 1)) {
1002 dot = end; // force dot to end of text[]
1004 dot_next(); // next line, then put before
1009 dot_right(); // move to right, can move to NL
1011 dot = string_insert(dot, p); // insert the string
1012 end_cmd_q(); // stop adding to q
1014 case 'U': // U- Undo; replace current line with original version
1015 if (reg[Ureg] != 0) {
1016 p = begin_line(dot);
1018 p = text_hole_delete(p, q); // delete cur line
1019 p = string_insert(p, reg[Ureg]); // insert orig line
1024 #endif /* BB_FEATURE_VI_YANKMARK */
1025 case '$': // $- goto end of line
1026 case VI_K_END: // Cursor Key End
1030 dot = end_line(dot + 1);
1032 case '%': // %- find matching char of pair () [] {}
1033 for (q = dot; q < end && *q != '\n'; q++) {
1034 if (strchr("()[]{}", *q) != NULL) {
1035 // we found half of a pair
1036 p = find_pair(q, *q);
1048 case 'f': // f- forward to a user specified char
1049 last_forward_char = get_one_char(); // get the search char
1051 // dont seperate these two commands. 'f' depends on ';'
1053 //**** fall thru to ... 'i'
1054 case ';': // ;- look at rest of line for last forward char
1059 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1062 if (*q == last_forward_char)
1065 case '-': // -- goto prev line
1072 #ifdef BB_FEATURE_VI_DOT_CMD
1073 case '.': // .- repeat the last modifying command
1074 // Stuff the last_modifying_cmd back into stdin
1075 // and let it be re-executed.
1076 if (last_modifying_cmd != 0) {
1077 ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
1080 #endif /* BB_FEATURE_VI_DOT_CMD */
1081 #ifdef BB_FEATURE_VI_SEARCH
1082 case '?': // /- search for a pattern
1083 case '/': // /- search for a pattern
1086 q = get_input_line(buf); // get input line- use "status line"
1087 if (strlen((char *) q) == 1)
1088 goto dc3; // if no pat re-use old pat
1089 if (strlen((char *) q) > 1) { // new pat- save it and find
1090 // there is a new pat
1091 if (last_search_pattern != 0) {
1092 free(last_search_pattern);
1094 last_search_pattern = (Byte *) strdup((char *) q);
1095 goto dc3; // now find the pattern
1097 // user changed mind and erased the "/"- do nothing
1099 case 'N': // N- backward search for last pattern
1103 dir = BACK; // assume BACKWARD search
1105 if (last_search_pattern[0] == '?') {
1109 goto dc4; // now search for pattern
1111 case 'n': // n- repeat search for last pattern
1112 // search rest of text[] starting at next char
1113 // if search fails return orignal "p" not the "p+1" address
1118 if (last_search_pattern == 0) {
1119 msg = (Byte *) "No previous regular expression";
1122 if (last_search_pattern[0] == '/') {
1123 dir = FORWARD; // assume FORWARD search
1126 if (last_search_pattern[0] == '?') {
1131 q = char_search(p, last_search_pattern + 1, dir, FULL);
1133 dot = q; // good search, update "dot"
1137 // no pattern found between "dot" and "end"- continue at top
1142 q = char_search(p, last_search_pattern + 1, dir, FULL);
1143 if (q != NULL) { // found something
1144 dot = q; // found new pattern- goto it
1145 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1147 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1150 msg = (Byte *) "Pattern not found";
1155 case '{': // {- move backward paragraph
1156 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1157 if (q != NULL) { // found blank line
1158 dot = next_line(q); // move to next blank line
1161 case '}': // }- move forward paragraph
1162 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1163 if (q != NULL) { // found blank line
1164 dot = next_line(q); // move to next blank line
1167 #endif /* BB_FEATURE_VI_SEARCH */
1168 case '0': // 0- goto begining of line
1178 if (c == '0' && cmdcnt < 1) {
1179 dot_begin(); // this was a standalone zero
1181 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1184 case ':': // :- the colon mode commands
1185 #ifdef BB_FEATURE_VI_COLON
1186 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1187 colon(p); // execute the command
1188 #else /* BB_FEATURE_VI_COLON */
1189 *status_buffer = '\0'; // clear the status buffer
1190 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1191 clear_to_eol(); // clear the line
1192 write(1, ":", 1); // write out the : prompt
1193 for (cnt = 0; cnt < 8; cnt++) {
1194 c1 = get_one_char();
1195 if (c1 == '\n' || c1 == '\r' || c1 == 27) {
1199 buf[cnt + 1] = '\0';
1200 write(1, buf + cnt, 1); // echo the char
1202 cnt = strlen((char *) buf);
1203 if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
1204 strncasecmp((char *) buf, "q!", cnt) == 0) { // delete lines
1205 if (file_modified == TRUE && buf[1] != '!') {
1206 psbs("No write since last change (:quit! overrides)");
1210 } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
1211 strncasecmp((char *) buf, "wq", cnt) == 0) {
1212 cnt = file_write(cfn, text, end - 1);
1213 file_modified = FALSE;
1214 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1215 if (buf[1] == 'q') {
1218 } else { // unrecognised cmd
1221 #endif /* BB_FEATURE_VI_COLON */
1223 case '<': // <- Left shift something
1224 case '>': // >- Right shift something
1225 cnt = count_lines(text, dot); // remember what line we are on
1226 c1 = get_one_char(); // get the type of thing to delete
1227 find_range(&p, &q, c1);
1228 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1231 i = count_lines(p, q); // # of lines we are shifting
1232 for ( ; i > 0; i--, p = next_line(p)) {
1234 // shift left- remove tab or 8 spaces
1236 // shrink buffer 1 char
1237 (void) text_hole_delete(p, p);
1238 } else if (*p == ' ') {
1239 // we should be calculating columns, not just SPACE
1240 for (j = 0; *p == ' ' && j < tabstop; j++) {
1241 (void) text_hole_delete(p, p);
1244 } else if (c == '>') {
1245 // shift right -- add tab or 8 spaces
1246 (void) char_insert(p, '\t');
1249 dot = find_line(cnt); // what line were we on
1251 end_cmd_q(); // stop adding to q
1253 case 'A': // A- append at e-o-l
1254 dot_end(); // go to e-o-l
1255 //**** fall thru to ... 'a'
1256 case 'a': // a- append after current char
1261 case 'B': // B- back a blank-delimited Word
1262 case 'E': // E- end of a blank-delimited word
1263 case 'W': // W- forward a blank-delimited word
1270 if (c == 'W' || isspace(dot[dir])) {
1271 dot = skip_thing(dot, 1, dir, S_TO_WS);
1272 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1275 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1277 case 'C': // C- Change to e-o-l
1278 case 'D': // D- delete to e-o-l
1280 dot = dollar_line(dot); // move to before NL
1281 // copy text into a register and delete
1282 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1284 goto dc_i; // start inserting
1285 #ifdef BB_FEATURE_VI_DOT_CMD
1287 end_cmd_q(); // stop adding to q
1288 #endif /* BB_FEATURE_VI_DOT_CMD */
1290 case 'G': // G- goto to a line number (default= E-O-F)
1291 dot = end - 1; // assume E-O-F
1293 dot = find_line(cmdcnt); // what line is #cmdcnt
1297 case 'H': // H- goto top line on screen
1299 if (cmdcnt > (rows - 1)) {
1300 cmdcnt = (rows - 1);
1307 case 'I': // I- insert before first non-blank
1310 //**** fall thru to ... 'i'
1311 case 'i': // i- insert before current char
1312 case VI_K_INSERT: // Cursor Key Insert
1314 cmd_mode = 1; // start insrting
1315 psb("-- Insert --");
1317 case 'J': // J- join current and next lines together
1321 dot_end(); // move to NL
1322 if (dot < end - 1) { // make sure not last char in text[]
1323 *dot++ = ' '; // replace NL with space
1324 while (isblnk(*dot)) { // delete leading WS
1328 end_cmd_q(); // stop adding to q
1330 case 'L': // L- goto bottom line on screen
1332 if (cmdcnt > (rows - 1)) {
1333 cmdcnt = (rows - 1);
1341 case 'M': // M- goto middle line on screen
1343 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
1344 dot = next_line(dot);
1346 case 'O': // O- open a empty line above
1348 p = begin_line(dot);
1349 if (p[-1] == '\n') {
1351 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1353 dot = char_insert(dot, '\n');
1356 dot = char_insert(dot, '\n'); // i\n\033
1361 case 'R': // R- continuous Replace char
1364 psb("-- Replace --");
1366 case 'X': // X- delete char before dot
1367 case 'x': // x- delete the current char
1368 case 's': // s- substitute the current char
1375 if (dot[dir] != '\n') {
1377 dot--; // delete prev char
1378 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1381 goto dc_i; // start insrting
1382 end_cmd_q(); // stop adding to q
1384 case 'Z': // Z- if modified, {write}; exit
1385 // ZZ means to save file (if necessary), then exit
1386 c1 = get_one_char();
1391 if (file_modified == TRUE
1392 #ifdef BB_FEATURE_VI_READONLY
1393 && readonly == FALSE
1394 #endif /* BB_FEATURE_VI_READONLY */
1396 cnt = file_write(cfn, text, end - 1);
1397 if (cnt == (end - 1 - text + 1)) {
1404 case '^': // ^- move to first non-blank on line
1408 case 'b': // b- back a word
1409 case 'e': // e- end of word
1416 if ((dot + dir) < text || (dot + dir) > end - 1)
1419 if (isspace(*dot)) {
1420 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1422 if (isalnum(*dot) || *dot == '_') {
1423 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1424 } else if (ispunct(*dot)) {
1425 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1428 case 'c': // c- change something
1429 case 'd': // d- delete something
1430 #ifdef BB_FEATURE_VI_YANKMARK
1431 case 'y': // y- yank something
1432 case 'Y': // Y- Yank a line
1433 #endif /* BB_FEATURE_VI_YANKMARK */
1434 yf = YANKDEL; // assume either "c" or "d"
1435 #ifdef BB_FEATURE_VI_YANKMARK
1436 if (c == 'y' || c == 'Y')
1438 #endif /* BB_FEATURE_VI_YANKMARK */
1441 c1 = get_one_char(); // get the type of thing to delete
1442 find_range(&p, &q, c1);
1443 if (c1 == 27) { // ESC- user changed mind and wants out
1444 c = c1 = 27; // Escape- do nothing
1445 } else if (strchr("wW", c1)) {
1447 // don't include trailing WS as part of word
1448 while (isblnk(*q)) {
1449 if (q <= text || q[-1] == '\n')
1454 dot = yank_delete(p, q, 0, yf); // delete word
1455 } else if (strchr("^0bBeE$", c1)) {
1456 // single line copy text into a register and delete
1457 dot = yank_delete(p, q, 0, yf); // delete word
1458 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
1459 // multiple line copy text into a register and delete
1460 dot = yank_delete(p, q, 1, yf); // delete lines
1462 dot = char_insert(dot, '\n');
1463 // on the last line of file don't move to prev line
1464 if (dot != (end-1)) {
1467 } else if (c == 'd') {
1472 // could not recognize object
1473 c = c1 = 27; // error-
1477 // if CHANGING, not deleting, start inserting after the delete
1479 strcpy((char *) buf, "Change");
1480 goto dc_i; // start inserting
1483 strcpy((char *) buf, "Delete");
1485 #ifdef BB_FEATURE_VI_YANKMARK
1486 if (c == 'y' || c == 'Y') {
1487 strcpy((char *) buf, "Yank");
1490 q = p + strlen((char *) p);
1491 for (cnt = 0; p <= q; p++) {
1495 psb("%s %d lines (%d chars) using [%c]",
1496 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1497 #endif /* BB_FEATURE_VI_YANKMARK */
1498 end_cmd_q(); // stop adding to q
1501 case 'k': // k- goto prev line, same col
1502 case VI_K_UP: // cursor key Up
1507 dot = move_to_col(dot, ccol + offset); // try stay in same col
1509 case 'r': // r- replace the current char with user input
1510 c1 = get_one_char(); // get the replacement char
1513 file_modified = TRUE; // has the file been modified
1515 end_cmd_q(); // stop adding to q
1517 case 'w': // w- forward a word
1521 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1522 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1523 } else if (ispunct(*dot)) { // we are on PUNCT
1524 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1527 dot++; // move over word
1528 if (isspace(*dot)) {
1529 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1533 c1 = get_one_char(); // get the replacement char
1536 cnt = (rows - 2) / 2; // put dot at center
1538 cnt = rows - 2; // put dot at bottom
1539 screenbegin = begin_line(dot); // start dot at top
1540 dot_scroll(cnt, -1);
1542 case '|': // |- move to column "cmdcnt"
1543 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1545 case '~': // ~- flip the case of letters a-z -> A-Z
1549 if (islower(*dot)) {
1550 *dot = toupper(*dot);
1551 file_modified = TRUE; // has the file been modified
1552 } else if (isupper(*dot)) {
1553 *dot = tolower(*dot);
1554 file_modified = TRUE; // has the file been modified
1557 end_cmd_q(); // stop adding to q
1559 //----- The Cursor and Function Keys -----------------------------
1560 case VI_K_HOME: // Cursor Key Home
1563 // The Fn keys could point to do_macro which could translate them
1564 case VI_K_FUN1: // Function Key F1
1565 case VI_K_FUN2: // Function Key F2
1566 case VI_K_FUN3: // Function Key F3
1567 case VI_K_FUN4: // Function Key F4
1568 case VI_K_FUN5: // Function Key F5
1569 case VI_K_FUN6: // Function Key F6
1570 case VI_K_FUN7: // Function Key F7
1571 case VI_K_FUN8: // Function Key F8
1572 case VI_K_FUN9: // Function Key F9
1573 case VI_K_FUN10: // Function Key F10
1574 case VI_K_FUN11: // Function Key F11
1575 case VI_K_FUN12: // Function Key F12
1580 // if text[] just became empty, add back an empty line
1582 (void) char_insert(text, '\n'); // start empty buf with dummy line
1585 // it is OK for dot to exactly equal to end, otherwise check dot validity
1587 dot = bound_dot(dot); // make sure "dot" is valid
1589 #ifdef BB_FEATURE_VI_YANKMARK
1590 check_context(c); // update the current context
1591 #endif /* BB_FEATURE_VI_YANKMARK */
1594 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1595 cnt = dot - begin_line(dot);
1596 // Try to stay off of the Newline
1597 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1601 //----- The Colon commands -------------------------------------
1602 #ifdef BB_FEATURE_VI_COLON
1603 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
1608 #ifdef BB_FEATURE_VI_YANKMARK
1610 #endif /* BB_FEATURE_VI_YANKMARK */
1611 #ifdef BB_FEATURE_VI_SEARCH
1612 Byte *pat, buf[BUFSIZ];
1613 #endif /* BB_FEATURE_VI_SEARCH */
1615 *addr = -1; // assume no addr
1616 if (*p == '.') { // the current line
1618 q = begin_line(dot);
1619 *addr = count_lines(text, q);
1620 #ifdef BB_FEATURE_VI_YANKMARK
1621 } else if (*p == '\'') { // is this a mark addr
1625 if (c >= 'a' && c <= 'z') {
1629 if (q != NULL) { // is mark valid
1630 *addr = count_lines(text, q); // count lines
1633 #endif /* BB_FEATURE_VI_YANKMARK */
1634 #ifdef BB_FEATURE_VI_SEARCH
1635 } else if (*p == '/') { // a search pattern
1637 for (p++; *p; p++) {
1643 pat = (Byte *) strdup((char *) buf); // save copy of pattern
1646 q = char_search(dot, pat, FORWARD, FULL);
1648 *addr = count_lines(text, q);
1651 #endif /* BB_FEATURE_VI_SEARCH */
1652 } else if (*p == '$') { // the last line in file
1654 q = begin_line(end - 1);
1655 *addr = count_lines(text, q);
1656 } else if (isdigit(*p)) { // specific line number
1657 sscanf((char *) p, "%d%n", addr, &st);
1659 } else { // I don't reconise this
1660 // unrecognised address- assume -1
1666 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
1668 //----- get the address' i.e., 1,3 'a,'b -----
1669 // get FIRST addr, if present
1671 p++; // skip over leading spaces
1672 if (*p == '%') { // alias for 1,$
1675 *e = count_lines(text, end-1);
1678 p = get_one_address(p, b);
1681 if (*p == ',') { // is there a address seperator
1685 // get SECOND addr, if present
1686 p = get_one_address(p, e);
1690 p++; // skip over trailing spaces
1694 static void colon(Byte * buf)
1696 Byte c, *orig_buf, *buf1, *q, *r;
1697 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
1698 int i, l, li, ch, st, b, e;
1699 int useforce, forced;
1702 // :3154 // if (-e line 3154) goto it else stay put
1703 // :4,33w! foo // write a portion of buffer to file "foo"
1704 // :w // write all of buffer to current file
1706 // :q! // quit- dont care about modified file
1707 // :'a,'z!sort -u // filter block through sort
1708 // :'f // goto mark "f"
1709 // :'fl // list literal the mark "f" line
1710 // :.r bar // read file "bar" into buffer before dot
1711 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1712 // :/xyz/ // goto the "xyz" line
1713 // :s/find/replace/ // substitute pattern "find" with "replace"
1714 // :!<cmd> // run <cmd> then return
1716 if (strlen((char *) buf) <= 0)
1719 buf++; // move past the ':'
1721 forced = useforce = FALSE;
1722 li = st = ch = i = 0;
1724 q = text; // assume 1,$ for the range
1726 li = count_lines(text, end - 1);
1727 fn = cfn; // default to current file
1728 memset(cmd, '\0', BUFSIZ); // clear cmd[]
1729 memset(args, '\0', BUFSIZ); // clear args[]
1731 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1732 buf = get_address(buf, &b, &e);
1734 // remember orig command line
1737 // get the COMMAND into cmd[]
1739 while (*buf != '\0') {
1744 // get any ARGuments
1745 while (isblnk(*buf))
1747 strcpy((char *) args, (char *) buf);
1748 if (last_char_is((char *)cmd,'!')) {
1750 cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
1753 // if there is only one addr, then the addr
1754 // is the line number of the single line the
1755 // user wants. So, reset the end
1756 // pointer to point at end of the "b" line
1757 q = find_line(b); // what line is #b
1762 // we were given two addrs. change the
1763 // end pointer to the addr given by user.
1764 r = find_line(e); // what line is #e
1768 // ------------ now look for the command ------------
1769 i = strlen((char *) cmd);
1770 if (i == 0) { // :123CR goto line #123
1772 dot = find_line(b); // what line is #b
1775 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
1776 // :!ls run the <cmd>
1777 (void) alarm(0); // wait for input- no alarms
1778 place_cursor(rows - 1, 0); // go to Status line
1779 clear_to_eol(); // clear the line
1781 system(orig_buf+1); // run the cmd
1783 Hit_Return(); // let user see results
1784 (void) alarm(3); // done waiting for input
1785 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1786 if (b < 0) { // no addr given- use defaults
1787 b = e = count_lines(text, dot);
1790 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1791 if (b < 0) { // no addr given- use defaults
1792 q = begin_line(dot); // assume .,. for the range
1795 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1797 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1800 // don't edit, if the current file has been modified
1801 if (file_modified == TRUE && useforce != TRUE) {
1802 psbs("No write since last change (:edit! overrides)");
1805 if (strlen(args) > 0) {
1806 // the user supplied a file name
1808 } else if (cfn != 0 && strlen(cfn) > 0) {
1809 // no user supplied name- use the current filename
1813 // no user file name, no current name- punt
1814 psbs("No current filename");
1818 // see if file exists- if not, its just a new file request
1819 if ((sr=stat((char*)fn, &st_buf)) < 0) {
1820 // This is just a request for a new file creation.
1821 // The file_insert below will fail but we get
1822 // an empty buffer with a file name. Then the "write"
1823 // command can do the create.
1825 if ((st_buf.st_mode & (S_IFREG)) == 0) {
1826 // This is not a regular file
1827 psbs("\"%s\" is not a regular file", fn);
1830 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
1831 // dont have any read permissions
1832 psbs("\"%s\" is not readable", fn);
1837 // There is a read-able regular file
1838 // make this the current file
1839 q = (Byte *) strdup((char *) fn); // save the cfn
1841 free(cfn); // free the old name
1842 cfn = q; // remember new cfn
1845 // delete all the contents of text[]
1846 new_text(2 * file_size(fn));
1847 screenbegin = dot = end = text;
1850 ch = file_insert(fn, text, file_size(fn));
1853 // start empty buf with dummy line
1854 (void) char_insert(text, '\n');
1857 file_modified = FALSE;
1858 #ifdef BB_FEATURE_VI_YANKMARK
1859 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
1860 free(reg[Ureg]); // free orig line reg- for 'U'
1863 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
1864 free(reg[YDreg]); // free default yank/delete register
1867 for (li = 0; li < 28; li++) {
1870 #endif /* BB_FEATURE_VI_YANKMARK */
1871 // how many lines in text[]?
1872 li = count_lines(text, end - 1);
1874 #ifdef BB_FEATURE_VI_READONLY
1876 #endif /* BB_FEATURE_VI_READONLY */
1878 (sr < 0 ? " [New file]" : ""),
1879 #ifdef BB_FEATURE_VI_READONLY
1880 (readonly == TRUE ? " [Read only]" : ""),
1881 #endif /* BB_FEATURE_VI_READONLY */
1883 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1884 if (b != -1 || e != -1) {
1885 ni((Byte *) "No address allowed on this command");
1888 if (strlen((char *) args) > 0) {
1889 // user wants a new filename
1892 cfn = (Byte *) strdup((char *) args);
1894 // user wants file status info
1897 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1898 // print out values of all features
1899 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1900 clear_to_eol(); // clear the line
1905 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1906 if (b < 0) { // no addr given- use defaults
1907 q = begin_line(dot); // assume .,. for the range
1910 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1911 clear_to_eol(); // clear the line
1912 write(1, "\r\n", 2);
1913 for (; q <= r; q++) {
1919 } else if (*q < ' ') {
1927 #ifdef BB_FEATURE_VI_SET
1929 #endif /* BB_FEATURE_VI_SET */
1931 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1932 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1933 if (useforce == TRUE) {
1934 // force end of argv list
1941 // don't exit if the file been modified
1942 if (file_modified == TRUE) {
1943 psbs("No write since last change (:%s! overrides)",
1944 (*cmd == 'q' ? "quit" : "next"));
1947 // are there other file to edit
1948 if (*cmd == 'q' && optind < save_argc - 1) {
1949 psbs("%d more file to edit", (save_argc - optind - 1));
1952 if (*cmd == 'n' && optind >= save_argc - 1) {
1953 psbs("No more files to edit");
1957 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1959 if (strlen((char *) fn) <= 0) {
1960 psbs("No filename given");
1963 if (b < 0) { // no addr given- use defaults
1964 q = begin_line(dot); // assume "dot"
1966 // read after current line- unless user said ":0r foo"
1969 l= readonly; // remember current files' status
1970 ch = file_insert(fn, q, file_size(fn));
1973 goto vc1; // nothing was inserted
1974 // how many lines in text[]?
1975 li = count_lines(q, q + ch - 1);
1977 #ifdef BB_FEATURE_VI_READONLY
1979 #endif /* BB_FEATURE_VI_READONLY */
1981 #ifdef BB_FEATURE_VI_READONLY
1982 (readonly == TRUE ? " [Read only]" : ""),
1983 #endif /* BB_FEATURE_VI_READONLY */
1986 // if the insert is before "dot" then we need to update
1989 file_modified = TRUE;
1991 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
1992 if (file_modified == TRUE && useforce != TRUE) {
1993 psbs("No write since last change (:rewind! overrides)");
1995 // reset the filenames to edit
1996 optind = fn_start - 1;
1999 #ifdef BB_FEATURE_VI_SET
2000 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
2001 i = 0; // offset into args
2002 if (strlen((char *) args) == 0) {
2003 // print out values of all options
2004 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
2005 clear_to_eol(); // clear the line
2006 printf("----------------------------------------\r\n");
2007 #ifdef BB_FEATURE_VI_SETOPTS
2010 printf("autoindent ");
2013 printf("ignorecase ");
2016 printf("showmatch ");
2017 printf("tabstop=%d ", tabstop);
2018 #endif /* BB_FEATURE_VI_SETOPTS */
2022 if (strncasecmp((char *) args, "no", 2) == 0)
2023 i = 2; // ":set noautoindent"
2024 #ifdef BB_FEATURE_VI_SETOPTS
2025 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
2026 strncasecmp((char *) args + i, "ai", 2) == 0) {
2027 autoindent = (i == 2) ? 0 : 1;
2029 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
2030 strncasecmp((char *) args + i, "ic", 2) == 0) {
2031 ignorecase = (i == 2) ? 0 : 1;
2033 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
2034 strncasecmp((char *) args + i, "sm", 2) == 0) {
2035 showmatch = (i == 2) ? 0 : 1;
2037 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
2038 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
2039 if (ch > 0 && ch < columns - 1)
2042 #endif /* BB_FEATURE_VI_SETOPTS */
2043 #endif /* BB_FEATURE_VI_SET */
2044 #ifdef BB_FEATURE_VI_SEARCH
2045 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
2049 // F points to the "find" pattern
2050 // R points to the "replace" pattern
2051 // replace the cmd line delimiters "/" with NULLs
2052 gflag = 0; // global replace flag
2053 c = orig_buf[1]; // what is the delimiter
2054 F = orig_buf + 2; // start of "find"
2055 R = (Byte *) strchr((char *) F, c); // middle delimiter
2056 *R++ = '\0'; // terminate "find"
2057 buf1 = (Byte *) strchr((char *) R, c);
2058 *buf1++ = '\0'; // terminate "replace"
2059 if (*buf1 == 'g') { // :s/foo/bar/g
2061 gflag++; // turn on gflag
2064 if (b < 0) { // maybe :s/foo/bar/
2065 q = begin_line(dot); // start with cur line
2066 b = count_lines(text, q); // cur line number
2069 e = b; // maybe :.s/foo/bar/
2070 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2071 ls = q; // orig line start
2073 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
2075 // we found the "find" pattern- delete it
2076 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
2077 // inset the "replace" patern
2078 (void) string_insert(buf1, R); // insert the string
2079 // check for "global" :s/foo/bar/g
2081 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
2082 q = buf1 + strlen((char *) R);
2083 goto vc4; // don't let q move past cur line
2089 #endif /* BB_FEATURE_VI_SEARCH */
2090 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
2091 psb("%s", vi_Version);
2092 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
2093 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
2094 // is there a file name to write to?
2095 if (strlen((char *) args) > 0) {
2098 #ifdef BB_FEATURE_VI_READONLY
2099 if (readonly == TRUE && useforce == FALSE) {
2100 psbs("\"%s\" File is read only", fn);
2103 #endif /* BB_FEATURE_VI_READONLY */
2104 // how many lines in text[]?
2105 li = count_lines(q, r);
2107 // see if file exists- if not, its just a new file request
2108 if (useforce == TRUE) {
2109 // if "fn" is not write-able, chmod u+w
2110 // sprintf(syscmd, "chmod u+w %s", fn);
2114 l = file_write(fn, q, r);
2115 if (useforce == TRUE && forced == TRUE) {
2117 // sprintf(syscmd, "chmod u-w %s", fn);
2121 psb("\"%s\" %dL, %dC", fn, li, l);
2122 if (q == text && r == end - 1 && l == ch)
2123 file_modified = FALSE;
2124 if (cmd[1] == 'q' && l == ch) {
2127 #ifdef BB_FEATURE_VI_READONLY
2129 #endif /* BB_FEATURE_VI_READONLY */
2130 #ifdef BB_FEATURE_VI_YANKMARK
2131 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2132 if (b < 0) { // no addr given- use defaults
2133 q = begin_line(dot); // assume .,. for the range
2136 text_yank(q, r, YDreg);
2137 li = count_lines(q, r);
2138 psb("Yank %d lines (%d chars) into [%c]",
2139 li, strlen((char *) reg[YDreg]), what_reg());
2140 #endif /* BB_FEATURE_VI_YANKMARK */
2146 dot = bound_dot(dot); // make sure "dot" is valid
2150 static void Hit_Return(void)
2154 standout_start(); // start reverse video
2155 write(1, "[Hit return to continue]", 24);
2156 standout_end(); // end reverse video
2157 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2159 redraw(TRUE); // force redraw all
2161 #endif /* BB_FEATURE_VI_COLON */
2163 //----- Synchronize the cursor to Dot --------------------------
2164 static void sync_cursor(Byte * d, int *row, int *col)
2166 Byte *beg_cur, *end_cur; // begin and end of "d" line
2167 Byte *beg_scr, *end_scr; // begin and end of screen
2171 beg_cur = begin_line(d); // first char of cur line
2172 end_cur = end_line(d); // last char of cur line
2174 beg_scr = end_scr = screenbegin; // first char of screen
2175 end_scr = end_screen(); // last char of screen
2177 if (beg_cur < screenbegin) {
2178 // "d" is before top line on screen
2179 // how many lines do we have to move
2180 cnt = count_lines(beg_cur, screenbegin);
2182 screenbegin = beg_cur;
2183 if (cnt > (rows - 1) / 2) {
2184 // we moved too many lines. put "dot" in middle of screen
2185 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2186 screenbegin = prev_line(screenbegin);
2189 } else if (beg_cur > end_scr) {
2190 // "d" is after bottom line on screen
2191 // how many lines do we have to move
2192 cnt = count_lines(end_scr, beg_cur);
2193 if (cnt > (rows - 1) / 2)
2194 goto sc1; // too many lines
2195 for (ro = 0; ro < cnt - 1; ro++) {
2196 // move screen begin the same amount
2197 screenbegin = next_line(screenbegin);
2198 // now, move the end of screen
2199 end_scr = next_line(end_scr);
2200 end_scr = end_line(end_scr);
2203 // "d" is on screen- find out which row
2205 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2211 // find out what col "d" is on
2213 do { // drive "co" to correct column
2214 if (*tp == '\n' || *tp == '\0')
2218 co += ((tabstop - 1) - (co % tabstop));
2219 } else if (*tp < ' ') {
2220 co++; // display as ^X, use 2 columns
2222 } while (tp++ < d && ++co);
2224 // "co" is the column where "dot" is.
2225 // The screen has "columns" columns.
2226 // The currently displayed columns are 0+offset -- columns+ofset
2227 // |-------------------------------------------------------------|
2229 // offset | |------- columns ----------------|
2231 // If "co" is already in this range then we do not have to adjust offset
2232 // but, we do have to subtract the "offset" bias from "co".
2233 // If "co" is outside this range then we have to change "offset".
2234 // If the first char of a line is a tab the cursor will try to stay
2235 // in column 7, but we have to set offset to 0.
2237 if (co < 0 + offset) {
2240 if (co >= columns + offset) {
2241 offset = co - columns + 1;
2243 // if the first char of the line is a tab, and "dot" is sitting on it
2244 // force offset to 0.
2245 if (d == beg_cur && *d == '\t') {
2254 //----- Text Movement Routines ---------------------------------
2255 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2257 while (p > text && p[-1] != '\n')
2258 p--; // go to cur line B-o-l
2262 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2264 while (p < end - 1 && *p != '\n')
2265 p++; // go to cur line E-o-l
2269 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2271 while (p < end - 1 && *p != '\n')
2272 p++; // go to cur line E-o-l
2273 // Try to stay off of the Newline
2274 if (*p == '\n' && (p - begin_line(p)) > 0)
2279 static Byte *prev_line(Byte * p) // return pointer first char prev line
2281 p = begin_line(p); // goto begining of cur line
2282 if (p[-1] == '\n' && p > text)
2283 p--; // step to prev line
2284 p = begin_line(p); // goto begining of prev line
2288 static Byte *next_line(Byte * p) // return pointer first char next line
2291 if (*p == '\n' && p < end - 1)
2292 p++; // step to next line
2296 //----- Text Information Routines ------------------------------
2297 static Byte *end_screen(void)
2302 // find new bottom line
2304 for (cnt = 0; cnt < rows - 2; cnt++)
2310 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2315 if (stop < start) { // start and stop are backwards- reverse them
2321 stop = end_line(stop); // get to end of this line
2322 for (q = start; q <= stop && q <= end - 1; q++) {
2329 static Byte *find_line(int li) // find begining of line #li
2333 for (q = text; li > 1; li--) {
2339 //----- Dot Movement Routines ----------------------------------
2340 static void dot_left(void)
2342 if (dot > text && dot[-1] != '\n')
2346 static void dot_right(void)
2348 if (dot < end - 1 && *dot != '\n')
2352 static void dot_begin(void)
2354 dot = begin_line(dot); // return pointer to first char cur line
2357 static void dot_end(void)
2359 dot = end_line(dot); // return pointer to last char cur line
2362 static Byte *move_to_col(Byte * p, int l)
2369 if (*p == '\n' || *p == '\0')
2373 co += ((tabstop - 1) - (co % tabstop));
2374 } else if (*p < ' ') {
2375 co++; // display as ^X, use 2 columns
2377 } while (++co <= l && p++ < end);
2381 static void dot_next(void)
2383 dot = next_line(dot);
2386 static void dot_prev(void)
2388 dot = prev_line(dot);
2391 static void dot_scroll(int cnt, int dir)
2395 for (; cnt > 0; cnt--) {
2398 // ctrl-Y scroll up one line
2399 screenbegin = prev_line(screenbegin);
2402 // ctrl-E scroll down one line
2403 screenbegin = next_line(screenbegin);
2406 // make sure "dot" stays on the screen so we dont scroll off
2407 if (dot < screenbegin)
2409 q = end_screen(); // find new bottom line
2411 dot = begin_line(q); // is dot is below bottom line?
2415 static void dot_skip_over_ws(void)
2418 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2422 static void dot_delete(void) // delete the char at 'dot'
2424 (void) text_hole_delete(dot, dot);
2427 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2429 if (p >= end && end > text) {
2431 indicate_error('1');
2435 indicate_error('2');
2440 //----- Helper Utility Routines --------------------------------
2442 //----------------------------------------------------------------
2443 //----- Char Routines --------------------------------------------
2444 /* Chars that are part of a word-
2445 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2446 * Chars that are Not part of a word (stoppers)
2447 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2448 * Chars that are WhiteSpace
2449 * TAB NEWLINE VT FF RETURN SPACE
2450 * DO NOT COUNT NEWLINE AS WHITESPACE
2453 static Byte *new_screen(int ro, int co)
2457 screensize = ro * co + 8;
2458 screen = (Byte *) malloc(screensize);
2462 static Byte *new_text(int size)
2465 size = 10240; // have a minimum size for new files
2470 text = (Byte *) malloc(size + 8);
2471 memset(text, '\0', size); // clear new text[]
2472 //text += 4; // leave some room for "oops"
2473 textend = text + size - 1;
2474 //textend -= 4; // leave some root for "oops"
2478 #ifdef BB_FEATURE_VI_SEARCH
2479 static int mycmp(Byte * s1, Byte * s2, int len)
2483 i = strncmp((char *) s1, (char *) s2, len);
2484 #ifdef BB_FEATURE_VI_SETOPTS
2486 i = strncasecmp((char *) s1, (char *) s2, len);
2488 #endif /* BB_FEATURE_VI_SETOPTS */
2492 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2494 #ifndef REGEX_SEARCH
2498 len = strlen((char *) pat);
2499 if (dir == FORWARD) {
2500 stop = end - 1; // assume range is p - end-1
2501 if (range == LIMITED)
2502 stop = next_line(p); // range is to next line
2503 for (start = p; start < stop; start++) {
2504 if (mycmp(start, pat, len) == 0) {
2508 } else if (dir == BACK) {
2509 stop = text; // assume range is text - p
2510 if (range == LIMITED)
2511 stop = prev_line(p); // range is to prev line
2512 for (start = p - len; start >= stop; start--) {
2513 if (mycmp(start, pat, len) == 0) {
2518 // pattern not found
2520 #else /*REGEX_SEARCH */
2522 struct re_pattern_buffer preg;
2526 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2532 // assume a LIMITED forward search
2540 // count the number of chars to search over, forward or backward
2544 // RANGE could be negative if we are searching backwards
2547 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2549 // The pattern was not compiled
2550 psbs("bad search pattern: \"%s\": %s", pat, q);
2551 i = 0; // return p if pattern not compiled
2561 // search for the compiled pattern, preg, in p[]
2562 // range < 0- search backward
2563 // range > 0- search forward
2565 // re_search() < 0 not found or error
2566 // re_search() > 0 index of found pattern
2567 // struct pattern char int int int struct reg
2568 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2569 i = re_search(&preg, q, size, 0, range, 0);
2572 i = 0; // return NULL if pattern not found
2575 if (dir == FORWARD) {
2581 #endif /*REGEX_SEARCH */
2583 #endif /* BB_FEATURE_VI_SEARCH */
2585 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2587 if (c == 22) { // Is this an ctrl-V?
2588 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2589 p--; // backup onto ^
2590 refresh(FALSE); // show the ^
2594 file_modified = TRUE; // has the file been modified
2595 } else if (c == 27) { // Is this an ESC?
2598 end_cmd_q(); // stop adding to q
2599 *status_buffer = '\0'; // clear the status buffer
2600 if (p[-1] != '\n') {
2603 } else if (c == erase_char) { // Is this a BS
2605 if (p[-1] != '\n') {
2607 p = text_hole_delete(p, p); // shrink buffer 1 char
2608 #ifdef BB_FEATURE_VI_DOT_CMD
2609 // also rmove char from last_modifying_cmd
2610 if (strlen((char *) last_modifying_cmd) > 0) {
2613 q = last_modifying_cmd;
2614 q[strlen((char *) q) - 1] = '\0'; // erase BS
2615 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2617 #endif /* BB_FEATURE_VI_DOT_CMD */
2620 // insert a char into text[]
2621 Byte *sp; // "save p"
2624 c = '\n'; // translate \r to \n
2625 sp = p; // remember addr of insert
2626 p = stupid_insert(p, c); // insert the char
2627 #ifdef BB_FEATURE_VI_SETOPTS
2628 if (showmatch && strchr(")]}", *sp) != NULL) {
2631 if (autoindent && c == '\n') { // auto indent the new line
2634 q = prev_line(p); // use prev line as templet
2635 for (; isblnk(*q); q++) {
2636 p = stupid_insert(p, *q); // insert the char
2639 #endif /* BB_FEATURE_VI_SETOPTS */
2644 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2646 p = text_hole_make(p, 1);
2649 file_modified = TRUE; // has the file been modified
2655 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2657 Byte *save_dot, *p, *q;
2663 if (strchr("cdy><", c)) {
2664 // these cmds operate on whole lines
2665 p = q = begin_line(p);
2666 for (cnt = 1; cnt < cmdcnt; cnt++) {
2670 } else if (strchr("^%$0bBeE", c)) {
2671 // These cmds operate on char positions
2672 do_cmd(c); // execute movement cmd
2674 } else if (strchr("wW", c)) {
2675 do_cmd(c); // execute movement cmd
2677 dot--; // move back off of next word
2678 if (dot > text && *dot == '\n')
2679 dot--; // stay off NL
2681 } else if (strchr("H-k{", c)) {
2682 // these operate on multi-lines backwards
2683 q = end_line(dot); // find NL
2684 do_cmd(c); // execute movement cmd
2687 } else if (strchr("L+j}\r\n", c)) {
2688 // these operate on multi-lines forwards
2689 p = begin_line(dot);
2690 do_cmd(c); // execute movement cmd
2691 dot_end(); // find NL
2694 c = 27; // error- return an ESC char
2707 static int st_test(Byte * p, int type, int dir, Byte * tested)
2717 if (type == S_BEFORE_WS) {
2719 test = ((!isspace(c)) || c == '\n');
2721 if (type == S_TO_WS) {
2723 test = ((!isspace(c)) || c == '\n');
2725 if (type == S_OVER_WS) {
2727 test = ((isspace(c)));
2729 if (type == S_END_PUNCT) {
2731 test = ((ispunct(c)));
2733 if (type == S_END_ALNUM) {
2735 test = ((isalnum(c)) || c == '_');
2741 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2745 while (st_test(p, type, dir, &c)) {
2746 // make sure we limit search to correct number of lines
2747 if (c == '\n' && --linecnt < 1)
2749 if (dir >= 0 && p >= end - 1)
2751 if (dir < 0 && p <= text)
2753 p += dir; // move to next char
2758 // find matching char of pair () [] {}
2759 static Byte *find_pair(Byte * p, Byte c)
2766 dir = 1; // assume forward
2790 for (q = p + dir; text <= q && q < end; q += dir) {
2791 // look for match, count levels of pairs (( ))
2793 level++; // increase pair levels
2795 level--; // reduce pair level
2797 break; // found matching pair
2800 q = NULL; // indicate no match
2804 #ifdef BB_FEATURE_VI_SETOPTS
2805 // show the matching char of a pair, () [] {}
2806 static void showmatching(Byte * p)
2810 // we found half of a pair
2811 q = find_pair(p, *p); // get loc of matching char
2813 indicate_error('3'); // no matching char
2815 // "q" now points to matching pair
2816 save_dot = dot; // remember where we are
2817 dot = q; // go to new loc
2818 refresh(FALSE); // let the user see it
2819 (void) mysleep(40); // give user some time
2820 dot = save_dot; // go back to old loc
2824 #endif /* BB_FEATURE_VI_SETOPTS */
2826 // open a hole in text[]
2827 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2836 cnt = end - src; // the rest of buffer
2837 if (memmove(dest, src, cnt) != dest) {
2838 psbs("can't create room for new characters");
2840 memset(p, ' ', size); // clear new hole
2841 end = end + size; // adjust the new END
2842 file_modified = TRUE; // has the file been modified
2847 // close a hole in text[]
2848 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2853 // move forwards, from beginning
2857 if (q < p) { // they are backward- swap them
2861 hole_size = q - p + 1;
2863 if (src < text || src > end)
2865 if (dest < text || dest >= end)
2868 goto thd_atend; // just delete the end of the buffer
2869 if (memmove(dest, src, cnt) != dest) {
2870 psbs("can't delete the character");
2873 end = end - hole_size; // adjust the new END
2875 dest = end - 1; // make sure dest in below end-1
2877 dest = end = text; // keep pointers valid
2878 file_modified = TRUE; // has the file been modified
2883 // copy text into register, then delete text.
2884 // if dist <= 0, do not include, or go past, a NewLine
2886 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2890 // make sure start <= stop
2892 // they are backwards, reverse them
2898 // we can not cross NL boundaries
2902 // dont go past a NewLine
2903 for (; p + 1 <= stop; p++) {
2905 stop = p; // "stop" just before NewLine
2911 #ifdef BB_FEATURE_VI_YANKMARK
2912 text_yank(start, stop, YDreg);
2913 #endif /* BB_FEATURE_VI_YANKMARK */
2914 if (yf == YANKDEL) {
2915 p = text_hole_delete(start, stop);
2920 static void show_help(void)
2922 printf("These features are available:\n");
2923 #ifdef BB_FEATURE_VI_SEARCH
2924 printf("\tPattern searches with / and ?\n");
2925 #endif /* BB_FEATURE_VI_SEARCH */
2926 #ifdef BB_FEATURE_VI_DOT_CMD
2927 printf("\tLast command repeat with \'.\'\n");
2928 #endif /* BB_FEATURE_VI_DOT_CMD */
2929 #ifdef BB_FEATURE_VI_YANKMARK
2930 printf("\tLine marking with 'x\n");
2931 printf("\tNamed buffers with \"x\n");
2932 #endif /* BB_FEATURE_VI_YANKMARK */
2933 #ifdef BB_FEATURE_VI_READONLY
2934 printf("\tReadonly with -R command line arg\n");
2935 #endif /* BB_FEATURE_VI_READONLY */
2936 #ifdef BB_FEATURE_VI_SET
2937 printf("\tSome colon mode commands with \':\'\n");
2938 #endif /* BB_FEATURE_VI_SET */
2939 #ifdef BB_FEATURE_VI_SETOPTS
2940 printf("\tSettable options with \":set\"\n");
2941 #endif /* BB_FEATURE_VI_SETOPTS */
2942 #ifdef BB_FEATURE_VI_USE_SIGNALS
2943 printf("\tSignal catching- ^C\n");
2944 printf("\tJob suspend and resume with ^Z\n");
2945 #endif /* BB_FEATURE_VI_USE_SIGNALS */
2946 #ifdef BB_FEATURE_VI_WIN_RESIZE
2947 printf("\tAdapt to window re-sizes\n");
2948 #endif /* BB_FEATURE_VI_WIN_RESIZE */
2951 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2956 strcpy((char *) buf, ""); // init buf
2957 if (strlen((char *) s) <= 0)
2958 s = (Byte *) "(NULL)";
2959 for (; *s > '\0'; s++) {
2962 strcat((char *) buf, SOs);
2966 strcat((char *) buf, "^");
2970 strcat((char *) buf, (char *) b);
2972 strcat((char *) buf, SOn);
2974 strcat((char *) buf, "$");
2979 #ifdef BB_FEATURE_VI_DOT_CMD
2980 static void start_new_cmd_q(Byte c)
2983 if (last_modifying_cmd != 0)
2984 free(last_modifying_cmd);
2985 // get buffer for new cmd
2986 last_modifying_cmd = (Byte *) malloc(BUFSIZ);
2987 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2988 // if there is a current cmd count put it in the buffer first
2990 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2991 // save char c onto queue
2992 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2997 static void end_cmd_q()
2999 #ifdef BB_FEATURE_VI_YANKMARK
3000 YDreg = 26; // go back to default Yank/Delete reg
3001 #endif /* BB_FEATURE_VI_YANKMARK */
3005 #endif /* BB_FEATURE_VI_DOT_CMD */
3007 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
3008 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
3012 i = strlen((char *) s);
3013 p = text_hole_make(p, i);
3014 strncpy((char *) p, (char *) s, i);
3015 for (cnt = 0; *s != '\0'; s++) {
3019 #ifdef BB_FEATURE_VI_YANKMARK
3020 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
3021 #endif /* BB_FEATURE_VI_YANKMARK */
3024 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
3026 #ifdef BB_FEATURE_VI_YANKMARK
3027 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
3032 if (q < p) { // they are backwards- reverse them
3039 if (t != 0) { // if already a yank register
3042 t = (Byte *) malloc(cnt + 1); // get a new register
3043 memset(t, '\0', cnt + 1); // clear new text[]
3044 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
3049 static Byte what_reg(void)
3055 c = 'D'; // default to D-reg
3056 if (0 <= YDreg && YDreg <= 25)
3057 c = 'a' + (Byte) YDreg;
3065 static void check_context(Byte cmd)
3067 // A context is defined to be "modifying text"
3068 // Any modifying command establishes a new context.
3070 if (dot < context_start || dot > context_end) {
3071 if (strchr((char *) modifying_cmds, cmd) != NULL) {
3072 // we are trying to modify text[]- make this the current context
3073 mark[27] = mark[26]; // move cur to prev
3074 mark[26] = dot; // move local to cur
3075 context_start = prev_line(prev_line(dot));
3076 context_end = next_line(next_line(dot));
3077 //loiter= start_loiter= now;
3083 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
3087 // the current context is in mark[26]
3088 // the previous context is in mark[27]
3089 // only swap context if other context is valid
3090 if (text <= mark[27] && mark[27] <= end - 1) {
3092 mark[27] = mark[26];
3094 p = mark[26]; // where we are going- previous context
3095 context_start = prev_line(prev_line(prev_line(p)));
3096 context_end = next_line(next_line(next_line(p)));
3100 #endif /* BB_FEATURE_VI_YANKMARK */
3102 static int isblnk(Byte c) // is the char a blank or tab
3104 return (c == ' ' || c == '\t');
3107 //----- Set terminal attributes --------------------------------
3108 static void rawmode(void)
3110 tcgetattr(0, &term_orig);
3111 term_vi = term_orig;
3112 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
3113 term_vi.c_iflag &= (~IXON & ~ICRNL);
3114 term_vi.c_cc[VMIN] = 1;
3115 term_vi.c_cc[VTIME] = 0;
3116 erase_char = term_vi.c_cc[VERASE];
3117 tcsetattr(0, TCSANOW, &term_vi);
3120 static void cookmode(void)
3122 tcsetattr(0, TCSANOW, &term_orig);
3125 #ifdef BB_FEATURE_VI_WIN_RESIZE
3126 //----- See what the window size currently is --------------------
3127 static void window_size_get(int sig)
3131 i = ioctl(0, TIOCGWINSZ, &winsize);
3134 winsize.ws_row = 24;
3135 winsize.ws_col = 80;
3137 if (winsize.ws_row <= 1) {
3138 winsize.ws_row = 24;
3140 if (winsize.ws_col <= 1) {
3141 winsize.ws_col = 80;
3143 rows = (int) winsize.ws_row;
3144 columns = (int) winsize.ws_col;
3146 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3148 //----- Come here when we get a window resize signal ---------
3149 #ifdef BB_FEATURE_VI_USE_SIGNALS
3150 static void winch_sig(int sig)
3152 signal(SIGWINCH, winch_sig);
3153 #ifdef BB_FEATURE_VI_WIN_RESIZE
3155 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3156 new_screen(rows, columns); // get memory for virtual screen
3157 redraw(TRUE); // re-draw the screen
3160 //----- Come here when we get a continue signal -------------------
3161 static void cont_sig(int sig)
3163 rawmode(); // terminal to "raw"
3164 *status_buffer = '\0'; // clear the status buffer
3165 redraw(TRUE); // re-draw the screen
3167 signal(SIGTSTP, suspend_sig);
3168 signal(SIGCONT, SIG_DFL);
3169 kill(getpid(), SIGCONT);
3172 //----- Come here when we get a Suspend signal -------------------
3173 static void suspend_sig(int sig)
3175 place_cursor(rows, 0); // go to bottom of screen
3176 clear_to_eol(); // Erase to end of line
3177 cookmode(); // terminal to "cooked"
3179 signal(SIGCONT, cont_sig);
3180 signal(SIGTSTP, SIG_DFL);
3181 kill(getpid(), SIGTSTP);
3184 //----- Come here when we get a signal --------------------
3185 static void catch_sig(int sig)
3187 signal(SIGHUP, catch_sig);
3188 signal(SIGINT, catch_sig);
3189 signal(SIGTERM, catch_sig);
3190 longjmp(restart, sig);
3193 static void alarm_sig(int sig)
3195 signal(SIGALRM, catch_sig);
3196 longjmp(restart, sig);
3199 //----- Come here when we get a core dump signal -----------------
3200 static void core_sig(int sig)
3202 signal(SIGQUIT, core_sig);
3203 signal(SIGILL, core_sig);
3204 signal(SIGTRAP, core_sig);
3205 signal(SIGIOT, core_sig);
3206 signal(SIGABRT, core_sig);
3207 signal(SIGFPE, core_sig);
3208 signal(SIGBUS, core_sig);
3209 signal(SIGSEGV, core_sig);
3211 signal(SIGSYS, core_sig);
3214 dot = bound_dot(dot); // make sure "dot" is valid
3216 longjmp(restart, sig);
3218 #endif /* BB_FEATURE_VI_USE_SIGNALS */
3220 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3222 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3226 tv.tv_usec = hund * 10000;
3227 select(1, &rfds, NULL, NULL, &tv);
3228 return (FD_ISSET(0, &rfds));
3231 //----- IO Routines --------------------------------------------
3232 static Byte readit(void) // read (maybe cursor) key from stdin
3235 int i, bufsiz, cnt, cmdindex;
3241 static struct esc_cmds esccmds[] = {
3242 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3243 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3244 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3245 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3246 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3247 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3248 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3249 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3250 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3251 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3252 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3253 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3254 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3255 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3256 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3257 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3258 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3259 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3260 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3261 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3262 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3263 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3264 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3265 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3266 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3267 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3268 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3269 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3270 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3271 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3272 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3275 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3277 (void) alarm(0); // turn alarm OFF while we wait for input
3278 // get input from User- are there already input chars in Q?
3279 bufsiz = strlen((char *) readbuffer);
3282 // the Q is empty, wait for a typed char
3283 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3286 goto ri0; // interrupted sys call
3289 if (errno == EFAULT)
3291 if (errno == EINVAL)
3298 readbuffer[bufsiz] = '\0';
3300 // return char if it is not part of ESC sequence
3301 if (readbuffer[0] != 27)
3304 // This is an ESC char. Is this Esc sequence?
3305 // Could be bare Esc key. See if there are any
3306 // more chars to read after the ESC. This would
3307 // be a Function or Cursor Key sequence.
3311 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
3313 // keep reading while there are input chars and room in buffer
3314 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3315 // read the rest of the ESC string
3316 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3319 readbuffer[bufsiz] = '\0'; // Terminate the string
3322 // Maybe cursor or function key?
3323 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3324 cnt = strlen((char *) esccmds[cmdindex].seq);
3325 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3327 // is a Cursor key- put derived value back into Q
3328 readbuffer[0] = esccmds[cmdindex].val;
3329 // squeeze out the ESC sequence
3330 for (i = 1; i < cnt; i++) {
3331 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3332 readbuffer[BUFSIZ - 1] = '\0';
3339 // remove one char from Q
3340 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3341 readbuffer[BUFSIZ - 1] = '\0';
3342 (void) alarm(3); // we are done waiting for input, turn alarm ON
3346 //----- IO Routines --------------------------------------------
3347 static Byte get_one_char()
3351 #ifdef BB_FEATURE_VI_DOT_CMD
3352 // ! adding2q && ioq == 0 read()
3353 // ! adding2q && ioq != 0 *ioq
3354 // adding2q *last_modifying_cmd= read()
3356 // we are not adding to the q.
3357 // but, we may be reading from a q
3359 // there is no current q, read from STDIN
3360 c = readit(); // get the users input
3362 // there is a queue to get chars from first
3365 // the end of the q, read from STDIN
3367 ioq_start = ioq = 0;
3368 c = readit(); // get the users input
3372 // adding STDIN chars to q
3373 c = readit(); // get the users input
3374 if (last_modifying_cmd != 0) {
3375 // add new char to q
3376 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3379 #else /* BB_FEATURE_VI_DOT_CMD */
3380 c = readit(); // get the users input
3381 #endif /* BB_FEATURE_VI_DOT_CMD */
3382 return (c); // return the char, where ever it came from
3385 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
3386 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3391 static Byte *obufp = NULL;
3393 strcpy((char *) buf, (char *) prompt);
3394 *status_buffer = '\0'; // clear the status buffer
3395 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
3396 clear_to_eol(); // clear the line
3397 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3399 for (i = strlen((char *) buf); i < BUFSIZ;) {
3400 c = get_one_char(); // read user input
3401 if (c == '\n' || c == '\r' || c == 27)
3402 break; // is this end of input
3403 if (c == erase_char) { // user wants to erase prev char
3404 i--; // backup to prev char
3405 buf[i] = '\0'; // erase the char
3406 buf[i + 1] = '\0'; // null terminate buffer
3407 write(1, "
\b \b", 3); // erase char on screen
3408 if (i <= 0) { // user backs up before b-o-l, exit
3412 buf[i] = c; // save char in buffer
3413 buf[i + 1] = '\0'; // make sure buffer is null terminated
3414 write(1, buf + i, 1); // echo the char back to user
3421 obufp = (Byte *) strdup((char *) buf);
3424 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
3426 static int file_size(Byte * fn) // what is the byte size of "fn"
3431 if (fn == 0 || strlen(fn) <= 0)
3434 sr = stat((char *) fn, &st_buf); // see if file exists
3436 cnt = (int) st_buf.st_size;
3441 static int file_insert(Byte * fn, Byte * p, int size)
3446 #ifdef BB_FEATURE_VI_READONLY
3448 #endif /* BB_FEATURE_VI_READONLY */
3449 if (fn == 0 || strlen((char*) fn) <= 0) {
3450 psbs("No filename given");
3454 // OK- this is just a no-op
3459 psbs("Trying to insert a negative number (%d) of characters", size);
3462 if (p < text || p > end) {
3463 psbs("Trying to insert file outside of memory");
3467 // see if we can open the file
3468 fd = open((char *) fn, O_RDWR); // assume read & write
3470 // could not open for writing- maybe file is read only
3471 fd = open((char *) fn, O_RDONLY); // try read-only
3473 psbs("\"%s\" %s", fn, "could not open file");
3476 #ifdef BB_FEATURE_VI_READONLY
3477 // got the file- read-only
3479 #endif /* BB_FEATURE_VI_READONLY */
3481 p = text_hole_make(p, size);
3482 cnt = read(fd, p, size);
3486 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3487 psbs("could not read file \"%s\"", fn);
3488 } else if (cnt < size) {
3489 // There was a partial read, shrink unused space text[]
3490 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3491 psbs("could not read all of file \"%s\"", fn);
3494 file_modified = TRUE;
3499 static int file_write(Byte * fn, Byte * first, Byte * last)
3501 int fd, cnt, charcnt;
3504 psbs("No current filename");
3508 // FIXIT- use the correct umask()
3509 fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
3512 cnt = last - first + 1;
3513 charcnt = write(fd, first, cnt);
3514 if (charcnt == cnt) {
3516 //file_modified= FALSE; // the file has not been modified
3524 //----- Terminal Drawing ---------------------------------------
3525 // The terminal is made up of 'rows' line of 'columns' columns.
3526 // classicly this would be 24 x 80.
3527 // screen coordinates
3533 // 23,0 ... 23,79 status line
3536 //----- Move the cursor to row x col (count from 0, not 1) -------
3537 static void place_cursor(int row, int col)
3550 sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
3551 l = strlen((char *) buf);
3555 //----- Erase from cursor to end of line -----------------------
3556 static void clear_to_eol()
3558 write(1, "\033[0K", 4); // Erase from cursor to end of line
3561 //----- Erase from cursor to end of screen -----------------------
3562 static void clear_to_eos()
3564 write(1, "\033[0J", 4); // Erase from cursor to end of screen
3567 //----- Start standout mode ------------------------------------
3568 static void standout_start() // send "start reverse video" sequence
3570 write(1, "\033[7m", 4); // Start reverse video mode
3573 //----- End standout mode --------------------------------------
3574 static void standout_end() // send "end reverse video" sequence
3576 write(1, "\033[0m", 4); // End reverse video mode
3579 //----- Flash the screen --------------------------------------
3580 static void flash(int h)
3582 standout_start(); // send "start reverse video" sequence
3585 standout_end(); // send "end reverse video" sequence
3591 write(1, "\007", 1); // send out a bell character
3594 static void indicate_error(char c)
3596 #ifdef BB_FEATURE_VI_CRASHME
3598 return; // generate a random command
3599 #endif /* BB_FEATURE_VI_CRASHME */
3600 if (err_method == 0) {
3607 //----- Screen[] Routines --------------------------------------
3608 //----- Erase the Screen[] memory ------------------------------
3609 static void screen_erase()
3613 for (i = 0; i < screensize; i++) {
3618 //----- Draw the status line at bottom of the screen -------------
3619 static void show_status_line(void)
3623 cnt = strlen((char *) status_buffer);
3624 place_cursor(rows - 1, 0); // put cursor on status line
3626 write(1, status_buffer, cnt);
3629 place_cursor(crow, ccol); // put cursor back in correct place
3632 //----- format the status buffer, the bottom line of screen ------
3633 // print status buffer, with STANDOUT mode
3634 static void psbs(char *format, ...)
3638 va_start(args, format);
3639 strcpy((char *) status_buffer, "\033[7m"); // Terminal standout mode on
3640 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3642 strcat((char *) status_buffer, "\033[0m"); // Terminal standout mode off
3648 // print status buffer
3649 static void psb(char *format, ...)
3653 va_start(args, format);
3654 vsprintf((char *) status_buffer, format, args);
3659 static void ni(Byte * s) // display messages
3663 print_literal(buf, s);
3664 psbs("\'%s\' is not implemented", buf);
3667 static void edit_status(void) // show file status on status line
3669 int cur, tot, percent;
3671 cur = count_lines(text, dot);
3672 tot = count_lines(text, end - 1);
3673 // current line percent
3674 // ------------- ~~ ----------
3677 percent = (100 * cur) / tot;
3683 #ifdef BB_FEATURE_VI_READONLY
3685 #endif /* BB_FEATURE_VI_READONLY */
3686 "%s line %d of %d --%d%%--",
3687 (cfn != 0 ? (char *) cfn : "No file"),
3688 #ifdef BB_FEATURE_VI_READONLY
3689 (readonly == TRUE ? " [Read only]" : ""),
3690 #endif /* BB_FEATURE_VI_READONLY */
3691 (file_modified == TRUE ? " [modified]" : ""),
3695 //----- Force refresh of all Lines -----------------------------
3696 static void redraw(int full_screen)
3698 place_cursor(0, 0); // put cursor in correct place
3699 clear_to_eos(); // tel terminal to erase display
3700 screen_erase(); // erase the internal screen buffer
3701 refresh(full_screen); // this will redraw the entire display
3704 //----- Refresh the changed screen lines -----------------------
3705 // Copy the source line from text[] into the buffer and note
3706 // if the current screenline is different from the new buffer.
3707 // If they differ then that line needs redrawing on the terminal.
3709 static void refresh(int full_screen)
3711 static int old_offset;
3712 int li, co, changed;
3713 Byte c, buf[MAX_SCR_COLS];
3714 Byte *tp, *sp; // pointer into text[] and screen[]
3716 #ifdef BB_FEATURE_VI_WIN_RESIZE
3718 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3719 sync_cursor(dot, &crow, &ccol);
3720 tp = screenbegin; // index into text[] of top line
3721 // compare text[] to screen[] and mark screen[] lines that need updating
3722 for (li = 0; li < rows - 1; li++) {
3723 // format current text line into buf with "columns" wide
3724 for (co = 0; co < columns + offset;) {
3725 c = ' '; // assume blank
3726 if (li > 0 && co == 0) {
3727 c = '~'; // not first line, assume Tilde
3729 // are there chars in text[]
3730 // and have we gone past the end
3731 if (text < end && tp < end) {
3736 if (c < ' ' || c > '~') {
3740 for (; (co % tabstop) != (tabstop - 1); co++) {
3745 c |= '@'; // make it visible
3746 c &= 0x7f; // get rid of hi bit
3749 // the co++ is done here so that the column will
3750 // not be overwritten when we blank-out the rest of line
3755 if (co >= columns + offset) {
3756 // skip to the end of the current text[] line
3757 while (tp < end && *tp++ != '\n');
3759 // try to keep the cursor near it's current position
3760 // remember how many chars in this row- where the cursor sits
3761 // blank out the rest of the buffer
3762 while (co < MAX_SCR_COLS - 1) {
3765 buf[co++] = 0; // NULL terminate the buffer
3767 // if necessary, update virtual screen[] and terminal from buf[]
3768 changed = FALSE; // assume no change
3769 sp = &screen[li * columns]; // start of screen line
3770 for (co = 0; co < columns; co++) {
3771 if (sp[co] != buf[co + offset]) {
3772 sp[co] = buf[co + offset];
3773 changed = TRUE; // mark for redraw
3776 // if horz offset has changed, force a redraw
3777 if (offset != old_offset)
3780 // write all marked screen lines out to terminal
3781 if (changed == TRUE) {
3782 place_cursor(li, 0); // put cursor in correct place
3783 clear_to_eol(); // Erase to end of line
3784 if (full_screen == FALSE) {
3785 // don't redraw every column on terminal
3786 // look backwards for last non-blank
3787 for (co = columns + offset; co >= 0; co--) {
3794 // redraw every column on terminal
3797 // write line out to terminal
3798 write(1, buf + offset, co);
3802 place_cursor(crow, ccol);
3803 if (offset != old_offset)
3804 old_offset = offset;