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.6 2001/05/07 17:37:43 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -s -DSTANDALONE -DBB_FEATURE_VI_CRASHME -o vi vi.c # include testing features
35 * $HOME/.exrc and ./.exrc
36 * add magic to search /foo.*bar
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 * More intelligence in refresh()
43 * ":r !cmd" and "!cmd" to filter text through an external command
44 * A true "undo" facility
45 * An "ex" line oriented mode- maybe using "cmdedit"
48 //---- Feature -------------- Bytes to immplement
51 #define 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; // terminal standout start/normal ESC sequence
148 static char *bell; // terminal bell sequence
149 static char *Ceol, *Ceos; // Clear-end-of-line and Clear-end-of-screen ESC sequence
150 static char *CMrc; // Cursor motion arbitrary destination ESC sequence
151 static char *CMup, *CMdown; // Cursor motion up and down ESC sequence
152 static Byte *status_buffer; // mesages to the user
153 static Byte last_input_char; // last char read from user
154 static Byte last_forward_char; // last char searched for with 'f'
155 static Byte *cfn; // previous, current, and next file name
156 static Byte *text, *end, *textend; // pointers to the user data in memory
157 static Byte *screen; // pointer to the virtual screen buffer
158 static int screensize; // and its size
159 static Byte *screenbegin; // index into text[], of top line on the screen
160 static Byte *dot; // where all the action takes place
162 static struct termios term_orig, term_vi; // remember what the cooked mode was
164 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
165 static int last_row; // where the cursor was last moved to
166 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
167 #ifdef BB_FEATURE_VI_USE_SIGNALS
168 static jmp_buf restart; // catch_sig()
169 #endif /* BB_FEATURE_VI_USE_SIGNALS */
170 #ifdef BB_FEATURE_VI_WIN_RESIZE
171 static struct winsize winsize; // remember the window size
172 #endif /* BB_FEATURE_VI_WIN_RESIZE */
173 #ifdef BB_FEATURE_VI_DOT_CMD
174 static int adding2q; // are we currently adding user input to q
175 static Byte *last_modifying_cmd; // last modifying cmd for "."
176 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
177 #endif /* BB_FEATURE_VI_DOT_CMD */
178 #if defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
179 static Byte *modifying_cmds; // cmds that modify text[]
180 #endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
181 #ifdef BB_FEATURE_VI_READONLY
182 static int vi_readonly, readonly;
183 #endif /* BB_FEATURE_VI_READONLY */
184 #ifdef BB_FEATURE_VI_SETOPTS
185 static int autoindent;
186 static int showmatch;
187 static int ignorecase;
188 #endif /* BB_FEATURE_VI_SETOPTS */
189 #ifdef BB_FEATURE_VI_YANKMARK
190 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
191 static int YDreg, Ureg; // default delete register and orig line for "U"
192 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
193 static Byte *context_start, *context_end;
194 #endif /* BB_FEATURE_VI_YANKMARK */
195 #ifdef BB_FEATURE_VI_SEARCH
196 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
197 #endif /* BB_FEATURE_VI_SEARCH */
200 static void edit_file(Byte *); // edit one file
201 static void do_cmd(Byte); // execute a command
202 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
203 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
204 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
205 static Byte *dollar_line(Byte *); // return pointer to just before NL
206 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
207 static Byte *next_line(Byte *); // return pointer to next line B-o-l
208 static Byte *end_screen(void); // get pointer to last char on screen
209 static int count_lines(Byte *, Byte *); // count line from start to stop
210 static Byte *find_line(int); // find begining of line #li
211 static Byte *move_to_col(Byte *, int); // move "p" to column l
212 static int isblnk(Byte); // is the char a blank or tab
213 static void dot_left(void); // move dot left- dont leave line
214 static void dot_right(void); // move dot right- dont leave line
215 static void dot_begin(void); // move dot to B-o-l
216 static void dot_end(void); // move dot to E-o-l
217 static void dot_next(void); // move dot to next line B-o-l
218 static void dot_prev(void); // move dot to prev line B-o-l
219 static void dot_scroll(int, int); // move the screen up or down
220 static void dot_skip_over_ws(void); // move dot pat WS
221 static void dot_delete(void); // delete the char at 'dot'
222 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
223 static Byte *new_screen(int, int); // malloc virtual screen memory
224 static Byte *new_text(int); // malloc memory for text[] buffer
225 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
226 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
227 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
228 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
229 static Byte *skip_thing(Byte *, int, int, int); // skip some object
230 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
231 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
232 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
233 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
234 static void show_help(void); // display some help info
235 static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
236 static void rawmode(void); // set "raw" mode on tty
237 static void cookmode(void); // return to "cooked" mode on tty
238 static int mysleep(int); // sleep for 'h' 1/100 seconds
239 static Byte readit(void); // read (maybe cursor) key from stdin
240 static Byte get_one_char(void); // read 1 char from stdin
241 static int file_size(Byte *); // what is the byte size of "fn"
242 static int file_insert(Byte *, Byte *, int);
243 static int file_write(Byte *, Byte *, Byte *);
244 static void place_cursor(int, int, int);
245 static void screen_erase();
246 static void clear_to_eol(void);
247 static void clear_to_eos(void);
248 static void standout_start(void); // send "start reverse video" sequence
249 static void standout_end(void); // send "end reverse video" sequence
250 static void flash(int); // flash the terminal screen
251 static void beep(void); // beep the terminal
252 static void indicate_error(char); // use flash or beep to indicate error
253 static void show_status_line(void); // put a message on the bottom line
254 static void psb(char *, ...); // Print Status Buf
255 static void psbs(char *, ...); // Print Status Buf in standout mode
256 static void ni(Byte *); // display messages
257 static void edit_status(void); // show file status on status line
258 static void redraw(int); // force a full screen refresh
259 static void format_line(Byte*, Byte*, int);
260 static void refresh(int); // update the terminal from screen[]
262 #ifdef BB_FEATURE_VI_SEARCH
263 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
264 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
265 #endif /* BB_FEATURE_VI_SEARCH */
266 #ifdef BB_FEATURE_VI_COLON
267 static void Hit_Return(void);
268 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
269 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
270 static void colon(Byte *); // execute the "colon" mode cmds
271 #endif /* BB_FEATURE_VI_COLON */
272 static Byte *get_input_line(Byte *); // get input line- use "status line"
273 #ifdef BB_FEATURE_VI_USE_SIGNALS
274 static void winch_sig(int); // catch window size changes
275 static void suspend_sig(int); // catch ctrl-Z
276 static void alarm_sig(int); // catch alarm time-outs
277 static void catch_sig(int); // catch ctrl-C
278 static void core_sig(int); // catch a core dump signal
279 #endif /* BB_FEATURE_VI_USE_SIGNALS */
280 #ifdef BB_FEATURE_VI_DOT_CMD
281 static void start_new_cmd_q(Byte); // new queue for command
282 static void end_cmd_q(); // stop saving input chars
283 #else /* BB_FEATURE_VI_DOT_CMD */
285 #endif /* BB_FEATURE_VI_DOT_CMD */
286 #ifdef BB_FEATURE_VI_WIN_RESIZE
287 static void window_size_get(int); // find out what size the window is
288 #endif /* BB_FEATURE_VI_WIN_RESIZE */
289 #ifdef BB_FEATURE_VI_SETOPTS
290 static void showmatching(Byte *); // show the matching pair () [] {}
291 #endif /* BB_FEATURE_VI_SETOPTS */
292 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
293 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
294 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
295 #ifdef BB_FEATURE_VI_YANKMARK
296 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
297 static Byte what_reg(void); // what is letter of current YDreg
298 static void check_context(Byte); // remember context for '' command
299 static Byte *swap_context(Byte *); // goto new context for '' command
300 #endif /* BB_FEATURE_VI_YANKMARK */
301 #ifdef BB_FEATURE_VI_CRASHME
302 static void crash_dummy();
303 static void crash_test();
304 static int crashme = 0;
305 #endif /* BB_FEATURE_VI_CRASHME */
308 extern int vi_main(int argc, char **argv)
312 #ifdef BB_FEATURE_VI_YANKMARK
314 #endif /* BB_FEATURE_VI_YANKMARK */
316 CMrc= "\033[%d;%dH"; // Terminal Crusor motion ESC sequence
317 CMup= "\033[A"; // move cursor up one line, same col
318 CMdown="\n"; // move cursor down one line, same col
319 Ceol= "\033[0K"; // Clear from cursor to end of line
320 Ceos= "\033[0J"; // Clear from cursor to end of screen
321 SOs = "\033[7m"; // Terminal standout mode on
322 SOn = "\033[0m"; // Terminal standout mode off
323 bell= "\007"; // Terminal bell sequence
324 #ifdef BB_FEATURE_VI_CRASHME
325 (void) srand((long) getpid());
326 #endif /* BB_FEATURE_VI_CRASHME */
327 status_buffer = (Byte *) malloc(200); // hold messages to user
328 #ifdef BB_FEATURE_VI_READONLY
329 vi_readonly = readonly = FALSE;
330 if (strncmp(argv[0], "view", 4) == 0) {
334 #endif /* BB_FEATURE_VI_READONLY */
335 #ifdef BB_FEATURE_VI_SETOPTS
339 #endif /* BB_FEATURE_VI_SETOPTS */
340 #ifdef BB_FEATURE_VI_YANKMARK
341 for (i = 0; i < 28; i++) {
343 } // init the yank regs
344 #endif /* BB_FEATURE_VI_YANKMARK */
345 #ifdef BB_FEATURE_VI_DOT_CMD
346 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
347 #endif /* BB_FEATURE_VI_DOT_CMD */
349 // 1- process $HOME/.exrc file
350 // 2- process EXINIT variable from environment
351 // 3- process command line args
352 while ((c = getopt(argc, argv, "hCR")) != -1) {
354 #ifdef BB_FEATURE_VI_CRASHME
358 #endif /* BB_FEATURE_VI_CRASHME */
359 #ifdef BB_FEATURE_VI_READONLY
360 case 'R': // Read-only flag
363 #endif /* BB_FEATURE_VI_READONLY */
364 //case 'r': // recover flag- ignore- we don't use tmp file
365 //case 'x': // encryption flag- ignore
366 //case 'c': // execute command first
367 //case 'h': // help -- just use default
374 // The argv array can be used by the ":next" and ":rewind" commands
376 fn_start = optind; // remember first file name for :next and :rew
379 //----- This is the main file handling loop --------------
380 if (optind >= argc) {
381 editing = 1; // 0= exit, 1= one file, 2= multiple files
384 for (; optind < argc; optind++) {
385 editing = 1; // 0=exit, 1=one file, 2+ =many files
388 cfn = (Byte *) strdup(argv[optind]);
392 //-----------------------------------------------------------
397 static void edit_file(Byte * fn)
402 #ifdef BB_FEATURE_VI_USE_SIGNALS
405 #endif /* BB_FEATURE_VI_USE_SIGNALS */
406 #ifdef BB_FEATURE_VI_YANKMARK
407 static Byte *cur_line;
408 #endif /* BB_FEATURE_VI_YANKMARK */
414 #ifdef BB_FEATURE_VI_WIN_RESIZE
416 #endif /* BB_FEATURE_VI_WIN_RESIZE */
417 new_screen(rows, columns); // get memory for virtual screen
419 cnt = file_size(fn); // file size
420 size = 2 * cnt; // 200% of file size
421 new_text(size); // get a text[] buffer
422 screenbegin = dot = end = text;
424 ch= file_insert(fn, text, cnt);
427 (void) char_insert(text, '\n'); // start empty buf with dummy line
429 file_modified = FALSE;
430 #ifdef BB_FEATURE_VI_YANKMARK
431 YDreg = 26; // default Yank/Delete reg
432 Ureg = 27; // hold orig line for "U" cmd
433 for (cnt = 0; cnt < 28; cnt++) {
436 mark[26] = mark[27] = text; // init "previous context"
437 #endif /* BB_FEATURE_VI_YANKMARK */
439 err_method = 1; // flash
440 last_forward_char = last_input_char = '\0';
445 #ifdef BB_FEATURE_VI_USE_SIGNALS
446 signal(SIGHUP, catch_sig);
447 signal(SIGINT, catch_sig);
448 signal(SIGALRM, alarm_sig);
449 signal(SIGTERM, catch_sig);
450 signal(SIGQUIT, core_sig);
451 signal(SIGILL, core_sig);
452 signal(SIGTRAP, core_sig);
453 signal(SIGIOT, core_sig);
454 signal(SIGABRT, core_sig);
455 signal(SIGFPE, core_sig);
456 signal(SIGBUS, core_sig);
457 signal(SIGSEGV, core_sig);
459 signal(SIGSYS, core_sig);
461 signal(SIGWINCH, winch_sig);
462 signal(SIGTSTP, suspend_sig);
463 sig = setjmp(restart);
467 msg = "(window resize)";
477 msg = "(I tried to touch invalid memory)";
481 psbs("-- caught signal %d %s--", sig, msg);
482 screenbegin = dot = text;
484 #endif /* BB_FEATURE_VI_USE_SIGNALS */
487 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
490 offset = 0; // no horizontal offset
492 #ifdef BB_FEATURE_VI_DOT_CMD
493 if (last_modifying_cmd != 0)
494 free(last_modifying_cmd);
495 if (ioq_start != NULL)
497 ioq = ioq_start = last_modifying_cmd = 0;
499 #endif /* BB_FEATURE_VI_DOT_CMD */
500 redraw(FALSE); // dont force every col re-draw
503 //------This is the main Vi cmd handling loop -----------------------
504 while (editing > 0) {
505 #ifdef BB_FEATURE_VI_CRASHME
507 if ((end - text) > 1) {
508 crash_dummy(); // generate a random command
512 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
516 #endif /* BB_FEATURE_VI_CRASHME */
517 last_input_char = c = get_one_char(); // get a cmd from user
518 #ifdef BB_FEATURE_VI_YANKMARK
519 // save a copy of the current line- for the 'U" command
520 if (begin_line(dot) != cur_line) {
521 cur_line = begin_line(dot);
522 text_yank(begin_line(dot), end_line(dot), Ureg);
524 #endif /* BB_FEATURE_VI_YANKMARK */
525 #ifdef BB_FEATURE_VI_DOT_CMD
526 // These are commands that change text[].
527 // Remember the input for the "." command
528 if (!adding2q && ioq_start == 0
529 && strchr((char *) modifying_cmds, c) != NULL) {
532 #endif /* BB_FEATURE_VI_DOT_CMD */
533 do_cmd(c); // execute the user command
535 // poll to see if there is input already waiting. if we are
536 // not able to display output fast enough to keep up, skip
537 // the display update until we catch up with input.
538 if (mysleep(0) == 0) {
539 // no input pending- so update output
543 #ifdef BB_FEATURE_VI_CRASHME
545 crash_test(); // test editor variables
546 #endif /* BB_FEATURE_VI_CRASHME */
548 //-------------------------------------------------------------------
550 place_cursor(rows, 0, FALSE); // go to bottom of screen
551 clear_to_eol(); // Erase to end of line
555 static Byte readbuffer[BUFSIZ];
557 #ifdef BB_FEATURE_VI_CRASHME
558 static int totalcmds = 0;
559 static int Mp = 85; // Movement command Probability
560 static int Np = 90; // Non-movement command Probability
561 static int Dp = 96; // Delete command Probability
562 static int Ip = 97; // Insert command Probability
563 static int Yp = 98; // Yank command Probability
564 static int Pp = 99; // Put command Probability
565 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
566 char chars[20] = "\t012345 abcdABCD-=.$";
567 char *words[20] = { "this", "is", "a", "test",
568 "broadcast", "the", "emergency", "of",
569 "system", "quick", "brown", "fox",
570 "jumped", "over", "lazy", "dogs",
571 "back", "January", "Febuary", "March"
574 "You should have received a copy of the GNU General Public License\n",
575 "char c, cm, *cmd, *cmd1;\n",
576 "generate a command by percentages\n",
577 "Numbers may be typed as a prefix to some commands.\n",
578 "Quit, discarding changes!\n",
579 "Forced write, if permission originally not valid.\n",
580 "In general, any ex or ed command (such as substitute or delete).\n",
581 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
582 "Please get w/ me and I will go over it with you.\n",
583 "The following is a list of scheduled, committed changes.\n",
584 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
585 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
586 "Any question about transactions please contact Sterling Huxley.\n",
587 "I will try to get back to you by Friday, December 31.\n",
588 "This Change will be implemented on Friday.\n",
589 "Let me know if you have problems accessing this;\n",
590 "Sterling Huxley recently added you to the access list.\n",
591 "Would you like to go to lunch?\n",
592 "The last command will be automatically run.\n",
593 "This is too much english for a computer geek.\n",
595 char *multilines[20] = {
596 "You should have received a copy of the GNU General Public License\n",
597 "char c, cm, *cmd, *cmd1;\n",
598 "generate a command by percentages\n",
599 "Numbers may be typed as a prefix to some commands.\n",
600 "Quit, discarding changes!\n",
601 "Forced write, if permission originally not valid.\n",
602 "In general, any ex or ed command (such as substitute or delete).\n",
603 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
604 "Please get w/ me and I will go over it with you.\n",
605 "The following is a list of scheduled, committed changes.\n",
606 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
607 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
608 "Any question about transactions please contact Sterling Huxley.\n",
609 "I will try to get back to you by Friday, December 31.\n",
610 "This Change will be implemented on Friday.\n",
611 "Let me know if you have problems accessing this;\n",
612 "Sterling Huxley recently added you to the access list.\n",
613 "Would you like to go to lunch?\n",
614 "The last command will be automatically run.\n",
615 "This is too much english for a computer geek.\n",
618 // create a random command to execute
619 static void crash_dummy()
621 static int sleeptime; // how long to pause between commands
622 char c, cm, *cmd, *cmd1;
623 int i, cnt, thing, rbi, startrbi, percent;
625 // "dot" movement commands
626 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
628 // is there already a command running?
629 if (strlen((char *) readbuffer) > 0)
633 sleeptime = 0; // how long to pause between commands
634 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
635 // generate a command by percentages
636 percent = (int) lrand48() % 100; // get a number from 0-99
637 if (percent < Mp) { // Movement commands
638 // available commands
641 } else if (percent < Np) { // non-movement commands
642 cmd = "mz<>\'\""; // available commands
644 } else if (percent < Dp) { // Delete commands
645 cmd = "dx"; // available commands
647 } else if (percent < Ip) { // Inset commands
648 cmd = "iIaAsrJ"; // available commands
650 } else if (percent < Yp) { // Yank commands
651 cmd = "yY"; // available commands
653 } else if (percent < Pp) { // Put commands
654 cmd = "pP"; // available commands
657 // We do not know how to handle this command, try again
661 // randomly pick one of the available cmds from "cmd[]"
662 i = (int) lrand48() % strlen(cmd);
664 if (strchr(":\024", cm))
665 goto cd0; // dont allow colon or ctrl-T commands
666 readbuffer[rbi++] = cm; // put cmd into input buffer
668 // now we have the command-
669 // there are 1, 2, and multi char commands
670 // find out which and generate the rest of command as necessary
671 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
672 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
673 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
674 cmd1 = "abcdefghijklmnopqrstuvwxyz";
676 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
678 readbuffer[rbi++] = c; // add movement to input buffer
680 if (strchr("iIaAsc", cm)) { // multi-char commands
683 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
685 readbuffer[rbi++] = c; // add movement to input buffer
687 thing = (int) lrand48() % 4; // what thing to insert
688 cnt = (int) lrand48() % 10; // how many to insert
689 for (i = 0; i < cnt; i++) {
690 if (thing == 0) { // insert chars
691 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
692 } else if (thing == 1) { // insert words
693 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
694 strcat((char *) readbuffer, " ");
695 sleeptime = 0; // how fast to type
696 } else if (thing == 2) { // insert lines
697 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
698 sleeptime = 0; // how fast to type
699 } else { // insert multi-lines
700 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
701 sleeptime = 0; // how fast to type
704 strcat((char *) readbuffer, "\033");
709 (void) mysleep(sleeptime); // sleep 1/100 sec
712 // test to see if there are any errors
713 static void crash_test()
715 static time_t oldtim;
717 char d[2], buf[BUFSIZ], msg[BUFSIZ];
721 strcat((char *) msg, "end<text ");
724 strcat((char *) msg, "end>textend ");
727 strcat((char *) msg, "dot<text ");
730 strcat((char *) msg, "dot>end ");
732 if (screenbegin < text) {
733 strcat((char *) msg, "screenbegin<text ");
735 if (screenbegin > end - 1) {
736 strcat((char *) msg, "screenbegin>end-1 ");
739 if (strlen(msg) > 0) {
741 sprintf(buf, "\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
742 totalcmds, last_input_char, msg, SOs, SOn);
743 write(1, buf, strlen(buf));
744 while (read(0, d, 1) > 0) {
745 if (d[0] == '\n' || d[0] == '\r')
750 tim = (time_t) time((time_t *) 0);
751 if (tim >= (oldtim + 3)) {
752 sprintf((char *) status_buffer,
753 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
754 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
759 #endif /* BB_FEATURE_VI_CRASHME */
761 //---------------------------------------------------------------------
762 //----- the Ascii Chart -----------------------------------------------
764 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
765 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
766 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
767 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
768 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
769 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
770 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
771 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
772 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
773 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
774 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
775 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
776 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
777 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
778 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
779 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
780 //---------------------------------------------------------------------
782 //----- Execute a Vi Command -----------------------------------
783 static void do_cmd(Byte c)
785 Byte c1, *p, *q, *msg, buf[9], *save_dot;
786 int cnt, i, j, dir, yf;
788 c1 = c; // quiet the compiler
789 cnt = yf = dir = 0; // quiet the compiler
790 p = q = save_dot = msg = buf; // quiet the compiler
791 memset(buf, '\0', 9); // clear buf
793 // we are 'R'eplacing the current *dot with new char
795 // don't Replace past E-o-l
796 cmd_mode = 1; // convert to insert
798 if (1 <= c && c <= 127) { // only ASCII chars
800 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
801 dot = char_insert(dot, c); // insert new char
807 // hitting "Insert" twice means "R" replace mode
808 if (c == VI_K_INSERT) goto dc5;
809 // insert the char c at "dot"
810 if (1 <= c && c <= 127) {
811 dot = char_insert(dot, c); // only ASCII chars
825 #ifdef BB_FEATURE_VI_CRASHME
826 case 0x14: // dc4 ctrl-T
827 crashme = (crashme == 0) ? 1 : 0;
829 #endif /* BB_FEATURE_VI_CRASHME */
858 //case 'u': // u- FIXME- there is no undo
860 default: // unrecognised command
869 end_cmd_q(); // stop adding to q
870 case 0x00: // nul- ignore
872 case 2: // ctrl-B scroll up full screen
873 case VI_K_PAGEUP: // Cursor Key Page Up
874 dot_scroll(rows - 2, -1);
876 #ifdef BB_FEATURE_VI_USE_SIGNALS
877 case 0x03: // ctrl-C interrupt
880 case 26: // ctrl-Z suspend
881 suspend_sig(SIGTSTP);
883 #endif /* BB_FEATURE_VI_USE_SIGNALS */
884 case 4: // ctrl-D scroll down half screen
885 dot_scroll((rows - 2) / 2, 1);
887 case 5: // ctrl-E scroll down one line
890 case 6: // ctrl-F scroll down full screen
891 case VI_K_PAGEDOWN: // Cursor Key Page Down
892 dot_scroll(rows - 2, 1);
894 case 7: // ctrl-G show current status
897 case 'h': // h- move left
898 case VI_K_LEFT: // cursor key Left
899 case 8: // ctrl-H- move left (This may be ERASE char)
900 case 127: // DEL- move left (This may be ERASE char)
906 case 10: // Newline ^J
907 case 'j': // j- goto next line, same col
908 case VI_K_DOWN: // cursor key Down
912 dot_next(); // go to next B-o-l
913 dot = move_to_col(dot, ccol + offset); // try stay in same col
915 case 12: // ctrl-L force redraw whole screen
916 case 18: // ctrl-R force redraw
917 place_cursor(0, 0, FALSE); // put cursor in correct place
918 clear_to_eos(); // tel terminal to erase display
920 screen_erase(); // erase the internal screen buffer
921 refresh(TRUE); // this will redraw the entire display
923 case 13: // Carriage Return ^M
924 case '+': // +- goto next line
931 case 21: // ctrl-U scroll up half screen
932 dot_scroll((rows - 2) / 2, -1);
934 case 25: // ctrl-Y scroll up one line
940 cmd_mode = 0; // stop insrting
942 *status_buffer = '\0'; // clear status buffer
944 case ' ': // move right
945 case 'l': // move right
946 case VI_K_RIGHT: // Cursor Key Right
952 #ifdef BB_FEATURE_VI_YANKMARK
953 case '"': // "- name a register to use for Delete/Yank
962 case '\'': // '- goto a specific mark
969 if (text <= q && q < end) {
971 dot_begin(); // go to B-o-l
974 } else if (c1 == '\'') { // goto previous context
975 dot = swap_context(dot); // swap current and previous context
976 dot_begin(); // go to B-o-l
982 case 'm': // m- Mark a line
983 // this is really stupid. If there are any inserts or deletes
984 // between text[0] and dot then this mark will not point to the
985 // correct location! It could be off by many lines!
986 // Well..., at least its quick and dirty.
992 mark[(int) c1] = dot;
997 case 'P': // P- Put register before
998 case 'p': // p- put register after
1001 psbs("Nothing in register %c", what_reg());
1004 // are we putting whole lines or strings
1005 if (strchr((char *) p, '\n') != NULL) {
1007 dot_begin(); // putting lines- Put above
1010 // are we putting after very last line?
1011 if (end_line(dot) == (end - 1)) {
1012 dot = end; // force dot to end of text[]
1014 dot_next(); // next line, then put before
1019 dot_right(); // move to right, can move to NL
1021 dot = string_insert(dot, p); // insert the string
1022 end_cmd_q(); // stop adding to q
1024 case 'U': // U- Undo; replace current line with original version
1025 if (reg[Ureg] != 0) {
1026 p = begin_line(dot);
1028 p = text_hole_delete(p, q); // delete cur line
1029 p = string_insert(p, reg[Ureg]); // insert orig line
1034 #endif /* BB_FEATURE_VI_YANKMARK */
1035 case '$': // $- goto end of line
1036 case VI_K_END: // Cursor Key End
1040 dot = end_line(dot + 1);
1042 case '%': // %- find matching char of pair () [] {}
1043 for (q = dot; q < end && *q != '\n'; q++) {
1044 if (strchr("()[]{}", *q) != NULL) {
1045 // we found half of a pair
1046 p = find_pair(q, *q);
1058 case 'f': // f- forward to a user specified char
1059 last_forward_char = get_one_char(); // get the search char
1061 // dont seperate these two commands. 'f' depends on ';'
1063 //**** fall thru to ... 'i'
1064 case ';': // ;- look at rest of line for last forward char
1068 if (last_forward_char == 0) break;
1070 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1073 if (*q == last_forward_char)
1076 case '-': // -- goto prev line
1083 #ifdef BB_FEATURE_VI_DOT_CMD
1084 case '.': // .- repeat the last modifying command
1085 // Stuff the last_modifying_cmd back into stdin
1086 // and let it be re-executed.
1087 if (last_modifying_cmd != 0) {
1088 ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
1091 #endif /* BB_FEATURE_VI_DOT_CMD */
1092 #ifdef BB_FEATURE_VI_SEARCH
1093 case '?': // /- search for a pattern
1094 case '/': // /- search for a pattern
1097 q = get_input_line(buf); // get input line- use "status line"
1098 if (strlen((char *) q) == 1)
1099 goto dc3; // if no pat re-use old pat
1100 if (strlen((char *) q) > 1) { // new pat- save it and find
1101 // there is a new pat
1102 if (last_search_pattern != 0) {
1103 free(last_search_pattern);
1105 last_search_pattern = (Byte *) strdup((char *) q);
1106 goto dc3; // now find the pattern
1108 // user changed mind and erased the "/"- do nothing
1110 case 'N': // N- backward search for last pattern
1114 dir = BACK; // assume BACKWARD search
1116 if (last_search_pattern[0] == '?') {
1120 goto dc4; // now search for pattern
1122 case 'n': // n- repeat search for last pattern
1123 // search rest of text[] starting at next char
1124 // if search fails return orignal "p" not the "p+1" address
1129 if (last_search_pattern == 0) {
1130 msg = (Byte *) "No previous regular expression";
1133 if (last_search_pattern[0] == '/') {
1134 dir = FORWARD; // assume FORWARD search
1137 if (last_search_pattern[0] == '?') {
1142 q = char_search(p, last_search_pattern + 1, dir, FULL);
1144 dot = q; // good search, update "dot"
1148 // no pattern found between "dot" and "end"- continue at top
1153 q = char_search(p, last_search_pattern + 1, dir, FULL);
1154 if (q != NULL) { // found something
1155 dot = q; // found new pattern- goto it
1156 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1158 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1161 msg = (Byte *) "Pattern not found";
1166 case '{': // {- move backward paragraph
1167 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1168 if (q != NULL) { // found blank line
1169 dot = next_line(q); // move to next blank line
1172 case '}': // }- move forward paragraph
1173 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1174 if (q != NULL) { // found blank line
1175 dot = next_line(q); // move to next blank line
1178 #endif /* BB_FEATURE_VI_SEARCH */
1179 case '0': // 0- goto begining of line
1189 if (c == '0' && cmdcnt < 1) {
1190 dot_begin(); // this was a standalone zero
1192 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1195 case ':': // :- the colon mode commands
1196 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1197 #ifdef BB_FEATURE_VI_COLON
1198 colon(p); // execute the command
1199 #else /* BB_FEATURE_VI_COLON */
1201 p++; // move past the ':'
1202 cnt = strlen((char *) p);
1205 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
1206 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
1207 if (file_modified == TRUE && p[1] != '!') {
1208 psbs("No write since last change (:quit! overrides)");
1212 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
1213 strncasecmp((char *) p, "wq", cnt) == 0) {
1214 cnt = file_write(cfn, text, end - 1);
1215 file_modified = FALSE;
1216 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1220 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
1221 edit_status(); // show current file status
1222 } else if (sscanf((char *) p, "%d", &j) > 0) {
1223 dot = find_line(j); // go to line # j
1225 } else { // unrecognised cmd
1228 #endif /* BB_FEATURE_VI_COLON */
1230 case '<': // <- Left shift something
1231 case '>': // >- Right shift something
1232 cnt = count_lines(text, dot); // remember what line we are on
1233 c1 = get_one_char(); // get the type of thing to delete
1234 find_range(&p, &q, c1);
1235 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1238 i = count_lines(p, q); // # of lines we are shifting
1239 for ( ; i > 0; i--, p = next_line(p)) {
1241 // shift left- remove tab or 8 spaces
1243 // shrink buffer 1 char
1244 (void) text_hole_delete(p, p);
1245 } else if (*p == ' ') {
1246 // we should be calculating columns, not just SPACE
1247 for (j = 0; *p == ' ' && j < tabstop; j++) {
1248 (void) text_hole_delete(p, p);
1251 } else if (c == '>') {
1252 // shift right -- add tab or 8 spaces
1253 (void) char_insert(p, '\t');
1256 dot = find_line(cnt); // what line were we on
1258 end_cmd_q(); // stop adding to q
1260 case 'A': // A- append at e-o-l
1261 dot_end(); // go to e-o-l
1262 //**** fall thru to ... 'a'
1263 case 'a': // a- append after current char
1268 case 'B': // B- back a blank-delimited Word
1269 case 'E': // E- end of a blank-delimited word
1270 case 'W': // W- forward a blank-delimited word
1277 if (c == 'W' || isspace(dot[dir])) {
1278 dot = skip_thing(dot, 1, dir, S_TO_WS);
1279 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1282 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1284 case 'C': // C- Change to e-o-l
1285 case 'D': // D- delete to e-o-l
1287 dot = dollar_line(dot); // move to before NL
1288 // copy text into a register and delete
1289 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1291 goto dc_i; // start inserting
1292 #ifdef BB_FEATURE_VI_DOT_CMD
1294 end_cmd_q(); // stop adding to q
1295 #endif /* BB_FEATURE_VI_DOT_CMD */
1297 case 'G': // G- goto to a line number (default= E-O-F)
1298 dot = end - 1; // assume E-O-F
1300 dot = find_line(cmdcnt); // what line is #cmdcnt
1304 case 'H': // H- goto top line on screen
1306 if (cmdcnt > (rows - 1)) {
1307 cmdcnt = (rows - 1);
1314 case 'I': // I- insert before first non-blank
1317 //**** fall thru to ... 'i'
1318 case 'i': // i- insert before current char
1319 case VI_K_INSERT: // Cursor Key Insert
1321 cmd_mode = 1; // start insrting
1322 psb("-- Insert --");
1324 case 'J': // J- join current and next lines together
1328 dot_end(); // move to NL
1329 if (dot < end - 1) { // make sure not last char in text[]
1330 *dot++ = ' '; // replace NL with space
1331 while (isblnk(*dot)) { // delete leading WS
1335 end_cmd_q(); // stop adding to q
1337 case 'L': // L- goto bottom line on screen
1339 if (cmdcnt > (rows - 1)) {
1340 cmdcnt = (rows - 1);
1348 case 'M': // M- goto middle line on screen
1350 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
1351 dot = next_line(dot);
1353 case 'O': // O- open a empty line above
1355 p = begin_line(dot);
1356 if (p[-1] == '\n') {
1358 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1360 dot = char_insert(dot, '\n');
1363 dot = char_insert(dot, '\n'); // i\n ESC
1368 case 'R': // R- continuous Replace char
1371 psb("-- Replace --");
1373 case 'X': // X- delete char before dot
1374 case 'x': // x- delete the current char
1375 case 's': // s- substitute the current char
1382 if (dot[dir] != '\n') {
1384 dot--; // delete prev char
1385 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1388 goto dc_i; // start insrting
1389 end_cmd_q(); // stop adding to q
1391 case 'Z': // Z- if modified, {write}; exit
1392 // ZZ means to save file (if necessary), then exit
1393 c1 = get_one_char();
1398 if (file_modified == TRUE
1399 #ifdef BB_FEATURE_VI_READONLY
1400 && vi_readonly == FALSE
1401 && readonly == FALSE
1402 #endif /* BB_FEATURE_VI_READONLY */
1404 cnt = file_write(cfn, text, end - 1);
1405 if (cnt == (end - 1 - text + 1)) {
1412 case '^': // ^- move to first non-blank on line
1416 case 'b': // b- back a word
1417 case 'e': // e- end of word
1424 if ((dot + dir) < text || (dot + dir) > end - 1)
1427 if (isspace(*dot)) {
1428 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1430 if (isalnum(*dot) || *dot == '_') {
1431 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1432 } else if (ispunct(*dot)) {
1433 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1436 case 'c': // c- change something
1437 case 'd': // d- delete something
1438 #ifdef BB_FEATURE_VI_YANKMARK
1439 case 'y': // y- yank something
1440 case 'Y': // Y- Yank a line
1441 #endif /* BB_FEATURE_VI_YANKMARK */
1442 yf = YANKDEL; // assume either "c" or "d"
1443 #ifdef BB_FEATURE_VI_YANKMARK
1444 if (c == 'y' || c == 'Y')
1446 #endif /* BB_FEATURE_VI_YANKMARK */
1449 c1 = get_one_char(); // get the type of thing to delete
1450 find_range(&p, &q, c1);
1451 if (c1 == 27) { // ESC- user changed mind and wants out
1452 c = c1 = 27; // Escape- do nothing
1453 } else if (strchr("wW", c1)) {
1455 // don't include trailing WS as part of word
1456 while (isblnk(*q)) {
1457 if (q <= text || q[-1] == '\n')
1462 dot = yank_delete(p, q, 0, yf); // delete word
1463 } else if (strchr("^0bBeEft$", c1)) {
1464 // single line copy text into a register and delete
1465 dot = yank_delete(p, q, 0, yf); // delete word
1466 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
1467 // multiple line copy text into a register and delete
1468 dot = yank_delete(p, q, 1, yf); // delete lines
1470 dot = char_insert(dot, '\n');
1471 // on the last line of file don't move to prev line
1472 if (dot != (end-1)) {
1475 } else if (c == 'd') {
1480 // could not recognize object
1481 c = c1 = 27; // error-
1485 // if CHANGING, not deleting, start inserting after the delete
1487 strcpy((char *) buf, "Change");
1488 goto dc_i; // start inserting
1491 strcpy((char *) buf, "Delete");
1493 #ifdef BB_FEATURE_VI_YANKMARK
1494 if (c == 'y' || c == 'Y') {
1495 strcpy((char *) buf, "Yank");
1498 q = p + strlen((char *) p);
1499 for (cnt = 0; p <= q; p++) {
1503 psb("%s %d lines (%d chars) using [%c]",
1504 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1505 #endif /* BB_FEATURE_VI_YANKMARK */
1506 end_cmd_q(); // stop adding to q
1509 case 'k': // k- goto prev line, same col
1510 case VI_K_UP: // cursor key Up
1515 dot = move_to_col(dot, ccol + offset); // try stay in same col
1517 case 'r': // r- replace the current char with user input
1518 c1 = get_one_char(); // get the replacement char
1521 file_modified = TRUE; // has the file been modified
1523 end_cmd_q(); // stop adding to q
1525 case 't': // t- move to char prior to next x
1526 last_forward_char = get_one_char();
1528 if (*dot == last_forward_char)
1530 last_forward_char= 0;
1532 case 'w': // w- forward a word
1536 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1537 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1538 } else if (ispunct(*dot)) { // we are on PUNCT
1539 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1542 dot++; // move over word
1543 if (isspace(*dot)) {
1544 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1548 c1 = get_one_char(); // get the replacement char
1551 cnt = (rows - 2) / 2; // put dot at center
1553 cnt = rows - 2; // put dot at bottom
1554 screenbegin = begin_line(dot); // start dot at top
1555 dot_scroll(cnt, -1);
1557 case '|': // |- move to column "cmdcnt"
1558 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1560 case '~': // ~- flip the case of letters a-z -> A-Z
1564 if (islower(*dot)) {
1565 *dot = toupper(*dot);
1566 file_modified = TRUE; // has the file been modified
1567 } else if (isupper(*dot)) {
1568 *dot = tolower(*dot);
1569 file_modified = TRUE; // has the file been modified
1572 end_cmd_q(); // stop adding to q
1574 //----- The Cursor and Function Keys -----------------------------
1575 case VI_K_HOME: // Cursor Key Home
1578 // The Fn keys could point to do_macro which could translate them
1579 case VI_K_FUN1: // Function Key F1
1580 case VI_K_FUN2: // Function Key F2
1581 case VI_K_FUN3: // Function Key F3
1582 case VI_K_FUN4: // Function Key F4
1583 case VI_K_FUN5: // Function Key F5
1584 case VI_K_FUN6: // Function Key F6
1585 case VI_K_FUN7: // Function Key F7
1586 case VI_K_FUN8: // Function Key F8
1587 case VI_K_FUN9: // Function Key F9
1588 case VI_K_FUN10: // Function Key F10
1589 case VI_K_FUN11: // Function Key F11
1590 case VI_K_FUN12: // Function Key F12
1595 // if text[] just became empty, add back an empty line
1597 (void) char_insert(text, '\n'); // start empty buf with dummy line
1600 // it is OK for dot to exactly equal to end, otherwise check dot validity
1602 dot = bound_dot(dot); // make sure "dot" is valid
1604 #ifdef BB_FEATURE_VI_YANKMARK
1605 check_context(c); // update the current context
1606 #endif /* BB_FEATURE_VI_YANKMARK */
1609 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1610 cnt = dot - begin_line(dot);
1611 // Try to stay off of the Newline
1612 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1616 //----- The Colon commands -------------------------------------
1617 #ifdef BB_FEATURE_VI_COLON
1618 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
1623 #ifdef BB_FEATURE_VI_YANKMARK
1625 #endif /* BB_FEATURE_VI_YANKMARK */
1626 #ifdef BB_FEATURE_VI_SEARCH
1627 Byte *pat, buf[BUFSIZ];
1628 #endif /* BB_FEATURE_VI_SEARCH */
1630 *addr = -1; // assume no addr
1631 if (*p == '.') { // the current line
1633 q = begin_line(dot);
1634 *addr = count_lines(text, q);
1635 #ifdef BB_FEATURE_VI_YANKMARK
1636 } else if (*p == '\'') { // is this a mark addr
1640 if (c >= 'a' && c <= 'z') {
1644 if (q != NULL) { // is mark valid
1645 *addr = count_lines(text, q); // count lines
1648 #endif /* BB_FEATURE_VI_YANKMARK */
1649 #ifdef BB_FEATURE_VI_SEARCH
1650 } else if (*p == '/') { // a search pattern
1652 for (p++; *p; p++) {
1658 pat = (Byte *) strdup((char *) buf); // save copy of pattern
1661 q = char_search(dot, pat, FORWARD, FULL);
1663 *addr = count_lines(text, q);
1666 #endif /* BB_FEATURE_VI_SEARCH */
1667 } else if (*p == '$') { // the last line in file
1669 q = begin_line(end - 1);
1670 *addr = count_lines(text, q);
1671 } else if (isdigit(*p)) { // specific line number
1672 sscanf((char *) p, "%d%n", addr, &st);
1674 } else { // I don't reconise this
1675 // unrecognised address- assume -1
1681 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
1683 //----- get the address' i.e., 1,3 'a,'b -----
1684 // get FIRST addr, if present
1686 p++; // skip over leading spaces
1687 if (*p == '%') { // alias for 1,$
1690 *e = count_lines(text, end-1);
1693 p = get_one_address(p, b);
1696 if (*p == ',') { // is there a address seperator
1700 // get SECOND addr, if present
1701 p = get_one_address(p, e);
1705 p++; // skip over trailing spaces
1709 static void colon(Byte * buf)
1711 Byte c, *orig_buf, *buf1, *q, *r;
1712 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
1713 int i, l, li, ch, st, b, e;
1714 int useforce, forced;
1717 // :3154 // if (-e line 3154) goto it else stay put
1718 // :4,33w! foo // write a portion of buffer to file "foo"
1719 // :w // write all of buffer to current file
1721 // :q! // quit- dont care about modified file
1722 // :'a,'z!sort -u // filter block through sort
1723 // :'f // goto mark "f"
1724 // :'fl // list literal the mark "f" line
1725 // :.r bar // read file "bar" into buffer before dot
1726 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1727 // :/xyz/ // goto the "xyz" line
1728 // :s/find/replace/ // substitute pattern "find" with "replace"
1729 // :!<cmd> // run <cmd> then return
1731 if (strlen((char *) buf) <= 0)
1734 buf++; // move past the ':'
1736 forced = useforce = FALSE;
1737 li = st = ch = i = 0;
1739 q = text; // assume 1,$ for the range
1741 li = count_lines(text, end - 1);
1742 fn = cfn; // default to current file
1743 memset(cmd, '\0', BUFSIZ); // clear cmd[]
1744 memset(args, '\0', BUFSIZ); // clear args[]
1746 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1747 buf = get_address(buf, &b, &e);
1749 // remember orig command line
1752 // get the COMMAND into cmd[]
1754 while (*buf != '\0') {
1759 // get any ARGuments
1760 while (isblnk(*buf))
1762 strcpy((char *) args, (char *) buf);
1763 if (last_char_is((char *)cmd, '!')) {
1765 cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
1768 // if there is only one addr, then the addr
1769 // is the line number of the single line the
1770 // user wants. So, reset the end
1771 // pointer to point at end of the "b" line
1772 q = find_line(b); // what line is #b
1777 // we were given two addrs. change the
1778 // end pointer to the addr given by user.
1779 r = find_line(e); // what line is #e
1783 // ------------ now look for the command ------------
1784 i = strlen((char *) cmd);
1785 if (i == 0) { // :123CR goto line #123
1787 dot = find_line(b); // what line is #b
1790 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
1791 // :!ls run the <cmd>
1792 (void) alarm(0); // wait for input- no alarms
1793 place_cursor(rows - 1, 0, FALSE); // go to Status line
1794 clear_to_eol(); // clear the line
1796 system(orig_buf+1); // run the cmd
1798 Hit_Return(); // let user see results
1799 (void) alarm(3); // done waiting for input
1800 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1801 if (b < 0) { // no addr given- use defaults
1802 b = e = count_lines(text, dot);
1805 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1806 if (b < 0) { // no addr given- use defaults
1807 q = begin_line(dot); // assume .,. for the range
1810 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1812 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1815 // don't edit, if the current file has been modified
1816 if (file_modified == TRUE && useforce != TRUE) {
1817 psbs("No write since last change (:edit! overrides)");
1820 if (strlen(args) > 0) {
1821 // the user supplied a file name
1823 } else if (cfn != 0 && strlen(cfn) > 0) {
1824 // no user supplied name- use the current filename
1828 // no user file name, no current name- punt
1829 psbs("No current filename");
1833 // see if file exists- if not, its just a new file request
1834 if ((sr=stat((char*)fn, &st_buf)) < 0) {
1835 // This is just a request for a new file creation.
1836 // The file_insert below will fail but we get
1837 // an empty buffer with a file name. Then the "write"
1838 // command can do the create.
1840 if ((st_buf.st_mode & (S_IFREG)) == 0) {
1841 // This is not a regular file
1842 psbs("\"%s\" is not a regular file", fn);
1845 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
1846 // dont have any read permissions
1847 psbs("\"%s\" is not readable", fn);
1852 // There is a read-able regular file
1853 // make this the current file
1854 q = (Byte *) strdup((char *) fn); // save the cfn
1856 free(cfn); // free the old name
1857 cfn = q; // remember new cfn
1860 // delete all the contents of text[]
1861 new_text(2 * file_size(fn));
1862 screenbegin = dot = end = text;
1865 ch = file_insert(fn, text, file_size(fn));
1868 // start empty buf with dummy line
1869 (void) char_insert(text, '\n');
1872 file_modified = FALSE;
1873 #ifdef BB_FEATURE_VI_YANKMARK
1874 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
1875 free(reg[Ureg]); // free orig line reg- for 'U'
1878 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
1879 free(reg[YDreg]); // free default yank/delete register
1882 for (li = 0; li < 28; li++) {
1885 #endif /* BB_FEATURE_VI_YANKMARK */
1886 // how many lines in text[]?
1887 li = count_lines(text, end - 1);
1889 #ifdef BB_FEATURE_VI_READONLY
1891 #endif /* BB_FEATURE_VI_READONLY */
1893 (sr < 0 ? " [New file]" : ""),
1894 #ifdef BB_FEATURE_VI_READONLY
1895 ((vi_readonly == TRUE || readonly == TRUE) ? " [Read only]" : ""),
1896 #endif /* BB_FEATURE_VI_READONLY */
1898 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1899 if (b != -1 || e != -1) {
1900 ni((Byte *) "No address allowed on this command");
1903 if (strlen((char *) args) > 0) {
1904 // user wants a new filename
1907 cfn = (Byte *) strdup((char *) args);
1909 // user wants file status info
1912 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1913 // print out values of all features
1914 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1915 clear_to_eol(); // clear the line
1920 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1921 if (b < 0) { // no addr given- use defaults
1922 q = begin_line(dot); // assume .,. for the range
1925 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1926 clear_to_eol(); // clear the line
1927 write(1, "\r\n", 2);
1928 for (; q <= r; q++) {
1934 } else if (*q < ' ') {
1942 #ifdef BB_FEATURE_VI_SET
1944 #endif /* BB_FEATURE_VI_SET */
1946 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1947 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1948 if (useforce == TRUE) {
1949 // force end of argv list
1956 // don't exit if the file been modified
1957 if (file_modified == TRUE) {
1958 psbs("No write since last change (:%s! overrides)",
1959 (*cmd == 'q' ? "quit" : "next"));
1962 // are there other file to edit
1963 if (*cmd == 'q' && optind < save_argc - 1) {
1964 psbs("%d more file to edit", (save_argc - optind - 1));
1967 if (*cmd == 'n' && optind >= save_argc - 1) {
1968 psbs("No more files to edit");
1972 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1974 if (strlen((char *) fn) <= 0) {
1975 psbs("No filename given");
1978 if (b < 0) { // no addr given- use defaults
1979 q = begin_line(dot); // assume "dot"
1981 // read after current line- unless user said ":0r foo"
1984 l= readonly; // remember current files' status
1985 ch = file_insert(fn, q, file_size(fn));
1988 goto vc1; // nothing was inserted
1989 // how many lines in text[]?
1990 li = count_lines(q, q + ch - 1);
1992 #ifdef BB_FEATURE_VI_READONLY
1994 #endif /* BB_FEATURE_VI_READONLY */
1996 #ifdef BB_FEATURE_VI_READONLY
1997 ((vi_readonly == TRUE || readonly == TRUE) ? " [Read only]" : ""),
1998 #endif /* BB_FEATURE_VI_READONLY */
2001 // if the insert is before "dot" then we need to update
2004 file_modified = TRUE;
2006 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
2007 if (file_modified == TRUE && useforce != TRUE) {
2008 psbs("No write since last change (:rewind! overrides)");
2010 // reset the filenames to edit
2011 optind = fn_start - 1;
2014 #ifdef BB_FEATURE_VI_SET
2015 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
2016 i = 0; // offset into args
2017 if (strlen((char *) args) == 0) {
2018 // print out values of all options
2019 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2020 clear_to_eol(); // clear the line
2021 printf("----------------------------------------\r\n");
2022 #ifdef BB_FEATURE_VI_SETOPTS
2025 printf("autoindent ");
2031 printf("ignorecase ");
2034 printf("showmatch ");
2035 printf("tabstop=%d ", tabstop);
2036 #endif /* BB_FEATURE_VI_SETOPTS */
2040 if (strncasecmp((char *) args, "no", 2) == 0)
2041 i = 2; // ":set noautoindent"
2042 #ifdef BB_FEATURE_VI_SETOPTS
2043 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
2044 strncasecmp((char *) args + i, "ai", 2) == 0) {
2045 autoindent = (i == 2) ? 0 : 1;
2047 if (strncasecmp((char *) args + i, "flash", 5) == 0 ||
2048 strncasecmp((char *) args + i, "fl", 2) == 0) {
2049 err_method = (i == 2) ? 0 : 1;
2051 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
2052 strncasecmp((char *) args + i, "ic", 2) == 0) {
2053 ignorecase = (i == 2) ? 0 : 1;
2055 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
2056 strncasecmp((char *) args + i, "sm", 2) == 0) {
2057 showmatch = (i == 2) ? 0 : 1;
2059 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
2060 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
2061 if (ch > 0 && ch < columns - 1)
2064 #endif /* BB_FEATURE_VI_SETOPTS */
2065 #endif /* BB_FEATURE_VI_SET */
2066 #ifdef BB_FEATURE_VI_SEARCH
2067 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
2071 // F points to the "find" pattern
2072 // R points to the "replace" pattern
2073 // replace the cmd line delimiters "/" with NULLs
2074 gflag = 0; // global replace flag
2075 c = orig_buf[1]; // what is the delimiter
2076 F = orig_buf + 2; // start of "find"
2077 R = (Byte *) strchr((char *) F, c); // middle delimiter
2078 *R++ = '\0'; // terminate "find"
2079 buf1 = (Byte *) strchr((char *) R, c);
2080 *buf1++ = '\0'; // terminate "replace"
2081 if (*buf1 == 'g') { // :s/foo/bar/g
2083 gflag++; // turn on gflag
2086 if (b < 0) { // maybe :s/foo/bar/
2087 q = begin_line(dot); // start with cur line
2088 b = count_lines(text, q); // cur line number
2091 e = b; // maybe :.s/foo/bar/
2092 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2093 ls = q; // orig line start
2095 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
2097 // we found the "find" pattern- delete it
2098 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
2099 // inset the "replace" patern
2100 (void) string_insert(buf1, R); // insert the string
2101 // check for "global" :s/foo/bar/g
2103 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
2104 q = buf1 + strlen((char *) R);
2105 goto vc4; // don't let q move past cur line
2111 #endif /* BB_FEATURE_VI_SEARCH */
2112 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
2113 psb("%s", vi_Version);
2114 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
2115 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
2116 // is there a file name to write to?
2117 if (strlen((char *) args) > 0) {
2120 #ifdef BB_FEATURE_VI_READONLY
2121 if ((vi_readonly == TRUE || readonly == TRUE) && useforce == FALSE) {
2122 psbs("\"%s\" File is read only", fn);
2125 #endif /* BB_FEATURE_VI_READONLY */
2126 // how many lines in text[]?
2127 li = count_lines(q, r);
2129 // see if file exists- if not, its just a new file request
2130 if (useforce == TRUE) {
2131 // if "fn" is not write-able, chmod u+w
2132 // sprintf(syscmd, "chmod u+w %s", fn);
2136 l = file_write(fn, q, r);
2137 if (useforce == TRUE && forced == TRUE) {
2139 // sprintf(syscmd, "chmod u-w %s", fn);
2143 psb("\"%s\" %dL, %dC", fn, li, l);
2144 if (q == text && r == end - 1 && l == ch)
2145 file_modified = FALSE;
2146 if (cmd[1] == 'q' && l == ch) {
2149 #ifdef BB_FEATURE_VI_READONLY
2151 #endif /* BB_FEATURE_VI_READONLY */
2152 #ifdef BB_FEATURE_VI_YANKMARK
2153 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2154 if (b < 0) { // no addr given- use defaults
2155 q = begin_line(dot); // assume .,. for the range
2158 text_yank(q, r, YDreg);
2159 li = count_lines(q, r);
2160 psb("Yank %d lines (%d chars) into [%c]",
2161 li, strlen((char *) reg[YDreg]), what_reg());
2162 #endif /* BB_FEATURE_VI_YANKMARK */
2168 dot = bound_dot(dot); // make sure "dot" is valid
2172 static void Hit_Return(void)
2176 standout_start(); // start reverse video
2177 write(1, "[Hit return to continue]", 24);
2178 standout_end(); // end reverse video
2179 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2181 redraw(TRUE); // force redraw all
2183 #endif /* BB_FEATURE_VI_COLON */
2185 //----- Synchronize the cursor to Dot --------------------------
2186 static void sync_cursor(Byte * d, int *row, int *col)
2188 Byte *beg_cur, *end_cur; // begin and end of "d" line
2189 Byte *beg_scr, *end_scr; // begin and end of screen
2193 beg_cur = begin_line(d); // first char of cur line
2194 end_cur = end_line(d); // last char of cur line
2196 beg_scr = end_scr = screenbegin; // first char of screen
2197 end_scr = end_screen(); // last char of screen
2199 if (beg_cur < screenbegin) {
2200 // "d" is before top line on screen
2201 // how many lines do we have to move
2202 cnt = count_lines(beg_cur, screenbegin);
2204 screenbegin = beg_cur;
2205 if (cnt > (rows - 1) / 2) {
2206 // we moved too many lines. put "dot" in middle of screen
2207 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2208 screenbegin = prev_line(screenbegin);
2211 } else if (beg_cur > end_scr) {
2212 // "d" is after bottom line on screen
2213 // how many lines do we have to move
2214 cnt = count_lines(end_scr, beg_cur);
2215 if (cnt > (rows - 1) / 2)
2216 goto sc1; // too many lines
2217 for (ro = 0; ro < cnt - 1; ro++) {
2218 // move screen begin the same amount
2219 screenbegin = next_line(screenbegin);
2220 // now, move the end of screen
2221 end_scr = next_line(end_scr);
2222 end_scr = end_line(end_scr);
2225 // "d" is on screen- find out which row
2227 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2233 // find out what col "d" is on
2235 do { // drive "co" to correct column
2236 if (*tp == '\n' || *tp == '\0')
2240 co += ((tabstop - 1) - (co % tabstop));
2241 } else if (*tp < ' ') {
2242 co++; // display as ^X, use 2 columns
2244 } while (tp++ < d && ++co);
2246 // "co" is the column where "dot" is.
2247 // The screen has "columns" columns.
2248 // The currently displayed columns are 0+offset -- columns+ofset
2249 // |-------------------------------------------------------------|
2251 // offset | |------- columns ----------------|
2253 // If "co" is already in this range then we do not have to adjust offset
2254 // but, we do have to subtract the "offset" bias from "co".
2255 // If "co" is outside this range then we have to change "offset".
2256 // If the first char of a line is a tab the cursor will try to stay
2257 // in column 7, but we have to set offset to 0.
2259 if (co < 0 + offset) {
2262 if (co >= columns + offset) {
2263 offset = co - columns + 1;
2265 // if the first char of the line is a tab, and "dot" is sitting on it
2266 // force offset to 0.
2267 if (d == beg_cur && *d == '\t') {
2276 //----- Text Movement Routines ---------------------------------
2277 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2279 while (p > text && p[-1] != '\n')
2280 p--; // go to cur line B-o-l
2284 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2286 while (p < end - 1 && *p != '\n')
2287 p++; // go to cur line E-o-l
2291 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2293 while (p < end - 1 && *p != '\n')
2294 p++; // go to cur line E-o-l
2295 // Try to stay off of the Newline
2296 if (*p == '\n' && (p - begin_line(p)) > 0)
2301 static Byte *prev_line(Byte * p) // return pointer first char prev line
2303 p = begin_line(p); // goto begining of cur line
2304 if (p[-1] == '\n' && p > text)
2305 p--; // step to prev line
2306 p = begin_line(p); // goto begining of prev line
2310 static Byte *next_line(Byte * p) // return pointer first char next line
2313 if (*p == '\n' && p < end - 1)
2314 p++; // step to next line
2318 //----- Text Information Routines ------------------------------
2319 static Byte *end_screen(void)
2324 // find new bottom line
2326 for (cnt = 0; cnt < rows - 2; cnt++)
2332 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2337 if (stop < start) { // start and stop are backwards- reverse them
2343 stop = end_line(stop); // get to end of this line
2344 for (q = start; q <= stop && q <= end - 1; q++) {
2351 static Byte *find_line(int li) // find begining of line #li
2355 for (q = text; li > 1; li--) {
2361 //----- Dot Movement Routines ----------------------------------
2362 static void dot_left(void)
2364 if (dot > text && dot[-1] != '\n')
2368 static void dot_right(void)
2370 if (dot < end - 1 && *dot != '\n')
2374 static void dot_begin(void)
2376 dot = begin_line(dot); // return pointer to first char cur line
2379 static void dot_end(void)
2381 dot = end_line(dot); // return pointer to last char cur line
2384 static Byte *move_to_col(Byte * p, int l)
2391 if (*p == '\n' || *p == '\0')
2395 co += ((tabstop - 1) - (co % tabstop));
2396 } else if (*p < ' ') {
2397 co++; // display as ^X, use 2 columns
2399 } while (++co <= l && p++ < end);
2403 static void dot_next(void)
2405 dot = next_line(dot);
2408 static void dot_prev(void)
2410 dot = prev_line(dot);
2413 static void dot_scroll(int cnt, int dir)
2417 for (; cnt > 0; cnt--) {
2420 // ctrl-Y scroll up one line
2421 screenbegin = prev_line(screenbegin);
2424 // ctrl-E scroll down one line
2425 screenbegin = next_line(screenbegin);
2428 // make sure "dot" stays on the screen so we dont scroll off
2429 if (dot < screenbegin)
2431 q = end_screen(); // find new bottom line
2433 dot = begin_line(q); // is dot is below bottom line?
2437 static void dot_skip_over_ws(void)
2440 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2444 static void dot_delete(void) // delete the char at 'dot'
2446 (void) text_hole_delete(dot, dot);
2449 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2451 if (p >= end && end > text) {
2453 indicate_error('1');
2457 indicate_error('2');
2462 //----- Helper Utility Routines --------------------------------
2464 //----------------------------------------------------------------
2465 //----- Char Routines --------------------------------------------
2466 /* Chars that are part of a word-
2467 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2468 * Chars that are Not part of a word (stoppers)
2469 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2470 * Chars that are WhiteSpace
2471 * TAB NEWLINE VT FF RETURN SPACE
2472 * DO NOT COUNT NEWLINE AS WHITESPACE
2475 static Byte *new_screen(int ro, int co)
2481 screensize = ro * co + 8;
2482 screen = (Byte *) malloc(screensize);
2483 // initialize the new screen. assume this will be a empty file.
2485 // non-existant text[] lines start with a tilde (~).
2486 for (li = 1; li < ro - 1; li++) {
2487 screen[(li * co) + 0] = '~';
2492 static Byte *new_text(int size)
2495 size = 10240; // have a minimum size for new files
2500 text = (Byte *) malloc(size + 8);
2501 memset(text, '\0', size); // clear new text[]
2502 //text += 4; // leave some room for "oops"
2503 textend = text + size - 1;
2504 //textend -= 4; // leave some root for "oops"
2508 #ifdef BB_FEATURE_VI_SEARCH
2509 static int mycmp(Byte * s1, Byte * s2, int len)
2513 i = strncmp((char *) s1, (char *) s2, len);
2514 #ifdef BB_FEATURE_VI_SETOPTS
2516 i = strncasecmp((char *) s1, (char *) s2, len);
2518 #endif /* BB_FEATURE_VI_SETOPTS */
2522 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2524 #ifndef REGEX_SEARCH
2528 len = strlen((char *) pat);
2529 if (dir == FORWARD) {
2530 stop = end - 1; // assume range is p - end-1
2531 if (range == LIMITED)
2532 stop = next_line(p); // range is to next line
2533 for (start = p; start < stop; start++) {
2534 if (mycmp(start, pat, len) == 0) {
2538 } else if (dir == BACK) {
2539 stop = text; // assume range is text - p
2540 if (range == LIMITED)
2541 stop = prev_line(p); // range is to prev line
2542 for (start = p - len; start >= stop; start--) {
2543 if (mycmp(start, pat, len) == 0) {
2548 // pattern not found
2550 #else /*REGEX_SEARCH */
2552 struct re_pattern_buffer preg;
2556 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2562 // assume a LIMITED forward search
2570 // count the number of chars to search over, forward or backward
2574 // RANGE could be negative if we are searching backwards
2577 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2579 // The pattern was not compiled
2580 psbs("bad search pattern: \"%s\": %s", pat, q);
2581 i = 0; // return p if pattern not compiled
2591 // search for the compiled pattern, preg, in p[]
2592 // range < 0- search backward
2593 // range > 0- search forward
2595 // re_search() < 0 not found or error
2596 // re_search() > 0 index of found pattern
2597 // struct pattern char int int int struct reg
2598 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2599 i = re_search(&preg, q, size, 0, range, 0);
2602 i = 0; // return NULL if pattern not found
2605 if (dir == FORWARD) {
2611 #endif /*REGEX_SEARCH */
2613 #endif /* BB_FEATURE_VI_SEARCH */
2615 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2617 if (c == 22) { // Is this an ctrl-V?
2618 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2619 p--; // backup onto ^
2620 refresh(FALSE); // show the ^
2624 file_modified = TRUE; // has the file been modified
2625 } else if (c == 27) { // Is this an ESC?
2628 end_cmd_q(); // stop adding to q
2629 strcpy((char *) status_buffer, " "); // clear the status buffer
2630 if (p[-1] != '\n') {
2633 } else if (c == erase_char) { // Is this a BS
2635 if (p[-1] != '\n') {
2637 p = text_hole_delete(p, p); // shrink buffer 1 char
2638 #ifdef BB_FEATURE_VI_DOT_CMD
2639 // also rmove char from last_modifying_cmd
2640 if (strlen((char *) last_modifying_cmd) > 0) {
2643 q = last_modifying_cmd;
2644 q[strlen((char *) q) - 1] = '\0'; // erase BS
2645 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2647 #endif /* BB_FEATURE_VI_DOT_CMD */
2650 // insert a char into text[]
2651 Byte *sp; // "save p"
2654 c = '\n'; // translate \r to \n
2655 sp = p; // remember addr of insert
2656 p = stupid_insert(p, c); // insert the char
2657 #ifdef BB_FEATURE_VI_SETOPTS
2658 if (showmatch && strchr(")]}", *sp) != NULL) {
2661 if (autoindent && c == '\n') { // auto indent the new line
2664 q = prev_line(p); // use prev line as templet
2665 for (; isblnk(*q); q++) {
2666 p = stupid_insert(p, *q); // insert the char
2669 #endif /* BB_FEATURE_VI_SETOPTS */
2674 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2676 p = text_hole_make(p, 1);
2679 file_modified = TRUE; // has the file been modified
2685 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2687 Byte *save_dot, *p, *q;
2693 if (strchr("cdy><", c)) {
2694 // these cmds operate on whole lines
2695 p = q = begin_line(p);
2696 for (cnt = 1; cnt < cmdcnt; cnt++) {
2700 } else if (strchr("^%$0bBeEft", c)) {
2701 // These cmds operate on char positions
2702 do_cmd(c); // execute movement cmd
2704 } else if (strchr("wW", c)) {
2705 do_cmd(c); // execute movement cmd
2707 dot--; // move back off of next word
2708 if (dot > text && *dot == '\n')
2709 dot--; // stay off NL
2711 } else if (strchr("H-k{", c)) {
2712 // these operate on multi-lines backwards
2713 q = end_line(dot); // find NL
2714 do_cmd(c); // execute movement cmd
2717 } else if (strchr("L+j}\r\n", c)) {
2718 // these operate on multi-lines forwards
2719 p = begin_line(dot);
2720 do_cmd(c); // execute movement cmd
2721 dot_end(); // find NL
2724 c = 27; // error- return an ESC char
2737 static int st_test(Byte * p, int type, int dir, Byte * tested)
2747 if (type == S_BEFORE_WS) {
2749 test = ((!isspace(c)) || c == '\n');
2751 if (type == S_TO_WS) {
2753 test = ((!isspace(c)) || c == '\n');
2755 if (type == S_OVER_WS) {
2757 test = ((isspace(c)));
2759 if (type == S_END_PUNCT) {
2761 test = ((ispunct(c)));
2763 if (type == S_END_ALNUM) {
2765 test = ((isalnum(c)) || c == '_');
2771 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2775 while (st_test(p, type, dir, &c)) {
2776 // make sure we limit search to correct number of lines
2777 if (c == '\n' && --linecnt < 1)
2779 if (dir >= 0 && p >= end - 1)
2781 if (dir < 0 && p <= text)
2783 p += dir; // move to next char
2788 // find matching char of pair () [] {}
2789 static Byte *find_pair(Byte * p, Byte c)
2796 dir = 1; // assume forward
2820 for (q = p + dir; text <= q && q < end; q += dir) {
2821 // look for match, count levels of pairs (( ))
2823 level++; // increase pair levels
2825 level--; // reduce pair level
2827 break; // found matching pair
2830 q = NULL; // indicate no match
2834 #ifdef BB_FEATURE_VI_SETOPTS
2835 // show the matching char of a pair, () [] {}
2836 static void showmatching(Byte * p)
2840 // we found half of a pair
2841 q = find_pair(p, *p); // get loc of matching char
2843 indicate_error('3'); // no matching char
2845 // "q" now points to matching pair
2846 save_dot = dot; // remember where we are
2847 dot = q; // go to new loc
2848 refresh(FALSE); // let the user see it
2849 (void) mysleep(40); // give user some time
2850 dot = save_dot; // go back to old loc
2854 #endif /* BB_FEATURE_VI_SETOPTS */
2856 // open a hole in text[]
2857 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2866 cnt = end - src; // the rest of buffer
2867 if (memmove(dest, src, cnt) != dest) {
2868 psbs("can't create room for new characters");
2870 memset(p, ' ', size); // clear new hole
2871 end = end + size; // adjust the new END
2872 file_modified = TRUE; // has the file been modified
2877 // close a hole in text[]
2878 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2883 // move forwards, from beginning
2887 if (q < p) { // they are backward- swap them
2891 hole_size = q - p + 1;
2893 if (src < text || src > end)
2895 if (dest < text || dest >= end)
2898 goto thd_atend; // just delete the end of the buffer
2899 if (memmove(dest, src, cnt) != dest) {
2900 psbs("can't delete the character");
2903 end = end - hole_size; // adjust the new END
2905 dest = end - 1; // make sure dest in below end-1
2907 dest = end = text; // keep pointers valid
2908 file_modified = TRUE; // has the file been modified
2913 // copy text into register, then delete text.
2914 // if dist <= 0, do not include, or go past, a NewLine
2916 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2920 // make sure start <= stop
2922 // they are backwards, reverse them
2928 // we can not cross NL boundaries
2932 // dont go past a NewLine
2933 for (; p + 1 <= stop; p++) {
2935 stop = p; // "stop" just before NewLine
2941 #ifdef BB_FEATURE_VI_YANKMARK
2942 text_yank(start, stop, YDreg);
2943 #endif /* BB_FEATURE_VI_YANKMARK */
2944 if (yf == YANKDEL) {
2945 p = text_hole_delete(start, stop);
2950 static void show_help(void)
2952 printf("These features are available:\n");
2953 #ifdef BB_FEATURE_VI_SEARCH
2954 printf("\tPattern searches with / and ?\n");
2955 #endif /* BB_FEATURE_VI_SEARCH */
2956 #ifdef BB_FEATURE_VI_DOT_CMD
2957 printf("\tLast command repeat with \'.\'\n");
2958 #endif /* BB_FEATURE_VI_DOT_CMD */
2959 #ifdef BB_FEATURE_VI_YANKMARK
2960 printf("\tLine marking with 'x\n");
2961 printf("\tNamed buffers with \"x\n");
2962 #endif /* BB_FEATURE_VI_YANKMARK */
2963 #ifdef BB_FEATURE_VI_READONLY
2964 printf("\tReadonly if vi is called as \"view\"\n");
2965 printf("\tReadonly with -R command line arg\n");
2966 #endif /* BB_FEATURE_VI_READONLY */
2967 #ifdef BB_FEATURE_VI_SET
2968 printf("\tSome colon mode commands with \':\'\n");
2969 #endif /* BB_FEATURE_VI_SET */
2970 #ifdef BB_FEATURE_VI_SETOPTS
2971 printf("\tSettable options with \":set\"\n");
2972 #endif /* BB_FEATURE_VI_SETOPTS */
2973 #ifdef BB_FEATURE_VI_USE_SIGNALS
2974 printf("\tSignal catching- ^C\n");
2975 printf("\tJob suspend and resume with ^Z\n");
2976 #endif /* BB_FEATURE_VI_USE_SIGNALS */
2977 #ifdef BB_FEATURE_VI_WIN_RESIZE
2978 printf("\tAdapt to window re-sizes\n");
2979 #endif /* BB_FEATURE_VI_WIN_RESIZE */
2982 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2987 strcpy((char *) buf, ""); // init buf
2988 if (strlen((char *) s) <= 0)
2989 s = (Byte *) "(NULL)";
2990 for (; *s > '\0'; s++) {
2993 strcat((char *) buf, SOs);
2997 strcat((char *) buf, "^");
3001 strcat((char *) buf, (char *) b);
3003 strcat((char *) buf, SOn);
3005 strcat((char *) buf, "$");
3010 #ifdef BB_FEATURE_VI_DOT_CMD
3011 static void start_new_cmd_q(Byte c)
3014 if (last_modifying_cmd != 0)
3015 free(last_modifying_cmd);
3016 // get buffer for new cmd
3017 last_modifying_cmd = (Byte *) malloc(BUFSIZ);
3018 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
3019 // if there is a current cmd count put it in the buffer first
3021 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
3022 // save char c onto queue
3023 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3028 static void end_cmd_q()
3030 #ifdef BB_FEATURE_VI_YANKMARK
3031 YDreg = 26; // go back to default Yank/Delete reg
3032 #endif /* BB_FEATURE_VI_YANKMARK */
3036 #endif /* BB_FEATURE_VI_DOT_CMD */
3038 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
3039 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
3043 i = strlen((char *) s);
3044 p = text_hole_make(p, i);
3045 strncpy((char *) p, (char *) s, i);
3046 for (cnt = 0; *s != '\0'; s++) {
3050 #ifdef BB_FEATURE_VI_YANKMARK
3051 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
3052 #endif /* BB_FEATURE_VI_YANKMARK */
3055 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
3057 #ifdef BB_FEATURE_VI_YANKMARK
3058 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
3063 if (q < p) { // they are backwards- reverse them
3070 if (t != 0) { // if already a yank register
3073 t = (Byte *) malloc(cnt + 1); // get a new register
3074 memset(t, '\0', cnt + 1); // clear new text[]
3075 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
3080 static Byte what_reg(void)
3086 c = 'D'; // default to D-reg
3087 if (0 <= YDreg && YDreg <= 25)
3088 c = 'a' + (Byte) YDreg;
3096 static void check_context(Byte cmd)
3098 // A context is defined to be "modifying text"
3099 // Any modifying command establishes a new context.
3101 if (dot < context_start || dot > context_end) {
3102 if (strchr((char *) modifying_cmds, cmd) != NULL) {
3103 // we are trying to modify text[]- make this the current context
3104 mark[27] = mark[26]; // move cur to prev
3105 mark[26] = dot; // move local to cur
3106 context_start = prev_line(prev_line(dot));
3107 context_end = next_line(next_line(dot));
3108 //loiter= start_loiter= now;
3114 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
3118 // the current context is in mark[26]
3119 // the previous context is in mark[27]
3120 // only swap context if other context is valid
3121 if (text <= mark[27] && mark[27] <= end - 1) {
3123 mark[27] = mark[26];
3125 p = mark[26]; // where we are going- previous context
3126 context_start = prev_line(prev_line(prev_line(p)));
3127 context_end = next_line(next_line(next_line(p)));
3131 #endif /* BB_FEATURE_VI_YANKMARK */
3133 static int isblnk(Byte c) // is the char a blank or tab
3135 return (c == ' ' || c == '\t');
3138 //----- Set terminal attributes --------------------------------
3139 static void rawmode(void)
3141 tcgetattr(0, &term_orig);
3142 term_vi = term_orig;
3143 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
3144 term_vi.c_iflag &= (~IXON & ~ICRNL);
3145 term_vi.c_oflag &= (~ONLCR);
3146 term_vi.c_cc[VMIN] = 1;
3147 term_vi.c_cc[VTIME] = 0;
3148 erase_char = term_vi.c_cc[VERASE];
3149 tcsetattr(0, TCSANOW, &term_vi);
3152 static void cookmode(void)
3154 tcsetattr(0, TCSANOW, &term_orig);
3157 #ifdef BB_FEATURE_VI_WIN_RESIZE
3158 //----- See what the window size currently is --------------------
3159 static void window_size_get(int sig)
3163 i = ioctl(0, TIOCGWINSZ, &winsize);
3166 winsize.ws_row = 24;
3167 winsize.ws_col = 80;
3169 if (winsize.ws_row <= 1) {
3170 winsize.ws_row = 24;
3172 if (winsize.ws_col <= 1) {
3173 winsize.ws_col = 80;
3175 rows = (int) winsize.ws_row;
3176 columns = (int) winsize.ws_col;
3178 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3180 //----- Come here when we get a window resize signal ---------
3181 #ifdef BB_FEATURE_VI_USE_SIGNALS
3182 static void winch_sig(int sig)
3184 signal(SIGWINCH, winch_sig);
3185 #ifdef BB_FEATURE_VI_WIN_RESIZE
3187 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3188 new_screen(rows, columns); // get memory for virtual screen
3189 redraw(TRUE); // re-draw the screen
3192 //----- Come here when we get a continue signal -------------------
3193 static void cont_sig(int sig)
3195 rawmode(); // terminal to "raw"
3196 *status_buffer = '\0'; // clear the status buffer
3197 redraw(TRUE); // re-draw the screen
3199 signal(SIGTSTP, suspend_sig);
3200 signal(SIGCONT, SIG_DFL);
3201 kill(getpid(), SIGCONT);
3204 //----- Come here when we get a Suspend signal -------------------
3205 static void suspend_sig(int sig)
3207 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
3208 clear_to_eol(); // Erase to end of line
3209 cookmode(); // terminal to "cooked"
3211 signal(SIGCONT, cont_sig);
3212 signal(SIGTSTP, SIG_DFL);
3213 kill(getpid(), SIGTSTP);
3216 //----- Come here when we get a signal ---------------------------
3217 static void catch_sig(int sig)
3219 signal(SIGHUP, catch_sig);
3220 signal(SIGINT, catch_sig);
3221 signal(SIGTERM, catch_sig);
3222 longjmp(restart, sig);
3225 static void alarm_sig(int sig)
3227 signal(SIGALRM, catch_sig);
3228 longjmp(restart, sig);
3231 //----- Come here when we get a core dump signal -----------------
3232 static void core_sig(int sig)
3234 signal(SIGQUIT, core_sig);
3235 signal(SIGILL, core_sig);
3236 signal(SIGTRAP, core_sig);
3237 signal(SIGIOT, core_sig);
3238 signal(SIGABRT, core_sig);
3239 signal(SIGFPE, core_sig);
3240 signal(SIGBUS, core_sig);
3241 signal(SIGSEGV, core_sig);
3243 signal(SIGSYS, core_sig);
3246 dot = bound_dot(dot); // make sure "dot" is valid
3248 longjmp(restart, sig);
3250 #endif /* BB_FEATURE_VI_USE_SIGNALS */
3252 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3254 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3258 tv.tv_usec = hund * 10000;
3259 select(1, &rfds, NULL, NULL, &tv);
3260 return (FD_ISSET(0, &rfds));
3263 //----- IO Routines --------------------------------------------
3264 static Byte readit(void) // read (maybe cursor) key from stdin
3267 int i, bufsiz, cnt, cmdindex;
3273 static struct esc_cmds esccmds[] = {
3274 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3275 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3276 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3277 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3278 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3279 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3280 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3281 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3282 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3283 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3284 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3285 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3286 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3287 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3288 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3289 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3290 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3291 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3292 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3293 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3294 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3295 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3296 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3297 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3298 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3299 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3300 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3301 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3302 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3303 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3304 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3307 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3309 (void) alarm(0); // turn alarm OFF while we wait for input
3310 // get input from User- are there already input chars in Q?
3311 bufsiz = strlen((char *) readbuffer);
3314 // the Q is empty, wait for a typed char
3315 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3318 goto ri0; // interrupted sys call
3321 if (errno == EFAULT)
3323 if (errno == EINVAL)
3330 readbuffer[bufsiz] = '\0';
3332 // return char if it is not part of ESC sequence
3333 if (readbuffer[0] != 27)
3336 // This is an ESC char. Is this Esc sequence?
3337 // Could be bare Esc key. See if there are any
3338 // more chars to read after the ESC. This would
3339 // be a Function or Cursor Key sequence.
3343 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
3345 // keep reading while there are input chars and room in buffer
3346 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3347 // read the rest of the ESC string
3348 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3351 readbuffer[bufsiz] = '\0'; // Terminate the string
3354 // Maybe cursor or function key?
3355 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3356 cnt = strlen((char *) esccmds[cmdindex].seq);
3357 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3359 // is a Cursor key- put derived value back into Q
3360 readbuffer[0] = esccmds[cmdindex].val;
3361 // squeeze out the ESC sequence
3362 for (i = 1; i < cnt; i++) {
3363 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3364 readbuffer[BUFSIZ - 1] = '\0';
3371 // remove one char from Q
3372 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3373 readbuffer[BUFSIZ - 1] = '\0';
3374 (void) alarm(3); // we are done waiting for input, turn alarm ON
3378 //----- IO Routines --------------------------------------------
3379 static Byte get_one_char()
3383 #ifdef BB_FEATURE_VI_DOT_CMD
3384 // ! adding2q && ioq == 0 read()
3385 // ! adding2q && ioq != 0 *ioq
3386 // adding2q *last_modifying_cmd= read()
3388 // we are not adding to the q.
3389 // but, we may be reading from a q
3391 // there is no current q, read from STDIN
3392 c = readit(); // get the users input
3394 // there is a queue to get chars from first
3397 // the end of the q, read from STDIN
3399 ioq_start = ioq = 0;
3400 c = readit(); // get the users input
3404 // adding STDIN chars to q
3405 c = readit(); // get the users input
3406 if (last_modifying_cmd != 0) {
3407 // add new char to q
3408 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3411 #else /* BB_FEATURE_VI_DOT_CMD */
3412 c = readit(); // get the users input
3413 #endif /* BB_FEATURE_VI_DOT_CMD */
3414 return (c); // return the char, where ever it came from
3417 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3422 static Byte *obufp = NULL;
3424 strcpy((char *) buf, (char *) prompt);
3425 *status_buffer = '\0'; // clear the status buffer
3426 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
3427 clear_to_eol(); // clear the line
3428 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3430 for (i = strlen((char *) buf); i < BUFSIZ;) {
3431 c = get_one_char(); // read user input
3432 if (c == '\n' || c == '\r' || c == 27)
3433 break; // is this end of input
3434 if (c == erase_char) { // user wants to erase prev char
3435 i--; // backup to prev char
3436 buf[i] = '\0'; // erase the char
3437 buf[i + 1] = '\0'; // null terminate buffer
3438 write(1, "
\b \b", 3); // erase char on screen
3439 if (i <= 0) { // user backs up before b-o-l, exit
3443 buf[i] = c; // save char in buffer
3444 buf[i + 1] = '\0'; // make sure buffer is null terminated
3445 write(1, buf + i, 1); // echo the char back to user
3452 obufp = (Byte *) strdup((char *) buf);
3456 static int file_size(Byte * fn) // what is the byte size of "fn"
3461 if (fn == 0 || strlen(fn) <= 0)
3464 sr = stat((char *) fn, &st_buf); // see if file exists
3466 cnt = (int) st_buf.st_size;
3471 static int file_insert(Byte * fn, Byte * p, int size)
3476 #ifdef BB_FEATURE_VI_READONLY
3478 #endif /* BB_FEATURE_VI_READONLY */
3479 if (fn == 0 || strlen((char*) fn) <= 0) {
3480 psbs("No filename given");
3484 // OK- this is just a no-op
3489 psbs("Trying to insert a negative number (%d) of characters", size);
3492 if (p < text || p > end) {
3493 psbs("Trying to insert file outside of memory");
3497 // see if we can open the file
3498 #ifdef BB_FEATURE_VI_READONLY
3499 if (vi_readonly == TRUE) goto fi1; // do not try write-mode
3501 fd = open((char *) fn, O_RDWR); // assume read & write
3503 // could not open for writing- maybe file is read only
3504 #ifdef BB_FEATURE_VI_READONLY
3507 fd = open((char *) fn, O_RDONLY); // try read-only
3509 psbs("\"%s\" %s", fn, "could not open file");
3512 #ifdef BB_FEATURE_VI_READONLY
3513 // got the file- read-only
3515 #endif /* BB_FEATURE_VI_READONLY */
3517 p = text_hole_make(p, size);
3518 cnt = read(fd, p, size);
3522 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3523 psbs("could not read file \"%s\"", fn);
3524 } else if (cnt < size) {
3525 // There was a partial read, shrink unused space text[]
3526 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3527 psbs("could not read all of file \"%s\"", fn);
3530 file_modified = TRUE;
3535 static int file_write(Byte * fn, Byte * first, Byte * last)
3537 int fd, cnt, charcnt;
3540 psbs("No current filename");
3544 // FIXIT- use the correct umask()
3545 fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
3548 cnt = last - first + 1;
3549 charcnt = write(fd, first, cnt);
3550 if (charcnt == cnt) {
3552 //file_modified= FALSE; // the file has not been modified
3560 //----- Terminal Drawing ---------------------------------------
3561 // The terminal is made up of 'rows' line of 'columns' columns.
3562 // classicly this would be 24 x 80.
3563 // screen coordinates
3569 // 23,0 ... 23,79 status line
3572 //----- Move the cursor to row x col (count from 0, not 1) -------
3573 static void place_cursor(int row, int col, int opti)
3578 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
3581 // char cm3[BUFSIZ];
3583 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3585 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
3587 if (row < 0) row = 0;
3588 if (row >= rows) row = rows - 1;
3589 if (col < 0) col = 0;
3590 if (col >= columns) col = columns - 1;
3592 //----- 1. Try the standard terminal ESC sequence
3593 sprintf((char *) cm1, CMrc, row + 1, col + 1);
3595 if (opti == FALSE) goto pc0;
3597 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
3598 //----- find the minimum # of chars to move cursor -------------
3599 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
3600 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
3602 // move to the correct row
3603 while (row < Rrow) {
3604 // the cursor has to move up
3608 while (row > Rrow) {
3609 // the cursor has to move down
3610 strcat(cm2, CMdown);
3614 // now move to the correct column
3615 strcat(cm2, "\r"); // start at col 0
3616 // just send out orignal source char to get to correct place
3617 screenp = &screen[row * columns]; // start of screen line
3618 strncat(cm2, screenp, col);
3620 //----- 3. Try some other way of moving cursor
3621 //---------------------------------------------
3623 // pick the shortest cursor motion to send out
3625 if (strlen(cm2) < strlen(cm)) {
3627 } /* else if (strlen(cm3) < strlen(cm)) {
3630 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3633 if (l) write(1, cm, l); // move the cursor
3636 //----- Erase from cursor to end of line -----------------------
3637 static void clear_to_eol()
3639 write(1, Ceol, strlen(Ceol)); // Erase from cursor to end of line
3642 //----- Erase from cursor to end of screen -----------------------
3643 static void clear_to_eos()
3645 write(1, Ceos, strlen(Ceos)); // Erase from cursor to end of screen
3648 //----- Start standout mode ------------------------------------
3649 static void standout_start() // send "start reverse video" sequence
3651 write(1, SOs, strlen(SOs)); // Start reverse video mode
3654 //----- End standout mode --------------------------------------
3655 static void standout_end() // send "end reverse video" sequence
3657 write(1, SOn, strlen(SOn)); // End reverse video mode
3660 //----- Flash the screen --------------------------------------
3661 static void flash(int h)
3663 standout_start(); // send "start reverse video" sequence
3666 standout_end(); // send "end reverse video" sequence
3672 write(1, bell, strlen(bell)); // send out a bell character
3675 static void indicate_error(char c)
3677 #ifdef BB_FEATURE_VI_CRASHME
3679 return; // generate a random command
3680 #endif /* BB_FEATURE_VI_CRASHME */
3681 if (err_method == 0) {
3688 //----- Screen[] Routines --------------------------------------
3689 //----- Erase the Screen[] memory ------------------------------
3690 static void screen_erase()
3692 memset(screen, ' ', screensize); // clear new screen
3695 //----- Draw the status line at bottom of the screen -------------
3696 static void show_status_line(void)
3698 static int last_cksum;
3701 cnt = strlen((char *) status_buffer);
3702 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
3703 // don't write the status line unless it changes
3704 if (cnt > 0 && last_cksum != cksum) {
3705 last_cksum= cksum; // remember if we have seen this line
3706 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
3707 write(1, status_buffer, cnt);
3709 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
3713 //----- format the status buffer, the bottom line of screen ------
3714 // print status buffer, with STANDOUT mode
3715 static void psbs(char *format, ...)
3719 va_start(args, format);
3720 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
3721 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3723 strcat((char *) status_buffer, SOn); // Terminal standout mode off
3729 // print status buffer
3730 static void psb(char *format, ...)
3734 va_start(args, format);
3735 vsprintf((char *) status_buffer, format, args);
3740 static void ni(Byte * s) // display messages
3744 print_literal(buf, s);
3745 psbs("\'%s\' is not implemented", buf);
3748 static void edit_status(void) // show file status on status line
3750 int cur, tot, percent;
3752 cur = count_lines(text, dot);
3753 tot = count_lines(text, end - 1);
3754 // current line percent
3755 // ------------- ~~ ----------
3758 percent = (100 * cur) / tot;
3764 #ifdef BB_FEATURE_VI_READONLY
3766 #endif /* BB_FEATURE_VI_READONLY */
3767 "%s line %d of %d --%d%%--",
3768 (cfn != 0 ? (char *) cfn : "No file"),
3769 #ifdef BB_FEATURE_VI_READONLY
3770 ((vi_readonly == TRUE || readonly == TRUE) ? " [Read only]" : ""),
3771 #endif /* BB_FEATURE_VI_READONLY */
3772 (file_modified == TRUE ? " [modified]" : ""),
3776 //----- Force refresh of all Lines -----------------------------
3777 static void redraw(int full_screen)
3779 place_cursor(0, 0, FALSE); // put cursor in correct place
3780 clear_to_eos(); // tel terminal to erase display
3781 screen_erase(); // erase the internal screen buffer
3782 refresh(full_screen); // this will redraw the entire display
3785 //----- Format a text[] line into a buffer ---------------------
3786 static void format_line(Byte *dest, Byte *src, int li)
3791 for (co= 0; co < MAX_SCR_COLS; co++) {
3792 c= ' '; // assume blank
3793 if (li > 0 && co == 0) {
3794 c = '~'; // not first line, assume Tilde
3796 // are there chars in text[] and have we gone past the end
3797 if (text < end && src < end) {
3802 if (c < ' ' || c > '~') {
3806 for (; (co % tabstop) != (tabstop - 1); co++) {
3811 c |= '@'; // make it visible
3812 c &= 0x7f; // get rid of hi bit
3815 // the co++ is done here so that the column will
3816 // not be overwritten when we blank-out the rest of line
3823 //----- Refresh the changed screen lines -----------------------
3824 // Copy the source line from text[] into the buffer and note
3825 // if the current screenline is different from the new buffer.
3826 // If they differ then that line needs redrawing on the terminal.
3828 static void refresh(int full_screen)
3830 static int old_offset;
3832 Byte buf[MAX_SCR_COLS];
3833 Byte *tp, *sp; // pointer into text[] and screen[]
3834 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
3835 int last_li= -2; // last line that changed- for optimizing cursor movement
3836 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3838 #ifdef BB_FEATURE_VI_WIN_RESIZE
3840 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3841 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3842 tp = screenbegin; // index into text[] of top line
3844 // compare text[] to screen[] and mark screen[] lines that need updating
3845 for (li = 0; li < rows - 1; li++) {
3846 int cs, ce; // column start & end
3847 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
3848 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
3849 // format current text line into buf
3850 format_line(buf, tp, li);
3852 // skip to the end of the current text[] line
3853 while (tp < end && *tp++ != '\n') /*no-op*/ ;
3855 // see if there are any changes between vitual screen and buf
3856 changed = FALSE; // assume no change
3859 sp = &screen[li * columns]; // start of screen line
3860 if (full_screen == TRUE) {
3861 // force re-draw of every single column from 0 - columns-1
3864 // compare newly formatted buffer with virtual screen
3865 // look forward for first difference between buf and screen
3866 for ( ; cs <= ce; cs++) {
3867 if (buf[cs + offset] != sp[cs]) {
3868 changed = TRUE; // mark for redraw
3873 // look backward for last difference between buf and screen
3874 for ( ; ce >= cs; ce--) {
3875 if (buf[ce + offset] != sp[ce]) {
3876 changed = TRUE; // mark for redraw
3880 // now, cs is index of first diff, and ce is index of last diff
3882 // if horz offset has changed, force a redraw
3883 if (offset != old_offset) {
3888 // make a sanity check of columns indexes
3890 if (ce > columns-1) ce= columns-1;
3891 if (cs > ce) { cs= 0; ce= columns-1; }
3892 // is there a change between vitual screen and buf
3893 if (changed == TRUE) {
3894 // copy changed part of buffer to virtual screen
3895 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
3897 // move cursor to column of first change
3898 if (offset != old_offset) {
3899 // opti_cur_move is still too stupid
3900 // to handle offsets correctly
3901 place_cursor(li, cs, FALSE);
3903 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
3904 // if this just the next line
3905 // try to optimize cursor movement
3906 // otherwise, use standard ESC sequence
3907 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
3909 #else /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3910 place_cursor(li, cs, FALSE); // use standard ESC sequence
3911 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3914 // write line out to terminal
3915 write(1, sp+cs, ce-cs+1);
3916 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
3918 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3922 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
3923 #ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
3925 #endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
3927 if (offset != old_offset)
3928 old_offset = offset;