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.1 2001/04/04 17:31:15 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -DSTANDALONE -DCRASHME -o vi vi.c # include testing features
36 * add magic to search /foo.*bar
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
44 //---- Feature -------------- Bytes to immplement
46 #define BB_FEATURE_VI_COLON // 4288
47 #define BB_FEATURE_VI_YANKMARK // 1408
48 #define BB_FEATURE_VI_SEARCH // 1088
49 #define BB_FEATURE_VI_USE_SIGNALS // 1056
50 #define BB_FEATURE_VI_DOT_CMD // 576
51 #define BB_FEATURE_VI_READONLY // 128
52 #define BB_FEATURE_VI_SETOPTS // 576
53 #define BB_FEATURE_VI_SET // 224
54 #define BB_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
55 // To test editor using CRASHME:
57 // To stop testing, wait until all to text[] is deleted, or
58 // Ctrl-Z and kill -9 %1
59 // while in the editor Ctrl-T will toggle the crashme function on and off.
60 //#define BB_FEATURE_VI_CRASHME // randomly pick commands to execute
61 #endif /* STANDALONE */
65 #endif /* STANDALONE */
71 #include <sys/ioctl.h>
73 #include <sys/types.h>
85 #include <stdint.h> // INT32_MAX
88 extern int strncasecmp(const char *s1, const char *s2, size_t n);
90 #define INT32_MAX MAXINT
91 #include <libgen.h> //
92 #endif /* __linux__ */
96 #define FALSE ((int)0)
98 #define MAX_SCR_COLS 300
100 // Misc. non-Ascii keys that report an escape sequence
101 #define VI_K_UP 128 // cursor key Up
102 #define VI_K_DOWN 129 // cursor key Down
103 #define VI_K_RIGHT 130 // Cursor Key Right
104 #define VI_K_LEFT 131 // cursor key Left
105 #define VI_K_HOME 132 // Cursor Key Home
106 #define VI_K_END 133 // Cursor Key End
107 #define VI_K_INSERT 134 // Cursor Key Insert
108 #define VI_K_PAGEUP 135 // Cursor Key Page Up
109 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
110 #define VI_K_FUN1 137 // Function Key F1
111 #define VI_K_FUN2 138 // Function Key F2
112 #define VI_K_FUN3 139 // Function Key F3
113 #define VI_K_FUN4 140 // Function Key F4
114 #define VI_K_FUN5 141 // Function Key F5
115 #define VI_K_FUN6 142 // Function Key F6
116 #define VI_K_FUN7 143 // Function Key F7
117 #define VI_K_FUN8 144 // Function Key F8
118 #define VI_K_FUN9 145 // Function Key F9
119 #define VI_K_FUN10 146 // Function Key F10
120 #define VI_K_FUN11 147 // Function Key F11
121 #define VI_K_FUN12 148 // Function Key F12
123 static const int YANKONLY = FALSE;
124 static const int YANKDEL = TRUE;
125 static const int FORWARD = 1; // code depends on "1" for array index
126 static const int BACK = -1; // code depends on "-1" for array index
127 static const int LIMITED = 0; // how much of text[] in char_search
128 static const int FULL = 1; // how much of text[] in char_search
130 static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
131 static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
132 static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
133 static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
134 static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
136 typedef unsigned char Byte;
139 static int editing; // >0 while we are editing a file
140 static int cmd_mode; // 0=command 1=insert
141 static int file_modified; // buffer contents changed
142 static int err_method; // indicate error with beep or flash
143 static int fn_start; // index of first cmd line file name
144 static int save_argc; // how many file names on cmd line
145 static int cmdcnt; // repetition count
146 static fd_set rfds; // use select() for small sleeps
147 static struct timeval tv; // use select() for small sleeps
148 static char erase_char; // the users erase character
149 static int rows, columns; // the terminal screen is this size
150 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
151 static char *SOs, *SOn;
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_USE_SIGNALS
165 static jmp_buf restart; // catch_sig()
166 #endif /* BB_FEATURE_VI_USE_SIGNALS */
167 #ifdef BB_FEATURE_VI_WIN_RESIZE
168 static struct winsize winsize; // remember the window size
169 #endif /* BB_FEATURE_VI_WIN_RESIZE */
170 #ifdef BB_FEATURE_VI_DOT_CMD
171 static int adding2q; // are we currently adding user input to q
172 static Byte *last_modifying_cmd; // last modifying cmd for "."
173 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
174 #endif /* BB_FEATURE_VI_DOT_CMD */
175 #if defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
176 static Byte *modifying_cmds; // cmds that modify text[]
177 #endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
178 #ifdef BB_FEATURE_VI_READONLY
180 #endif /* BB_FEATURE_VI_READONLY */
181 #ifdef BB_FEATURE_VI_SETOPTS
182 static int autoindent;
183 static int showmatch;
184 static int ignorecase;
185 #endif /* BB_FEATURE_VI_SETOPTS */
186 #ifdef BB_FEATURE_VI_YANKMARK
187 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
188 static int YDreg, Ureg; // default delete register and orig line for "U"
189 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
190 static Byte *context_start, *context_end;
191 #endif /* BB_FEATURE_VI_YANKMARK */
192 #ifdef BB_FEATURE_VI_SEARCH
193 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
194 #endif /* BB_FEATURE_VI_SEARCH */
197 static void edit_file(Byte *); // edit one file
198 static void do_cmd(Byte); // execute a command
199 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
200 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
201 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
202 static Byte *dollar_line(Byte *); // return pointer to just before NL
203 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
204 static Byte *next_line(Byte *); // return pointer to next line B-o-l
205 static Byte *end_screen(void); // get pointer to last char on screen
206 static int count_lines(Byte *, Byte *); // count line from start to stop
207 static Byte *find_line(int); // find begining of line #li
208 static Byte *move_to_col(Byte *, int); // move "p" to column l
209 static int isblnk(Byte); // is the char a blank or tab
210 static void dot_left(void); // move dot left- dont leave line
211 static void dot_right(void); // move dot right- dont leave line
212 static void dot_begin(void); // move dot to B-o-l
213 static void dot_end(void); // move dot to E-o-l
214 static void dot_next(void); // move dot to next line B-o-l
215 static void dot_prev(void); // move dot to prev line B-o-l
216 static void dot_scroll(int, int); // move the screen up or down
217 static void dot_skip_over_ws(void); // move dot pat WS
218 static void dot_delete(void); // delete the char at 'dot'
219 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
220 static Byte *new_screen(int, int); // malloc virtual screen memory
221 static Byte *new_text(int); // malloc memory for text[] buffer
222 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
223 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
224 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
225 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
226 static Byte *skip_thing(Byte *, int, int, int); // skip some object
227 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
228 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
229 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
230 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
231 static void show_help(void); // display some help info
232 static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
233 static void rawmode(void); // set "raw" mode on tty
234 static void cookmode(void); // return to "cooked" mode on tty
235 static int mysleep(int); // sleep for 'h' 1/100 seconds
236 static Byte readit(void); // read (maybe cursor) key from stdin
237 static Byte get_one_char(void); // read 1 char from stdin
238 static int file_size(Byte *); // what is the byte size of "fn"
239 static int file_insert(Byte *, Byte *, int);
240 static int file_write(Byte *, Byte *, Byte *);
241 static void place_cursor(int, int);
242 static void screen_erase();
243 static void clear_to_eol(void);
244 static void clear_to_eos(void);
245 static void standout_start(void); // send "start reverse video" sequence
246 static void standout_end(void); // send "end reverse video" sequence
247 static void flash(int); // flash the terminal screen
248 static void beep(void); // beep the terminal
249 static void indicate_error(char); // use flash or beep to indicate error
250 static void show_status_line(void); // put a message on the bottom line
251 static void psb(char *, ...); // Print Status Buf
252 static void psbs(char *, ...); // Print Status Buf in standout mode
253 static void ni(Byte *); // display messages
254 static void edit_status(void); // show file status on status line
255 static void redraw(int); // force a full screen refresh
256 static void refresh(int); // update the terminal from screen[]
258 #ifdef BB_FEATURE_VI_SEARCH
259 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
260 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
261 #endif /* BB_FEATURE_VI_SEARCH */
262 #ifdef BB_FEATURE_VI_COLON
263 static void Hit_Return(void);
264 static Byte *get_address(Byte *, int *); // get colon addr, if present
265 static void colon(Byte *); // execute the "colon" mode cmds
266 #endif /* BB_FEATURE_VI_COLON */
267 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
268 static Byte *get_input_line(Byte *); // get input line- use "status line"
269 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
270 #ifdef BB_FEATURE_VI_USE_SIGNALS
271 static void winch_sig(int); // catch window size changes
272 static void suspend_sig(int); // catch ctrl-Z
273 static void alarm_sig(int); // catch alarm time-outs
274 static void catch_sig(int); // catch ctrl-C
275 static void core_sig(int); // catch a core dump signal
276 #endif /* BB_FEATURE_VI_USE_SIGNALS */
277 #ifdef BB_FEATURE_VI_DOT_CMD
278 static void start_new_cmd_q(Byte); // new queue for command
279 static void end_cmd_q(); // stop saving input chars
280 #else /* BB_FEATURE_VI_DOT_CMD */
282 #endif /* BB_FEATURE_VI_DOT_CMD */
283 #ifdef BB_FEATURE_VI_WIN_RESIZE
284 static void window_size_get(int); // find out what size the window is
285 #endif /* BB_FEATURE_VI_WIN_RESIZE */
286 #ifdef BB_FEATURE_VI_SETOPTS
287 static void showmatching(Byte *); // show the matching pair () [] {}
288 #endif /* BB_FEATURE_VI_SETOPTS */
289 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
290 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
291 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
292 #ifdef BB_FEATURE_VI_YANKMARK
293 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
294 static Byte what_reg(void); // what is letter of current YDreg
295 static void check_context(Byte); // remember context for '' command
296 static Byte *swap_context(Byte *); // goto new context for '' command
297 #endif /* BB_FEATURE_VI_YANKMARK */
298 #ifdef BB_FEATURE_VI_CRASHME
299 static void crash_dummy();
300 static void crash_test();
301 static int crashme = 0;
302 #endif /* BB_FEATURE_VI_CRASHME */
306 int main(int argc, char **argv)
308 extern int vi_main(int argc, char **argv)
309 #endif /* STANDALONE */
314 #ifdef BB_FEATURE_VI_YANKMARK
316 #endif /* BB_FEATURE_VI_YANKMARK */
318 SOs = "\033[7m"; // Terminal standout mode on
319 SOn = "\033[0m"; // Terminal standout mode off
320 #ifdef BB_FEATURE_VI_CRASHME
321 (void) srand((long) getpid());
322 #endif /* BB_FEATURE_VI_CRASHME */
323 status_buffer = (Byte *) malloc(200); // hold messages to user
324 #ifdef BB_FEATURE_VI_READONLY
326 if (strncmp(argv[0], "view", 4) == 0) {
329 #endif /* BB_FEATURE_VI_READONLY */
330 #ifdef BB_FEATURE_VI_SETOPTS
334 #endif /* BB_FEATURE_VI_SETOPTS */
335 #ifdef BB_FEATURE_VI_YANKMARK
336 for (i = 0; i < 28; i++) {
338 } // init the yank regs
339 #endif /* BB_FEATURE_VI_YANKMARK */
340 #ifdef BB_FEATURE_VI_DOT_CMD
341 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
342 #endif /* BB_FEATURE_VI_DOT_CMD */
344 // 1- process $HOME/.exrc file
345 // 2- process EXINIT variable from environment
346 // 3- process command line args
347 while ((c = getopt(argc, argv, "hCR")) != -1) {
349 #ifdef BB_FEATURE_VI_CRASHME
353 #endif /* BB_FEATURE_VI_CRASHME */
354 #ifdef BB_FEATURE_VI_READONLY
355 case 'R': // Read-only flag
358 #endif /* BB_FEATURE_VI_READONLY */
359 //case 'r': // recover flag- ignore- we don't use tmp file
360 //case 'x': // encryption flag- ignore
361 //case 'c': // execute command first
362 //case 'h': // help -- just use default
369 // The argv array can be used by the ":next" and ":rewind" commands
371 fn_start = optind; // remember first file name for :next and :rew
374 //----- This is the main file handling loop --------------
375 if (optind >= argc) {
376 editing = 1; // 0= exit, 1= one file, 2= multiple files
379 for (; optind < argc; optind++) {
380 editing = 1; // 0=exit, 1=one file, 2+ =many files
383 cfn = (Byte *) strdup(argv[optind]);
387 //-----------------------------------------------------------
392 static void edit_file(Byte * fn)
397 #ifdef BB_FEATURE_VI_USE_SIGNALS
400 #endif /* BB_FEATURE_VI_USE_SIGNALS */
401 #ifdef BB_FEATURE_VI_YANKMARK
402 static Byte *cur_line;
403 #endif /* BB_FEATURE_VI_YANKMARK */
408 #ifdef BB_FEATURE_VI_WIN_RESIZE
410 #endif /* BB_FEATURE_VI_WIN_RESIZE */
411 new_screen(rows, columns); // get memory for virtual screen
413 cnt = file_size(fn); // file size
414 size = 2 * cnt; // 200% of file size
415 new_text(size); // get a text[] buffer
416 screenbegin = dot = end = text;
418 file_insert(fn, text, cnt);
420 (void) char_insert(text, '\n'); // start empty buf with dummy line
422 file_modified = FALSE;
423 #ifdef BB_FEATURE_VI_YANKMARK
424 YDreg = 26; // default Yank/Delete reg
425 Ureg = 27; // hold orig line for "U" cmd
426 for (cnt = 0; cnt < 28; cnt++) {
429 mark[26] = mark[27] = text; // init "previous context"
430 #endif /* BB_FEATURE_VI_YANKMARK */
432 err_method = 1; // flash
433 last_forward_char = last_input_char = '\0';
438 #ifdef BB_FEATURE_VI_USE_SIGNALS
439 signal(SIGHUP, catch_sig);
440 signal(SIGINT, catch_sig);
441 signal(SIGALRM, alarm_sig);
442 signal(SIGTERM, catch_sig);
443 signal(SIGQUIT, core_sig);
444 signal(SIGILL, core_sig);
445 signal(SIGTRAP, core_sig);
446 signal(SIGIOT, core_sig);
447 signal(SIGABRT, core_sig);
448 signal(SIGFPE, core_sig);
449 signal(SIGBUS, core_sig);
450 signal(SIGSEGV, core_sig);
451 signal(SIGSYS, core_sig);
452 signal(SIGWINCH, winch_sig);
453 signal(SIGTSTP, suspend_sig);
454 sig = setjmp(restart);
458 msg = "(window resize)";
468 msg = "(I tried to touch invalid memory)";
472 psbs("-- caught signal %d %s--", sig, msg);
474 #endif /* BB_FEATURE_VI_USE_SIGNALS */
477 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
480 offset = 0; // no horizontal offset
482 #ifdef BB_FEATURE_VI_DOT_CMD
483 if (last_modifying_cmd != 0)
484 free(last_modifying_cmd);
485 if (ioq_start != NULL)
487 ioq = ioq_start = last_modifying_cmd = 0;
489 #endif /* BB_FEATURE_VI_DOT_CMD */
493 //------This is the main Vi cmd handling loop -----------------------
494 while (editing > 0) {
495 #ifdef BB_FEATURE_VI_CRASHME
497 if ((end - text) > 1) {
498 crash_dummy(); // generate a random command
502 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
506 #endif /* BB_FEATURE_VI_CRASHME */
507 last_input_char = c = get_one_char(); // get a cmd from user
508 #ifdef BB_FEATURE_VI_YANKMARK
509 // save a copy of the current line- for the 'U" command
510 if (begin_line(dot) != cur_line) {
511 cur_line = begin_line(dot);
512 text_yank(begin_line(dot), end_line(dot), Ureg);
514 #endif /* BB_FEATURE_VI_YANKMARK */
515 #ifdef BB_FEATURE_VI_DOT_CMD
516 // These are commands that change text[].
517 // Remember the input for the "." command
518 if (!adding2q && ioq_start == 0
519 && strchr((char *) modifying_cmds, c) != NULL) {
522 #endif /* BB_FEATURE_VI_DOT_CMD */
523 do_cmd(c); // execute the user command
525 // poll to see if there is input already waiting. if we are
526 // not able to display output fast enough to keep up, skip
527 // the display update until we catch up with input.
528 if (mysleep(0) == 0) {
529 // no input pending- so update output
533 #ifdef BB_FEATURE_VI_CRASHME
535 crash_test(); // test editor variables
536 #endif /* BB_FEATURE_VI_CRASHME */
538 //-------------------------------------------------------------------
540 place_cursor(rows, 0); // go to bottom of screen
541 clear_to_eol(); // Erase to end of line
545 static Byte readbuffer[BUFSIZ];
547 #ifdef BB_FEATURE_VI_CRASHME
548 static int totalcmds = 0;
549 static int Mp = 85; // Movement command Probability
550 static int Np = 90; // Non-movement command Probability
551 static int Dp = 96; // Delete command Probability
552 static int Ip = 97; // Insert command Probability
553 static int Yp = 98; // Yank command Probability
554 static int Pp = 99; // Put command Probability
555 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
556 char chars[20] = "\t012345 abcdABCD-=.$";
557 char *words[20] = { "this", "is", "a", "test",
558 "broadcast", "the", "emergency", "of",
559 "system", "quick", "brown", "fox",
560 "jumped", "over", "lazy", "dogs",
561 "back", "January", "Febuary", "March"
564 "You should have received a copy of the GNU General Public License\n",
565 "char c, cm, *cmd, *cmd1;\n",
566 "generate a command by percentages\n",
567 "Numbers may be typed as a prefix to some commands.\n",
568 "Quit, discarding changes!\n",
569 "Forced write, if permission originally not valid.\n",
570 "In general, any ex or ed command (such as substitute or delete).\n",
571 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
572 "Please get w/ me and I will go over it with you.\n",
573 "The following is a list of scheduled, committed changes.\n",
574 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
575 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
576 "Any question about transactions please contact Sterling Huxley.\n",
577 "I will try to get back to you by Friday, December 31.\n",
578 "This Change will be implemented on Friday.\n",
579 "Let me know if you have problems accessing this;\n",
580 "Sterling Huxley recently added you to the access list.\n",
581 "Would you like to go to lunch?\n",
582 "The last command will be automatically run.\n",
583 "This is too much english for a computer geek.\n",
585 char *multilines[20] = {
586 "You should have received a copy of the GNU General Public License\n",
587 "char c, cm, *cmd, *cmd1;\n",
588 "generate a command by percentages\n",
589 "Numbers may be typed as a prefix to some commands.\n",
590 "Quit, discarding changes!\n",
591 "Forced write, if permission originally not valid.\n",
592 "In general, any ex or ed command (such as substitute or delete).\n",
593 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
594 "Please get w/ me and I will go over it with you.\n",
595 "The following is a list of scheduled, committed changes.\n",
596 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
597 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
598 "Any question about transactions please contact Sterling Huxley.\n",
599 "I will try to get back to you by Friday, December 31.\n",
600 "This Change will be implemented on Friday.\n",
601 "Let me know if you have problems accessing this;\n",
602 "Sterling Huxley recently added you to the access list.\n",
603 "Would you like to go to lunch?\n",
604 "The last command will be automatically run.\n",
605 "This is too much english for a computer geek.\n",
608 // create a random command to execute
609 static void crash_dummy()
611 static int sleeptime; // how long to pause between commands
612 char c, cm, *cmd, *cmd1;
613 int i, cnt, thing, rbi, startrbi, percent;
615 // "dot" movement commands
616 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
618 // is there already a command running?
619 if (strlen((char *) readbuffer) > 0)
623 sleeptime = 0; // how long to pause between commands
624 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
625 // generate a command by percentages
626 percent = (int) lrand48() % 100; // get a number from 0-99
627 if (percent < Mp) { // Movement commands
628 // available commands
631 } else if (percent < Np) { // non-movement commands
632 cmd = "mz<>\'\""; // available commands
634 } else if (percent < Dp) { // Delete commands
635 cmd = "dx"; // available commands
637 } else if (percent < Ip) { // Inset commands
638 cmd = "iIaAsrJ"; // available commands
640 } else if (percent < Yp) { // Yank commands
641 cmd = "yY"; // available commands
643 } else if (percent < Pp) { // Put commands
644 cmd = "pP"; // available commands
647 // We do not know how to handle this command, try again
651 // randomly pick one of the available cmds from "cmd[]"
652 i = (int) lrand48() % strlen(cmd);
654 if (strchr(":\024", cm))
655 goto cd0; // dont allow these commands
656 readbuffer[rbi++] = cm; // put cmd into input buffer
658 // now we have the command-
659 // there are 1, 2, and multi char commands
660 // find out which and generate the rest of command as necessary
661 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
662 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
663 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
664 cmd1 = "abcdefghijklmnopqrstuvwxyz";
666 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
668 readbuffer[rbi++] = c; // add movement to input buffer
670 if (strchr("iIaAsc", cm)) { // multi-char commands
673 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
675 readbuffer[rbi++] = c; // add movement to input buffer
677 thing = (int) lrand48() % 4; // what thing to insert
678 cnt = (int) lrand48() % 10; // how many to insert
679 for (i = 0; i < cnt; i++) {
680 if (thing == 0) { // insert chars
681 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
682 } else if (thing == 1) { // insert words
683 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
684 strcat((char *) readbuffer, " ");
685 sleeptime = 0; // how fast to type
686 } else if (thing == 2) { // insert lines
687 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
688 sleeptime = 0; // how fast to type
689 } else { // insert multi-lines
690 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
691 sleeptime = 0; // how fast to type
694 strcat((char *) readbuffer, "\033");
699 (void) mysleep(sleeptime); // sleep 1/100 sec
702 // test to see if there are any errors
703 static void crash_test()
705 static time_t oldtim;
707 char d[2], buf[100], msg[BUFSIZ];
711 strcat((char *) msg, "end<text ");
714 strcat((char *) msg, "end>textend ");
717 strcat((char *) msg, "dot<text ");
720 strcat((char *) msg, "dot>end ");
722 if (screenbegin < text) {
723 strcat((char *) msg, "screenbegin<text ");
725 if (screenbegin > end - 1) {
726 strcat((char *) msg, "screenbegin>end-1 ");
729 if (strlen(msg) > 0) {
731 sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
732 write(1, buf, strlen(buf));
733 write(1, msg, strlen(msg));
734 write(1, "\n\n\n", 3);
735 write(1, "\033[7m[Hit return to continue]\033[0m", 32);
736 while (read(0, d, 1) > 0) {
737 if (d[0] == '\n' || d[0] == '\r')
742 tim = (time_t) time((time_t *) 0);
743 if (tim >= (oldtim + 3)) {
744 sprintf((char *) status_buffer,
745 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
746 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
751 #endif /* BB_FEATURE_VI_CRASHME */
753 //---------------------------------------------------------------------
754 //----- the Ascii Chart -----------------------------------------------
756 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
757 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
758 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
759 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
760 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
761 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
762 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
763 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
764 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
765 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
766 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
767 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
768 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
769 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
770 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
771 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
772 //---------------------------------------------------------------------
774 //----- Execute a Vi Command -----------------------------------
775 static void do_cmd(Byte c)
777 Byte c1, *p, *q, *msg, buf[9], *save_dot;
778 int cnt, i, j, dir, yf;
780 c1 = c; // quiet the compiler
781 cnt = yf = dir = 0; // quiet the compiler
782 p = q = save_dot = msg = buf; // quiet the compiler
783 memset(buf, '\0', 9); // clear buf
785 // we are 'R'eplacing the current *dot with new char
787 // don't Replace past E-o-l
788 cmd_mode = 1; // convert to insert
790 if (1 <= c && c <= 127) { // only ASCII chars
792 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
793 dot = char_insert(dot, c); // insert new char
799 // insert the char c at "dot"
800 if (1 <= c && c <= 127) {
801 dot = char_insert(dot, c); // only ASCII chars
816 case 0x14: // dc4 ctrl-T
817 #ifdef BB_FEATURE_VI_CRASHME
818 crashme = (crashme == 0) ? 1 : 0;
819 #endif /* BB_FEATURE_VI_CRASHME */
854 default: // unrecognised command
863 end_cmd_q(); // stop adding to q
864 case 0x00: // nul- ignore
866 case 2: // ctrl-B scroll up full screen
867 case VI_K_PAGEUP: // Cursor Key Page Up
868 dot_scroll(rows - 2, -1);
870 #ifdef BB_FEATURE_VI_USE_SIGNALS
871 case 0x03: // ctrl-C interrupt
874 case 26: // ctrl-Z suspend
875 suspend_sig(SIGTSTP);
877 #endif /* BB_FEATURE_VI_USE_SIGNALS */
878 case 4: // ctrl-D scroll down half screen
879 dot_scroll((rows - 2) / 2, 1);
881 case 5: // ctrl-E scroll down one line
884 case 6: // ctrl-F scroll down full screen
885 case VI_K_PAGEDOWN: // Cursor Key Page Down
886 dot_scroll(rows - 2, 1);
888 case 7: // ctrl-G show current status
891 case 'h': // h- move left
892 case VI_K_LEFT: // cursor key Left
893 case 8: // ^h- move left (This may be ERASE char)
894 case 127: // DEL- move left (This may be ERASE char)
900 case 10: // Newline ^J
901 case 'j': // j- goto next line, same col
902 case VI_K_DOWN: // cursor key Down
906 dot_next(); // go to next B-o-l
907 dot = move_to_col(dot, ccol + offset); // try stay in same col
909 case 12: // ctrl-L force redraw whole screen
910 place_cursor(0, 0); // put cursor in correct place
911 clear_to_eos(); // tel terminal to erase display
913 screen_erase(); // erase the internal screen buffer
914 refresh(TRUE); // this will redraw the entire display
916 case 13: // Carriage Return ^M
917 case '+': // +- goto next line
924 case 21: // ctrl-U scroll up half screen
925 dot_scroll((rows - 2) / 2, -1);
927 case 25: // ctrl-Y scroll up one line
933 cmd_mode = 0; // stop insrting
935 *status_buffer = '\0'; // clear status buffer
937 case ' ': // move right
938 case 'l': // move right
939 case VI_K_RIGHT: // Cursor Key Right
945 #ifdef BB_FEATURE_VI_YANKMARK
946 case '"': // "- name a register to use for Delete/Yank
955 case '\'': // '- goto a specific mark
962 if (text <= q && q < end) {
964 dot_begin(); // go to B-o-l
967 } else if (c1 == '\'') { // goto previous context
968 dot = swap_context(dot); // swap current and previous context
969 dot_begin(); // go to B-o-l
975 case 'm': // m- Mark a line
976 // this is really stupid. If there are any inserts or deletes
977 // between text[0] and dot then this mark will not point to the
978 // correct location! It could be off by many lines!
979 // Well..., at least its quick and dirty.
985 mark[(int) c1] = dot;
990 case 'P': // P- Put register before
991 case 'p': // p- put register after
994 psbs("Nothing in register %c", what_reg());
997 // are we putting whole lines or strings
998 if (strchr((char *) p, '\n') != NULL) {
1000 dot_begin(); // putting lines- Put above
1003 // are we putting after very last line?
1004 if (end_line(dot) == (end - 1)) {
1005 dot = end; // force dot to end of text[]
1007 dot_next(); // next line, then put before
1012 dot_right(); // move to right, can move to NL
1014 dot = string_insert(dot, p); // insert the string
1015 end_cmd_q(); // stop adding to q
1018 case 'U': // U- Undo; replace current line with original version
1019 if (reg[Ureg] != 0) {
1020 p = begin_line(dot);
1022 p = text_hole_delete(p, q); // delete cur line
1023 p = string_insert(p, reg[Ureg]); // insert orig line
1028 #endif /* BB_FEATURE_VI_YANKMARK */
1029 case '$': // $- goto end of line
1030 case VI_K_END: // Cursor Key End
1034 dot = end_line(dot + 1);
1036 case '%': // %- find matching char of pair () [] {}
1037 for (q = dot; q < end && *q != '\n'; q++) {
1038 if (strchr("()[]{}", *q) != NULL) {
1039 // we found half of a pair
1040 p = find_pair(q, *q);
1052 case 'f': // f- forward to a user specified char
1053 last_forward_char = get_one_char(); // get the search char
1055 // dont seperate these two commands. 'f' depends on ';'
1057 //**** fall thru to ... 'i'
1058 case ';': // ;- look at rest of line for last forward char
1063 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1066 if (*q == last_forward_char)
1069 case '-': // -- goto prev line
1076 #ifdef BB_FEATURE_VI_DOT_CMD
1077 case '.': // .- repeat the last modifying command
1078 // Stuff the last_modifying_cmd back into stdin
1079 // and let it be re-executed.
1080 if (last_modifying_cmd != 0) {
1081 ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
1084 #endif /* BB_FEATURE_VI_DOT_CMD */
1085 #ifdef BB_FEATURE_VI_SEARCH
1086 case '?': // /- search for a pattern
1087 case '/': // /- search for a pattern
1090 q = get_input_line(buf); // get input line- use "status line"
1091 if (strlen((char *) q) == 1)
1092 goto dc3; // if no pat re-use old pat
1093 if (strlen((char *) q) > 1) { // new pat- save it and find
1094 // there is a new pat
1095 if (last_search_pattern != 0) {
1096 free(last_search_pattern);
1098 last_search_pattern = (Byte *) strdup((char *) q);
1099 goto dc3; // now find the pattern
1101 // user changed mind and erased the "/"- do nothing
1103 case 'N': // N- backward search for last pattern
1107 dir = BACK; // assume BACKWARD search
1109 if (last_search_pattern[0] == '?') {
1113 goto dc4; // now search for pattern
1115 case 'n': // n- repeat search for last pattern
1116 // search rest of text[] starting at next char
1117 // if search fails return orignal "p" not the "p+1" address
1122 if (last_search_pattern == 0) {
1123 msg = (Byte *) "No previous regular expression";
1126 if (last_search_pattern[0] == '/') {
1127 dir = FORWARD; // assume FORWARD search
1130 if (last_search_pattern[0] == '?') {
1135 q = char_search(p, last_search_pattern + 1, dir, FULL);
1137 dot = q; // good search, update "dot"
1141 // no pattern found between "dot" and "end"- continue at top
1146 q = char_search(p, last_search_pattern + 1, dir, FULL);
1147 if (q != NULL) { // found something
1148 dot = q; // found new pattern- goto it
1149 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1151 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1154 msg = (Byte *) "Pattern not found";
1159 case '{': // {- move backward paragraph
1160 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1161 if (q != NULL) { // found blank line
1162 dot = next_line(q); // move to next blank line
1165 case '}': // }- move forward paragraph
1166 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1167 if (q != NULL) { // found blank line
1168 dot = next_line(q); // move to next blank line
1171 #endif /* BB_FEATURE_VI_SEARCH */
1172 case '0': // 0- goto begining of line
1182 if (c == '0' && cmdcnt < 1) {
1183 dot_begin(); // this was a standalone zero
1185 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1188 case ':': // :- the colon mode commands
1189 #ifdef BB_FEATURE_VI_COLON
1190 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1191 colon(p); // execute the command
1192 #else /* BB_FEATURE_VI_COLON */
1193 *status_buffer = '\0'; // clear the status buffer
1194 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1195 clear_to_eol(); // clear the line
1196 write(1, ":", 1); // write out the : prompt
1197 for (cnt = 0; cnt < 8; cnt++) {
1198 c1 = get_one_char();
1199 if (c1 == '\n' || c1 == '\r') {
1203 buf[cnt + 1] = '\0';
1204 write(1, buf + cnt, 1); // echo the char
1206 cnt = strlen((char *) buf);
1207 if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
1208 strncasecmp((char *) buf, "q!", cnt) == 0) { // delete lines
1209 if (file_modified == TRUE && buf[1] != '!') {
1210 psbs("No write since last change (:quit! overrides)");
1214 } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
1215 strncasecmp((char *) buf, "wq", cnt) == 0) {
1216 cnt = file_write(cfn, text, end - 1);
1217 file_modified = FALSE;
1218 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1219 if (buf[1] == 'q') {
1222 } else { // unrecognised cmd
1225 #endif /* BB_FEATURE_VI_COLON */
1227 case '<': // <- Left shift something
1228 case '>': // >- Right shift something
1229 cnt = count_lines(text, dot); // remember what line we are on
1230 c1 = get_one_char(); // get the type of thing to delete
1231 find_range(&p, &q, c1);
1232 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1235 i = count_lines(p, q); // # of lines we are shifting
1236 for ( ; i > 0; i--, p = next_line(p)) {
1238 // shift left- remove tab or 8 spaces
1240 // shrink buffer 1 char
1241 (void) text_hole_delete(p, p);
1242 } else if (*p == ' ') {
1243 // we should be calculating columns, not just SPACE
1244 for (j = 0; *p == ' ' && j < tabstop; j++) {
1245 (void) text_hole_delete(p, p);
1248 } else if (c == '>') {
1249 // shift right -- add tab or 8 spaces
1250 (void) char_insert(p, '\t');
1253 dot = find_line(cnt); // what line were we on
1255 end_cmd_q(); // stop adding to q
1257 case 'A': // A- append at e-o-l
1258 dot_end(); // go to e-o-l
1259 //**** fall thru to ... 'a'
1260 case 'a': // a- append after current char
1265 case 'B': // B- back a blank-delimited Word
1266 case 'E': // E- end of a blank-delimited word
1267 case 'W': // W- forward a blank-delimited word
1274 if (c == 'W' || isspace(dot[dir])) {
1275 dot = skip_thing(dot, 1, dir, S_TO_WS);
1276 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1279 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1281 case 'C': // C- Change to e-o-l
1282 case 'D': // D- delete to e-o-l
1284 dot = dollar_line(dot); // move to before NL
1285 // copy text into a register and delete
1286 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1288 goto dc_i; // start inserting
1289 #ifdef BB_FEATURE_VI_DOT_CMD
1291 end_cmd_q(); // stop adding to q
1292 #endif /* BB_FEATURE_VI_DOT_CMD */
1294 case 'H': // H- goto top line on screen
1296 if (cmdcnt > (rows - 1)) {
1297 cmdcnt = (rows - 1);
1304 case 'I': // I- insert before first non-blank
1307 //**** fall thru to ... 'i'
1308 case 'i': // i- insert before current char
1309 case VI_K_INSERT: // Cursor Key Insert
1311 cmd_mode = 1; // start insrting
1312 psb("-- Insert --");
1314 case 'J': // J- join current and next lines together
1318 dot_end(); // move to NL
1319 if (dot < end - 1) { // make sure not last char in text[]
1320 *dot++ = ' '; // replace NL with space
1321 while (isblnk(*dot)) { // delete leading WS
1325 end_cmd_q(); // stop adding to q
1327 case 'L': // L- goto bottom line on screen
1329 if (cmdcnt > (rows - 1)) {
1330 cmdcnt = (rows - 1);
1338 case 'O': // O- open a empty line above
1340 p = begin_line(dot);
1341 if (p[-1] == '\n') {
1343 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1345 dot = char_insert(dot, '\n');
1348 dot = char_insert(dot, '\n'); // i\n\033
1353 case 'R': // R- continuous Replace char
1355 psb("-- Replace --");
1357 case 'X': // X- delete char before dot
1358 case 'x': // x- delete the current char
1359 case 's': // s- substitute the current char
1366 if (dot[dir] != '\n') {
1368 dot--; // delete prev char
1369 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1372 goto dc_i; // start insrting
1373 end_cmd_q(); // stop adding to q
1375 case 'Z': // Z- if modified, {write}; exit
1376 // ZZ means to save file (if necessary), then exit
1377 c1 = get_one_char();
1382 if (file_modified == TRUE
1383 #ifdef BB_FEATURE_VI_READONLY
1384 && readonly == FALSE
1385 #endif /* BB_FEATURE_VI_READONLY */
1387 cnt = file_write(cfn, text, end - 1);
1388 if (cnt == (end - 1 - text + 1)) {
1395 case '^': // ^- move to first non-blank on line
1399 case 'b': // b- back a word
1400 case 'e': // e- end of word
1407 if ((dot + dir) < text || (dot + dir) > end - 1)
1410 if (isspace(*dot)) {
1411 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1413 if (isalnum(*dot) || *dot == '_') {
1414 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1415 } else if (ispunct(*dot)) {
1416 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1419 case 'c': // c- change something
1420 case 'd': // d- delete something
1421 #ifdef BB_FEATURE_VI_YANKMARK
1422 case 'y': // y- yank something
1423 case 'Y': // Y- Yank a line
1424 #endif /* BB_FEATURE_VI_YANKMARK */
1425 yf = YANKDEL; // assume either "c" or "d"
1426 #ifdef BB_FEATURE_VI_YANKMARK
1427 if (c == 'y' || c == 'Y')
1429 #endif /* BB_FEATURE_VI_YANKMARK */
1432 c1 = get_one_char(); // get the type of thing to delete
1433 find_range(&p, &q, c1);
1434 if (c1 == 27) { // ESC- user changed mind and wants out
1435 c = c1 = 27; // Escape- do nothing
1436 } else if (strchr("wW", c1)) {
1438 // don't include trailing WS as part of word
1439 while (isblnk(*q)) {
1440 if (q <= text || q[-1] == '\n')
1445 dot = yank_delete(p, q, 0, yf); // delete word
1446 } else if (strchr("0bBeE$", c1)) {
1447 // single line copy text into a register and delete
1448 dot = yank_delete(p, q, 0, yf); // delete word
1449 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
1450 // multiple line copy text into a register and delete
1451 dot = yank_delete(p, q, 1, yf); // delete lines
1457 // could not recognize object
1458 c = c1 = 27; // error-
1462 // if CHANGING, not deleting, start inserting after the delete
1464 strcpy((char *) buf, "Change");
1465 goto dc_i; // start inserting
1468 strcpy((char *) buf, "Delete");
1470 #ifdef BB_FEATURE_VI_YANKMARK
1471 if (c == 'y' || c == 'Y') {
1472 strcpy((char *) buf, "Yank");
1475 q = p + strlen((char *) p);
1476 for (cnt = 0; p <= q; p++) {
1480 psb("%s %d lines (%d chars) using [%c]",
1481 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1482 #endif /* BB_FEATURE_VI_YANKMARK */
1483 end_cmd_q(); // stop adding to q
1486 case 'k': // k- goto prev line, same col
1487 case VI_K_UP: // cursor key Up
1492 dot = move_to_col(dot, ccol + offset); // try stay in same col
1494 case 'r': // r- replace the current char with user input
1495 c1 = get_one_char(); // get the replacement char
1498 file_modified = TRUE; // has the file been modified
1500 end_cmd_q(); // stop adding to q
1502 case 'w': // w- forward a word
1506 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1507 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1508 } else if (ispunct(*dot)) { // we are on PUNCT
1509 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1512 dot++; // move over word
1513 if (isspace(*dot)) {
1514 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1518 c1 = get_one_char(); // get the replacement char
1521 cnt = (rows - 2) / 2; // put dot at center
1523 cnt = rows - 2; // put dot at bottom
1524 screenbegin = begin_line(dot); // start dot at top
1525 dot_scroll(cnt, -1);
1527 case '|': // |- move to column "cmdcnt"
1528 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1530 case '~': // ~- flip the case of letters a-z -> A-Z
1534 if (islower(*dot)) {
1535 *dot = toupper(*dot);
1536 file_modified = TRUE; // has the file been modified
1537 } else if (isupper(*dot)) {
1538 *dot = tolower(*dot);
1539 file_modified = TRUE; // has the file been modified
1542 end_cmd_q(); // stop adding to q
1544 //----- The Cursor and Function Keys -----------------------------
1545 case VI_K_HOME: // Cursor Key Home
1548 // The Fn keys could point to do_macro which could translate them
1549 case VI_K_FUN1: // Function Key F1
1550 case VI_K_FUN2: // Function Key F2
1551 case VI_K_FUN3: // Function Key F3
1552 case VI_K_FUN4: // Function Key F4
1553 case VI_K_FUN5: // Function Key F5
1554 case VI_K_FUN6: // Function Key F6
1555 case VI_K_FUN7: // Function Key F7
1556 case VI_K_FUN8: // Function Key F8
1557 case VI_K_FUN9: // Function Key F9
1558 case VI_K_FUN10: // Function Key F10
1559 case VI_K_FUN11: // Function Key F11
1560 case VI_K_FUN12: // Function Key F12
1565 // if text[] just became empty, add back an empty line
1567 (void) char_insert(text, '\n'); // start empty buf with dummy line
1570 // it is OK for dot to exactly equal to end, otherwise check dot validity
1572 dot = bound_dot(dot); // make sure "dot" is valid
1574 #ifdef BB_FEATURE_VI_YANKMARK
1575 check_context(c); // update the current context
1576 #endif /* BB_FEATURE_VI_YANKMARK */
1579 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1580 cnt = dot - begin_line(dot);
1581 // Try to stay off of the Newline
1582 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1586 //----- The Colon commands -------------------------------------
1587 #ifdef BB_FEATURE_VI_COLON
1588 static Byte *get_address(Byte * p, int *addr) // get colon addr, if present
1593 #ifdef BB_FEATURE_VI_YANKMARK
1595 #endif /* BB_FEATURE_VI_YANKMARK */
1596 #ifdef BB_FEATURE_VI_SEARCH
1597 Byte *pat, buf[1024];
1598 #endif /* BB_FEATURE_VI_SEARCH */
1600 *addr = -1; // assume no addr
1601 if (*p == '.') { // the current line
1603 q = begin_line(dot);
1604 *addr = count_lines(text, q);
1605 #ifdef BB_FEATURE_VI_YANKMARK
1606 } else if (*p == '\'') { // is this a mark addr
1610 if (c >= 'a' && c <= 'z') {
1614 if (q != NULL) { // is mark valid
1615 *addr = count_lines(text, q); // count lines
1618 #endif /* BB_FEATURE_VI_YANKMARK */
1619 #ifdef BB_FEATURE_VI_SEARCH
1620 } else if (*p == '/') { // a search pattern
1622 for (p++; *p; p++) {
1628 pat = (Byte *) strdup((char *) buf); // save copy of pattern
1631 q = char_search(dot, pat, FORWARD, FULL);
1633 *addr = count_lines(text, q);
1636 #endif /* BB_FEATURE_VI_SEARCH */
1637 } else if (*p == '$') { // the last line in file
1639 q = begin_line(end - 1);
1640 *addr = count_lines(text, q);
1641 } else if (isdigit(*p)) { // specific line number
1642 sscanf((char *) p, "%d%n", addr, &st);
1644 } else { // I don't reconise this
1645 // unrecognised address- assume -1
1651 static void colon(Byte * buf)
1653 Byte c, *orig_buf, *buf1, *q, *r;
1654 Byte *fn, cmd[100], args[100];
1655 int i, l, li, ch, st, b, e;
1656 int useforce, forced;
1658 // :3154 // if (-e line 3154) goto it else stay put
1659 // :4,33w! foo // write a portion of buffer to file "foo"
1660 // :w // write all of buffer to current file
1662 // :q! // quit- dont care about modified file
1663 // :'a,'z!sort -u // filter block through sort
1664 // :'f // goto mark "f"
1665 // :'fl // list literal the mark "f" line
1666 // :.r bar // read file "bar" into buffer before dot
1667 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1668 // :/xyz/ // goto the "xyz" line
1669 // :s/find/replace/ // substitute pattern "find" with "replace"
1671 if (strlen((char *) buf) <= 0)
1674 buf++; // move past the ':'
1676 forced = useforce = FALSE;
1677 li = st = ch = i = 0;
1679 q = text; // assume 1,$ for the range
1681 li = count_lines(text, end - 1);
1682 fn = cfn; // default to current file
1684 // look for optional FIRST address(es) :. :1 :1,9 :'q,'a
1685 while (isblnk(*buf))
1688 // get FIRST addr, if present
1689 buf = get_address(buf, &b);
1690 while (isblnk(*buf))
1694 while (isblnk(*buf))
1696 // look for SECOND address
1697 buf = get_address(buf, &e);
1699 while (isblnk(*buf))
1702 // remember orig command line
1705 // get the COMMAND into cmd[]
1708 while (*buf != '\0') {
1714 // get any ARGuments
1715 while (isblnk(*buf))
1717 strcpy((char *) args, (char *) buf);
1718 if (cmd[strlen((char *) cmd) - 1] == '!') {
1720 cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
1723 // if there is only one addr, then the addr
1724 // is the line number of the single line the
1725 // user wants. So, reset the end
1726 // pointer to point at end of the "b" line
1727 q = find_line(b); // what line is #b
1732 // we were given two addrs. change the
1733 // end pointer to the addr given by user.
1734 r = find_line(e); // what line is #e
1738 // ------------ now look for the command ------------
1739 i = strlen((char *) cmd);
1740 if (i == 0) { // :123CR goto line #123
1742 dot = find_line(b); // what line is #b
1745 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1746 if (b < 0) { // no addr given- use defaults
1747 b = e = count_lines(text, dot);
1750 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1751 if (b < 0) { // no addr given- use defaults
1752 q = begin_line(dot); // assume .,. for the range
1755 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1757 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1758 // don't exit if the file been modified
1759 if (file_modified == TRUE && useforce != TRUE) {
1760 psbs("No write since last change (:edit! overrides)");
1764 if (strlen((char *) fn) <= 0) {
1765 // no file name given, re-edit current file
1770 cfn = (Byte *) strdup((char *) fn); // make this the current file
1771 // delete all the contents of text[]
1772 new_text(2 * file_size(fn));
1773 screenbegin = dot = end = text;
1776 ch = file_insert(fn, text, file_size(fn));
1778 file_modified = FALSE;
1779 #ifdef BB_FEATURE_VI_YANKMARK
1781 free(reg[Ureg]); // free orig line reg- for 'U'
1782 if (reg[YDreg] != 0)
1783 free(reg[YDreg]); // free default yank/delete register
1784 for (li = 0; li < 28; li++) {
1787 #endif /* BB_FEATURE_VI_YANKMARK */
1788 // how many lines in text[]?
1789 li = count_lines(text, end - 1);
1790 psb("\"%s\" %dL, %dC", cfn, li, ch);
1791 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1792 if (b != -1 || e != -1) {
1793 ni((Byte *) "No address allowed on this command");
1796 if (strlen((char *) args) > 0) {
1797 // user wants a new filename
1800 cfn = (Byte *) strdup((char *) args);
1802 // user wants file status info
1805 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1806 // print out values of all features
1807 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1808 clear_to_eol(); // clear the line
1813 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1814 if (b < 0) { // no addr given- use defaults
1815 q = begin_line(dot); // assume .,. for the range
1818 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1819 clear_to_eol(); // clear the line
1820 write(1, "\r\n", 2);
1821 for (; q <= r; q++) {
1827 } else if (*q < ' ') {
1835 #ifdef BB_FEATURE_VI_SET
1837 #endif /* BB_FEATURE_VI_SET */
1839 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1840 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1841 if (useforce == TRUE) {
1842 // force end of argv list
1849 // don't exit if the file been modified
1850 if (file_modified == TRUE) {
1851 psbs("No write since last change (:%s! overrides)",
1852 (*cmd == 'q' ? "quit" : "next"));
1855 // are there other file to edit
1856 if (*cmd == 'q' && optind < save_argc - 1) {
1857 psbs("%d more file to edit", (save_argc - optind - 1));
1860 if (*cmd == 'n' && optind >= save_argc - 1) {
1861 psbs("No more files to edit");
1865 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1867 if (strlen((char *) fn) <= 0) {
1868 psbs("No filename given");
1871 if (b < 0) { // no addr given- use defaults
1872 q = begin_line(dot); // assume "dot"
1874 // read after current line- unless user said ":0r foo"
1877 ch = file_insert(fn, q, file_size(fn));
1879 goto vc1; // nothing was inserted
1880 // how many lines in text[]?
1881 li = count_lines(q, q + ch - 1);
1882 psb("\"%s\" %dL, %dC", fn, li, ch);
1884 // if the insert is before "dot" then we need to update
1887 file_modified = TRUE;
1889 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
1890 if (file_modified == TRUE && useforce != TRUE) {
1891 psbs("No write since last change (:rewind! overrides)");
1893 // reset the filenames to edit
1894 optind = fn_start - 1;
1897 #ifdef BB_FEATURE_VI_SET
1898 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
1899 i = 0; // offset into args
1900 if (strlen((char *) args) == 0) {
1901 // print out values of all options
1902 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1903 clear_to_eol(); // clear the line
1904 printf("----------------------------------------\r\n");
1905 #ifdef BB_FEATURE_VI_SETOPTS
1908 printf("autoindent ");
1911 printf("ignorecase ");
1914 printf("showmatch ");
1915 printf("tabstop=%d ", tabstop);
1916 #endif /* BB_FEATURE_VI_SETOPTS */
1920 if (strncasecmp((char *) args, "no", 2) == 0)
1921 i = 2; // ":set noautoindent"
1922 #ifdef BB_FEATURE_VI_SETOPTS
1923 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
1924 strncasecmp((char *) args + i, "ai", 2) == 0) {
1925 autoindent = (i == 2) ? 0 : 1;
1927 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
1928 strncasecmp((char *) args + i, "ic", 2) == 0) {
1929 ignorecase = (i == 2) ? 0 : 1;
1931 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
1932 strncasecmp((char *) args + i, "sm", 2) == 0) {
1933 showmatch = (i == 2) ? 0 : 1;
1935 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
1936 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1937 if (ch > 0 && ch < columns - 1)
1940 #endif /* BB_FEATURE_VI_SETOPTS */
1941 #endif /* BB_FEATURE_VI_SET */
1942 #ifdef BB_FEATURE_VI_SEARCH
1943 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1947 // F points to the "find" pattern
1948 // R points to the "replace" pattern
1949 // replace the cmd line delimiters "/" with NULLs
1950 gflag = 0; // global replace flag
1951 c = orig_buf[1]; // what is the delimiter
1952 F = orig_buf + 2; // start of "find"
1953 R = (Byte *) strchr((char *) F, c); // middle delimiter
1954 *R++ = '\0'; // terminate "find"
1955 buf1 = (Byte *) strchr((char *) R, c);
1956 *buf1++ = '\0'; // terminate "replace"
1957 if (*buf1 == 'g') { // :s/foo/bar/g
1959 gflag++; // turn on gflag
1962 if (b < 0) { // maybe :s/foo/bar/
1963 q = begin_line(dot); // start with cur line
1964 b = count_lines(text, q); // cur line number
1967 e = b; // maybe :.s/foo/bar/
1968 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1969 ls = q; // orig line start
1971 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1973 // we found the "find" pattern- delete it
1974 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1975 // inset the "replace" patern
1976 (void) string_insert(buf1, R); // insert the string
1977 // check for "global" :s/foo/bar/g
1979 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1980 q = buf1 + strlen((char *) R);
1981 goto vc4; // don't let q move past cur line
1987 #endif /* BB_FEATURE_VI_SEARCH */
1988 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1989 psb("%s", vi_Version);
1990 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1991 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
1992 // is there a file name to write to?
1993 if (strlen((char *) args) > 0) {
1996 #ifdef BB_FEATURE_VI_READONLY
1997 if (readonly == TRUE) {
1998 psbs("\"%s\" File is read only", fn);
2001 #endif /* BB_FEATURE_VI_READONLY */
2002 // how many lines in text[]?
2003 li = count_lines(q, r);
2005 if (useforce == TRUE) {
2006 // if "fn" is not write-able, chmod u+w
2007 // sprintf(syscmd, "chmod u+w %s", fn);
2011 l = file_write(fn, q, r);
2012 if (useforce == TRUE && forced == TRUE) {
2014 // sprintf(syscmd, "chmod u-w %s", fn);
2018 psb("\"%s\" %dL, %dC", fn, li, l);
2019 if (q == text && r == end - 1 && l == ch)
2020 file_modified = FALSE;
2021 if (cmd[1] == 'q' && l == ch) {
2024 #ifdef BB_FEATURE_VI_READONLY
2026 #endif /* BB_FEATURE_VI_READONLY */
2027 #ifdef BB_FEATURE_VI_YANKMARK
2028 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2029 if (b < 0) { // no addr given- use defaults
2030 q = begin_line(dot); // assume .,. for the range
2033 text_yank(q, r, YDreg);
2034 li = count_lines(q, r);
2035 psb("Yank %d lines (%d chars) into [%c]",
2036 li, strlen((char *) reg[YDreg]), what_reg());
2037 #endif /* BB_FEATURE_VI_YANKMARK */
2043 dot = bound_dot(dot); // make sure "dot" is valid
2047 static void Hit_Return(void)
2051 standout_start(); // start reverse video
2052 write(1, "[Hit return to continue]", 24);
2053 standout_end(); // end reverse video
2054 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2056 redraw(TRUE); // force redraw all
2058 #endif /* BB_FEATURE_VI_COLON */
2060 //----- Synchronize the cursor to Dot --------------------------
2061 static void sync_cursor(Byte * d, int *row, int *col)
2063 Byte *beg_cur, *end_cur; // begin and end of "d" line
2064 Byte *beg_scr, *end_scr; // begin and end of screen
2068 beg_cur = begin_line(d); // first char of cur line
2069 end_cur = end_line(d); // last char of cur line
2071 beg_scr = end_scr = screenbegin; // first char of screen
2072 end_scr = end_screen(); // last char of screen
2074 if (beg_cur < screenbegin) {
2075 // "d" is before top line on screen
2076 // how many lines do we have to move
2077 cnt = count_lines(beg_cur, screenbegin);
2079 screenbegin = beg_cur;
2080 if (cnt > (rows - 1) / 2) {
2081 // we moved too many lines. put "dot" in middle of screen
2082 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2083 screenbegin = prev_line(screenbegin);
2086 } else if (beg_cur > end_scr) {
2087 // "d" is after bottom line on screen
2088 // how many lines do we have to move
2089 cnt = count_lines(end_scr, beg_cur);
2090 if (cnt > (rows - 1) / 2)
2091 goto sc1; // too many lines
2092 for (ro = 0; ro < cnt - 1; ro++) {
2093 // move screen begin the same amount
2094 screenbegin = next_line(screenbegin);
2095 // now, move the end of screen
2096 end_scr = next_line(end_scr);
2097 end_scr = end_line(end_scr);
2100 // "d" is on screen- find out which row
2102 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2108 // find out what col "d" is on
2110 do { // drive "co" to correct column
2111 if (*tp == '\n' || *tp == '\0')
2115 co += ((tabstop - 1) - (co % tabstop));
2116 } else if (*tp < ' ') {
2117 co++; // display as ^X, use 2 columns
2119 } while (tp++ < d && ++co);
2121 // "co" is the column where "dot" is.
2122 // The screen has "columns" columns.
2123 // The currently displayed columns are 0+offset -- columns+ofset
2124 // |-------------------------------------------------------------|
2126 // offset | |------- columns ----------------|
2128 // If "co" is already in this range then we do not have to adjust offset
2129 // but, we do have to subtract the "offset" bias from "co".
2130 // If "co" is outside this range then we have to change "offset".
2131 // If the first char of a line is a tab the cursor will try to stay
2132 // in column 7, but we have to set offset to 0.
2134 if (co < 0 + offset) {
2137 if (co >= columns + offset) {
2138 offset = co - columns + 1;
2140 // if the first char of the line is a tab, and "dot" is sitting on it
2141 // force offset to 0.
2142 if (d == beg_cur && *d == '\t') {
2151 //----- Text Movement Routines ---------------------------------
2152 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2154 while (p > text && p[-1] != '\n')
2155 p--; // go to cur line B-o-l
2159 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2161 while (p < end - 1 && *p != '\n')
2162 p++; // go to cur line E-o-l
2166 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2168 while (p < end - 1 && *p != '\n')
2169 p++; // go to cur line E-o-l
2170 // Try to stay off of the Newline
2171 if (*p == '\n' && (p - begin_line(p)) > 0)
2176 static Byte *prev_line(Byte * p) // return pointer first char prev line
2178 p = begin_line(p); // goto begining of cur line
2179 if (p[-1] == '\n' && p > text)
2180 p--; // step to prev line
2181 p = begin_line(p); // goto begining of prev line
2185 static Byte *next_line(Byte * p) // return pointer first char next line
2188 if (*p == '\n' && p < end - 1)
2189 p++; // step to next line
2193 //----- Text Information Routines ------------------------------
2194 static Byte *end_screen(void)
2199 // find new bottom line
2201 for (cnt = 0; cnt < rows - 2; cnt++)
2207 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2212 if (stop < start) { // start and stop are backwards- reverse them
2218 stop = end_line(stop); // get to end of this line
2219 for (q = start; q <= stop && q <= end - 1; q++) {
2226 static Byte *find_line(int li) // find begining of line #li
2230 for (q = text; li > 1; li--) {
2236 //----- Dot Movement Routines ----------------------------------
2237 static void dot_left(void)
2239 if (dot > text && dot[-1] != '\n')
2243 static void dot_right(void)
2245 if (dot < end - 1 && *dot != '\n')
2249 static void dot_begin(void)
2251 dot = begin_line(dot); // return pointer to first char cur line
2254 static void dot_end(void)
2256 dot = end_line(dot); // return pointer to last char cur line
2259 static Byte *move_to_col(Byte * p, int l)
2266 if (*p == '\n' || *p == '\0')
2270 co += ((tabstop - 1) - (co % tabstop));
2271 } else if (*p < ' ') {
2272 co++; // display as ^X, use 2 columns
2274 } while (++co <= l && p++ < end);
2278 static void dot_next(void)
2280 dot = next_line(dot);
2283 static void dot_prev(void)
2285 dot = prev_line(dot);
2288 static void dot_scroll(int cnt, int dir)
2292 for (; cnt > 0; cnt--) {
2295 // ctrl-Y scroll up one line
2296 screenbegin = prev_line(screenbegin);
2299 // ctrl-E scroll down one line
2300 screenbegin = next_line(screenbegin);
2303 // make sure "dot" stays on the screen so we dont scroll off
2304 if (dot < screenbegin)
2306 q = end_screen(); // find new bottom line
2308 dot = begin_line(q); // is dot is below bottom line?
2312 static void dot_skip_over_ws(void)
2315 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2319 static void dot_delete(void) // delete the char at 'dot'
2321 (void) text_hole_delete(dot, dot);
2324 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2326 if (p >= end && end > text) {
2328 indicate_error('1');
2332 indicate_error('2');
2337 //----- Helper Utility Routines --------------------------------
2339 //----------------------------------------------------------------
2340 //----- Char Routines --------------------------------------------
2341 /* Chars that are part of a word-
2342 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2343 * Chars that are Not part of a word (stoppers)
2344 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2345 * Chars that are WhiteSpace
2346 * TAB NEWLINE VT FF RETURN SPACE
2347 * DO NOT COUNT NEWLINE AS WHITESPACE
2350 static Byte *new_screen(int ro, int co)
2354 screensize = ro * co + 8;
2355 screen = (Byte *) malloc(screensize);
2359 static Byte *new_text(int size)
2362 size = 10240; // have a minimum size for new files
2367 text = (Byte *) malloc(size + 8);
2368 memset(text, '\0', size); // clear new text[]
2369 //text += 4; // leave some room for "oops"
2370 textend = text + size - 1;
2371 //textend -= 4; // leave some root for "oops"
2375 #ifdef BB_FEATURE_VI_SEARCH
2376 static int mycmp(Byte * s1, Byte * s2, int len)
2380 i = strncmp((char *) s1, (char *) s2, len);
2381 #ifdef BB_FEATURE_VI_SETOPTS
2383 i = strncasecmp((char *) s1, (char *) s2, len);
2385 #endif /* BB_FEATURE_VI_SETOPTS */
2389 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2391 #ifndef REGEX_SEARCH
2395 len = strlen((char *) pat);
2396 if (dir == FORWARD) {
2397 stop = end - 1; // assume range is p - end-1
2398 if (range == LIMITED)
2399 stop = next_line(p); // range is to next line
2400 for (start = p; start < stop; start++) {
2401 if (mycmp(start, pat, len) == 0) {
2405 } else if (dir == BACK) {
2406 stop = text; // assume range is text - p
2407 if (range == LIMITED)
2408 stop = prev_line(p); // range is to prev line
2409 for (start = p - len; start >= stop; start--) {
2410 if (mycmp(start, pat, len) == 0) {
2415 // pattern not found
2417 #else /*REGEX_SEARCH */
2419 struct re_pattern_buffer preg;
2423 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2429 // assume a LIMITED forward search
2437 // count the number of chars to search over, forward or backward
2441 // RANGE could be negative if we are searching backwards
2444 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2446 // The pattern was not compiled
2447 psbs("bad search pattern: \"%s\": %s", pat, q);
2448 i = 0; // return p if pattern not compiled
2458 // search for the compiled pattern, preg, in p[]
2459 // range < 0- search backward
2460 // range > 0- search forward
2462 // re_search() < 0 not found or error
2463 // re_search() > 0 index of found pattern
2464 // struct pattern char int int int struct reg
2465 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2466 i = re_search(&preg, q, size, 0, range, 0);
2469 i = 0; // return NULL if pattern not found
2472 if (dir == FORWARD) {
2478 #endif /*REGEX_SEARCH */
2480 #endif /* BB_FEATURE_VI_SEARCH */
2482 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2484 if (c == 22) { // Is this an ctrl-V?
2485 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2486 p--; // backup onto ^
2487 refresh(FALSE); // show the ^
2491 file_modified = TRUE; // has the file been modified
2492 } else if (c == 27) { // Is this an ESC?
2495 end_cmd_q(); // stop adding to q
2496 *status_buffer = '\0'; // clear the status buffer
2497 if (p[-1] != '\n') {
2500 } else if (c == erase_char) { // Is this a BS
2502 if (p[-1] != '\n') {
2504 p = text_hole_delete(p, p); // shrink buffer 1 char
2505 #ifdef BB_FEATURE_VI_DOT_CMD
2506 // also rmove char from last_modifying_cmd
2507 if (strlen((char *) last_modifying_cmd) > 0) {
2510 q = last_modifying_cmd;
2511 q[strlen((char *) q) - 1] = '\0'; // erase BS
2512 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2514 #endif /* BB_FEATURE_VI_DOT_CMD */
2517 // insert a char into text[]
2518 Byte *sp; // "save p"
2521 c = '\n'; // translate \r to \n
2522 sp = p; // remember addr of insert
2523 p = stupid_insert(p, c); // insert the char
2524 #ifdef BB_FEATURE_VI_SETOPTS
2525 if (showmatch && strchr(")]}", *sp) != NULL) {
2528 if (autoindent && c == '\n') { // auto indent the new line
2531 q = prev_line(p); // use prev line as templet
2532 for (; isblnk(*q); q++) {
2533 p = stupid_insert(p, *q); // insert the char
2536 #endif /* BB_FEATURE_VI_SETOPTS */
2541 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2543 p = text_hole_make(p, 1);
2546 file_modified = TRUE; // has the file been modified
2552 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2554 Byte *save_dot, *p, *q;
2560 if (strchr("cdy><", c)) {
2561 p = q = begin_line(p);
2562 for (cnt = 1; cnt < cmdcnt; cnt++) {
2566 } else if (strchr("$0bBeE", c)) {
2567 do_cmd(c); // execute movement cmd
2569 } else if (strchr("wW", c)) {
2570 do_cmd(c); // execute movement cmd
2572 dot--; // move back off of next word
2573 if (dot > text && *dot == '\n')
2574 dot--; // stay off NL
2576 } else if (strchr("H-k{", c)) {
2577 q = end_line(dot); // find NL
2578 do_cmd(c); // execute movement cmd
2581 } else if (strchr("L+j}\r\n", c)) {
2582 p = begin_line(dot);
2583 do_cmd(c); // execute movement cmd
2584 dot_end(); // find NL
2587 c = 27; // error- return an ESC char
2600 static int st_test(Byte * p, int type, int dir, Byte * tested)
2610 if (type == S_BEFORE_WS) {
2612 test = ((!isspace(c)) || c == '\n');
2614 if (type == S_TO_WS) {
2616 test = ((!isspace(c)) || c == '\n');
2618 if (type == S_OVER_WS) {
2620 test = ((isspace(c)));
2622 if (type == S_END_PUNCT) {
2624 test = ((ispunct(c)));
2626 if (type == S_END_ALNUM) {
2628 test = ((isalnum(c)) || c == '_');
2634 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2638 while (st_test(p, type, dir, &c)) {
2639 // make sure we limit search to correct number of lines
2640 if (c == '\n' && --linecnt < 1)
2642 if (dir >= 0 && p >= end - 1)
2644 if (dir < 0 && p <= text)
2646 p += dir; // move to next char
2651 // find matching char of pair () [] {}
2652 static Byte *find_pair(Byte * p, Byte c)
2659 dir = 1; // assume forward
2683 for (q = p + dir; text <= q && q < end; q += dir) {
2684 // look for match, count levels of pairs (( ))
2686 level++; // increase pair levels
2688 level--; // reduce pair level
2690 break; // found matching pair
2693 q = NULL; // indicate no match
2697 #ifdef BB_FEATURE_VI_SETOPTS
2698 // show the matching char of a pair, () [] {}
2699 static void showmatching(Byte * p)
2703 // we found half of a pair
2704 q = find_pair(p, *p); // get loc of matching char
2706 indicate_error('3'); // no matching char
2708 // "q" now points to matching pair
2709 save_dot = dot; // remember where we are
2710 dot = q; // go to new loc
2711 refresh(FALSE); // let the user see it
2712 (void) mysleep(40); // give user some time
2713 dot = save_dot; // go back to old loc
2717 #endif /* BB_FEATURE_VI_SETOPTS */
2719 // open a hole in text[]
2720 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2729 cnt = end - src; // the rest of buffer
2730 if (memmove(dest, src, cnt) != dest) {
2731 psbs("can't create room for new characters");
2733 memset(p, ' ', size); // clear new hole
2734 end = end + size; // adjust the new END
2735 file_modified = TRUE; // has the file been modified
2740 // close a hole in text[]
2741 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2746 // move forwards, from beginning
2750 if (q < p) { // they are backward- swap them
2754 hole_size = q - p + 1;
2756 if (src < text || src > end)
2758 if (dest < text || dest >= end)
2761 goto thd_atend; // just delete the end of the buffer
2762 if (memmove(dest, src, cnt) != dest) {
2763 psbs("can't delete the character");
2766 end = end - hole_size; // adjust the new END
2768 dest = end - 1; // make sure dest in below end-1
2770 dest = end = text; // keep pointers valid
2771 file_modified = TRUE; // has the file been modified
2776 // copy text into register, then delete text.
2777 // if dist <= 0, do not include, or go past, a NewLine
2779 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2783 // make sure start <= stop
2785 // they are backwards, reverse them
2791 // we can not cross NL boundaries
2795 // dont go past a NewLine
2796 for (; p + 1 <= stop; p++) {
2798 stop = p; // "stop" just before NewLine
2804 #ifdef BB_FEATURE_VI_YANKMARK
2805 text_yank(start, stop, YDreg);
2806 #endif /* BB_FEATURE_VI_YANKMARK */
2807 if (yf == YANKDEL) {
2808 p = text_hole_delete(start, stop);
2813 static void show_help(void)
2815 printf("These features are available:\n");
2816 #ifdef BB_FEATURE_VI_SEARCH
2817 printf("\tPattern searches with / and ?\n");
2818 #endif /* BB_FEATURE_VI_SEARCH */
2819 #ifdef BB_FEATURE_VI_DOT_CMD
2820 printf("\tLast command repeat with \'.\'\n");
2821 #endif /* BB_FEATURE_VI_DOT_CMD */
2822 #ifdef BB_FEATURE_VI_YANKMARK
2823 printf("\tLine marking with 'x\n");
2824 printf("\tNamed buffers with \"x\n");
2825 #endif /* BB_FEATURE_VI_YANKMARK */
2826 #ifdef BB_FEATURE_VI_READONLY
2827 printf("\tReadonly with -R command line arg\n");
2828 #endif /* BB_FEATURE_VI_READONLY */
2829 #ifdef BB_FEATURE_VI_SET
2830 printf("\tSome colon mode commands with \':\'\n");
2831 #endif /* BB_FEATURE_VI_SET */
2832 #ifdef BB_FEATURE_VI_SETOPTS
2833 printf("\tSettable options with \":set\"\n");
2834 #endif /* BB_FEATURE_VI_SETOPTS */
2835 #ifdef BB_FEATURE_VI_USE_SIGNALS
2836 printf("\tSignal catching- ^C\n");
2837 printf("\tJob suspend and resume with ^Z\n");
2838 #endif /* BB_FEATURE_VI_USE_SIGNALS */
2839 #ifdef BB_FEATURE_VI_WIN_RESIZE
2840 printf("\tAdapt to window re-sizes\n");
2841 #endif /* BB_FEATURE_VI_WIN_RESIZE */
2844 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2849 strcpy((char *) buf, ""); // init buf
2850 if (strlen((char *) s) <= 0)
2851 s = (Byte *) "(NULL)";
2852 for (; *s > '\0'; s++) {
2855 strcat((char *) buf, SOs);
2859 strcat((char *) buf, "^");
2863 strcat((char *) buf, (char *) b);
2865 strcat((char *) buf, SOn);
2867 strcat((char *) buf, "$");
2872 #ifdef BB_FEATURE_VI_DOT_CMD
2873 static void start_new_cmd_q(Byte c)
2876 if (last_modifying_cmd != 0)
2877 free(last_modifying_cmd);
2878 // get buffer for new cmd
2879 last_modifying_cmd = (Byte *) malloc(BUFSIZ);
2880 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2881 // if there is a current cmd count put it in the buffer first
2883 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2884 // save char c onto queue
2885 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2890 static void end_cmd_q()
2892 #ifdef BB_FEATURE_VI_YANKMARK
2893 YDreg = 26; // go back to default Yank/Delete reg
2894 #endif /* BB_FEATURE_VI_YANKMARK */
2898 #endif /* BB_FEATURE_VI_DOT_CMD */
2900 #if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
2901 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2905 i = strlen((char *) s);
2906 p = text_hole_make(p, i);
2907 strncpy((char *) p, (char *) s, i);
2908 for (cnt = 0; *s != '\0'; s++) {
2912 #ifdef BB_FEATURE_VI_YANKMARK
2913 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2914 #endif /* BB_FEATURE_VI_YANKMARK */
2917 #endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
2919 #ifdef BB_FEATURE_VI_YANKMARK
2920 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2925 if (q < p) { // they are backwards- reverse them
2932 if (t != 0) { // if already a yank register
2935 t = (Byte *) malloc(cnt + 1); // get a new register
2936 memset(t, '\0', cnt + 1); // clear new text[]
2937 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2942 static Byte what_reg(void)
2948 c = 'D'; // default to D-reg
2949 if (0 <= YDreg && YDreg <= 25)
2950 c = 'a' + (Byte) YDreg;
2958 static void check_context(Byte cmd)
2960 // A context is defined to be "modifying text"
2961 // Any modifying command establishes a new context.
2963 if (dot < context_start || dot > context_end) {
2964 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2965 // we are trying to modify text[]- make this the current context
2966 mark[27] = mark[26]; // move cur to prev
2967 mark[26] = dot; // move local to cur
2968 context_start = prev_line(prev_line(dot));
2969 context_end = next_line(next_line(dot));
2970 //loiter= start_loiter= now;
2976 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2980 // the current context is in mark[26]
2981 // the previous context is in mark[27]
2982 // only swap context if other context is valid
2983 if (text <= mark[27] && mark[27] <= end - 1) {
2985 mark[27] = mark[26];
2987 p = mark[26]; // where we are going- previous context
2988 context_start = prev_line(prev_line(prev_line(p)));
2989 context_end = next_line(next_line(next_line(p)));
2993 #endif /* BB_FEATURE_VI_YANKMARK */
2995 static int isblnk(Byte c) // is the char a blank or tab
2997 return (c == ' ' || c == '\t');
3000 //----- Set terminal attributes --------------------------------
3001 static void rawmode(void)
3003 tcgetattr(0, &term_orig);
3004 term_vi = term_orig;
3005 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
3006 term_vi.c_iflag &= (~IXON & ~ICRNL);
3007 term_vi.c_cc[VMIN] = 1;
3008 term_vi.c_cc[VTIME] = 0;
3009 erase_char = term_vi.c_cc[VERASE];
3010 tcsetattr(0, TCSANOW, &term_vi);
3013 static void cookmode(void)
3015 tcsetattr(0, TCSANOW, &term_orig);
3018 #ifdef BB_FEATURE_VI_WIN_RESIZE
3019 //----- See what the window size currently is --------------------
3020 static void window_size_get(int sig)
3024 i = ioctl(0, TIOCGWINSZ, &winsize);
3027 winsize.ws_row = 24;
3028 winsize.ws_col = 80;
3030 if (winsize.ws_row <= 1) {
3031 winsize.ws_row = 24;
3033 if (winsize.ws_col <= 1) {
3034 winsize.ws_col = 80;
3036 rows = (int) winsize.ws_row;
3037 columns = (int) winsize.ws_col;
3039 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3041 //----- Come here when we get a window resize signal ---------
3042 #ifdef BB_FEATURE_VI_USE_SIGNALS
3043 static void winch_sig(int sig)
3045 signal(SIGWINCH, winch_sig);
3046 #ifdef BB_FEATURE_VI_WIN_RESIZE
3048 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3049 new_screen(rows, columns); // get memory for virtual screen
3050 redraw(TRUE); // re-draw the screen
3053 //----- Come here when we get a continue signal -------------------
3054 static void cont_sig(int sig)
3056 rawmode(); // terminal to "raw"
3057 *status_buffer = '\0'; // clear the status buffer
3058 redraw(TRUE); // re-draw the screen
3060 signal(SIGTSTP, suspend_sig);
3061 signal(SIGCONT, SIG_DFL);
3062 kill(getpid(), SIGCONT);
3065 //----- Come here when we get a Suspend signal -------------------
3066 static void suspend_sig(int sig)
3068 place_cursor(rows, 0); // go to bottom of screen
3069 clear_to_eol(); // Erase to end of line
3070 cookmode(); // terminal to "cooked"
3072 signal(SIGCONT, cont_sig);
3073 signal(SIGTSTP, SIG_DFL);
3074 kill(getpid(), SIGTSTP);
3077 //----- Come here when we get a signal --------------------
3078 static void catch_sig(int sig)
3080 signal(SIGHUP, catch_sig);
3081 signal(SIGINT, catch_sig);
3082 signal(SIGTERM, catch_sig);
3083 longjmp(restart, sig);
3086 static void alarm_sig(int sig)
3088 signal(SIGALRM, catch_sig);
3089 longjmp(restart, sig);
3092 //----- Come here when we get a core dump signal -----------------
3093 static void core_sig(int sig)
3095 signal(SIGQUIT, core_sig);
3096 signal(SIGILL, core_sig);
3097 signal(SIGTRAP, core_sig);
3098 signal(SIGIOT, core_sig);
3099 signal(SIGABRT, core_sig);
3100 signal(SIGFPE, core_sig);
3101 signal(SIGBUS, core_sig);
3102 signal(SIGSEGV, core_sig);
3103 signal(SIGSYS, core_sig);
3105 dot = bound_dot(dot); // make sure "dot" is valid
3107 longjmp(restart, sig);
3109 #endif /* BB_FEATURE_VI_USE_SIGNALS */
3111 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3113 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3117 tv.tv_usec = hund * 10000;
3118 select(1, &rfds, NULL, NULL, &tv);
3119 return (FD_ISSET(0, &rfds));
3122 //----- IO Routines --------------------------------------------
3123 static Byte readit(void) // read (maybe cursor) key from stdin
3126 int i, bufsiz, cnt, cmdindex;
3132 static struct esc_cmds esccmds[] = {
3133 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3134 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3135 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3136 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3137 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3138 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3139 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3140 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3141 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3142 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3143 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3144 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3145 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3146 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3147 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3148 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3149 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3150 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3151 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3152 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3153 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3154 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3155 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3156 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3157 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3158 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3159 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3160 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3161 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3162 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3163 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3166 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3168 (void) alarm(0); // turn alarm OFF while we wait for input
3169 // get input from User- are there already input chars in Q?
3170 bufsiz = strlen((char *) readbuffer);
3173 // the Q is empty, wait for a typed char
3174 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3177 goto ri0; // interrupted sys call
3180 if (errno == EFAULT)
3182 if (errno == EINVAL)
3189 readbuffer[bufsiz] = '\0';
3191 // return char if it is not part of ESC sequence
3192 if (readbuffer[0] != 27)
3195 // This is an ESC char. Is this Esc sequence?
3196 // Could be bare Esc key. See if there are any
3197 // more chars to read after the ESC. This would
3198 // be a Function or Cursor Key sequence.
3202 tv.tv_usec = 10000; // Wait 1/100 seconds- 1 Sec=1000000
3204 // keep reading while there are input chars and room in buffer
3205 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3206 // read the rest of the ESC string
3207 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3210 readbuffer[bufsiz] = '\0'; // Terminate the string
3213 // Maybe cursor or function key?
3214 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3215 cnt = strlen((char *) esccmds[cmdindex].seq);
3216 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3218 // is a Cursor key- put derived value back into Q
3219 readbuffer[0] = esccmds[cmdindex].val;
3220 // squeeze out the ESC sequence
3221 for (i = 1; i < cnt; i++) {
3222 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3223 readbuffer[BUFSIZ - 1] = '\0';
3230 // remove one char from Q
3231 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3232 readbuffer[BUFSIZ - 1] = '\0';
3233 (void) alarm(3); // we are done waiting for input, turn alarm ON
3237 //----- IO Routines --------------------------------------------
3238 static Byte get_one_char()
3242 #ifdef BB_FEATURE_VI_DOT_CMD
3243 // ! adding2q && ioq == 0 read()
3244 // ! adding2q && ioq != 0 *ioq
3245 // adding2q *last_modifying_cmd= read()
3247 // we are not adding to the q.
3248 // but, we may be reading from a q
3250 // there is no current q, read from STDIN
3251 c = readit(); // get the users input
3253 // there is a queue to get chars from first
3256 // the end of the q, read from STDIN
3258 ioq_start = ioq = 0;
3259 c = readit(); // get the users input
3263 // adding STDIN chars to q
3264 c = readit(); // get the users input
3265 if (last_modifying_cmd != 0) {
3266 // add new char to q
3267 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3270 #else /* BB_FEATURE_VI_DOT_CMD */
3271 c = readit(); // get the users input
3272 #endif /* BB_FEATURE_VI_DOT_CMD */
3273 return (c); // return the char, where ever it came from
3276 #if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
3277 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3282 static Byte *obufp = NULL;
3284 strcpy((char *) buf, (char *) prompt);
3285 *status_buffer = '\0'; // clear the status buffer
3286 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
3287 clear_to_eol(); // clear the line
3288 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3290 for (i = strlen((char *) buf); i < 500;) {
3291 c = get_one_char(); // read user input
3292 if (c == '\n' || c == '\r')
3293 break; // is this end of input
3294 if (c == erase_char) { // user wants to erase prev char
3295 i--; // backup to prev char
3296 buf[i] = '\0'; // erase the char
3297 buf[i + 1] = '\0'; // null terminate buffer
3298 write(1, "
\b \b", 3); // erase char on screen
3299 if (i <= 0) { // user backs up before b-o-l, exit
3303 buf[i] = c; // save char in buffer
3304 buf[i + 1] = '\0'; // make sure buffer is null terminated
3305 write(1, buf + i, 1); // echo the char back to user
3312 obufp = (Byte *) strdup((char *) buf);
3315 #endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
3317 static int file_size(Byte * fn) // what is the byte size of "fn"
3325 sr = stat((char *) fn, &st_buf); // see if file exists
3327 cnt = (int) st_buf.st_size;
3332 static int file_insert(Byte * fn, Byte * p, int size)
3339 psbs("No filename given");
3342 sr = stat((char *) fn, &st_buf); // see if file exists
3344 psbs("\"%s\" %s", fn, "count not stat file");
3347 if (size <= 0 || (int) st_buf.st_size <= 0) {
3348 psbs("The file size (%d) is too small", size);
3349 if ((int) st_buf.st_size <= 0)
3350 psbs("\"%s\" is empty", fn);
3353 // There is a file with content
3354 fd = open((char *) fn, O_RDWR);
3356 psbs("\"%s\" %s", fn, "could not open file");
3359 p = text_hole_make(p, size);
3360 cnt = read(fd, p, size);
3364 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3365 psbs("could not read file \"%s\"", fn);
3366 } else if (cnt < size) {
3367 // There was a partial read, shrink unused space text[]
3368 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3369 psbs("could not read all of file \"%s\"", fn);
3372 file_modified = TRUE;
3377 static int file_write(Byte * fn, Byte * first, Byte * last)
3379 int fd, cnt, charcnt;
3382 psbs("No current filename");
3386 // FIXIT- use the correct umask()
3387 fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
3390 cnt = last - first + 1;
3391 charcnt = write(fd, first, cnt);
3392 if (charcnt == cnt) {
3394 //file_modified= FALSE; // the file has not been modified
3402 //----- Terminal Drawing ---------------------------------------
3403 // The terminal is made up of 'rows' line of 'columns' columns.
3404 // classicly this would be 24 x 80.
3405 // screen coordinates
3411 // 23,0 ... 23,79 status line
3414 //----- Move the cursor to row x col (count from 0, not 1) -------
3415 static void place_cursor(int row, int col)
3428 sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
3429 l = strlen((char *) buf);
3433 //----- Erase from cursor to end of line -----------------------
3434 static void clear_to_eol()
3436 write(1, "\033[0K", 4); // Erase from cursor to end of line
3439 //----- Erase from cursor to end of screen -----------------------
3440 static void clear_to_eos()
3442 write(1, "\033[0J", 4); // Erase from cursor to end of screen
3445 //----- Start standout mode ------------------------------------
3446 static void standout_start() // send "start reverse video" sequence
3448 write(1, "\033[7m", 4); // Start reverse video mode
3451 //----- End standout mode --------------------------------------
3452 static void standout_end() // send "end reverse video" sequence
3454 write(1, "\033[0m", 4); // End reverse video mode
3457 //----- Flash the screen --------------------------------------
3458 static void flash(int h)
3460 standout_start(); // send "start reverse video" sequence
3463 standout_end(); // send "end reverse video" sequence
3469 write(1, "\007", 1); // send out a bell character
3472 static void indicate_error(char c)
3474 #ifdef BB_FEATURE_VI_CRASHME
3476 return; // generate a random command
3477 #endif /* BB_FEATURE_VI_CRASHME */
3478 if (err_method == 0) {
3485 //----- Screen[] Routines --------------------------------------
3486 //----- Erase the Screen[] memory ------------------------------
3487 static void screen_erase()
3491 for (i = 0; i < screensize; i++) {
3496 //----- Draw the status line at bottom of the screen -------------
3497 static void show_status_line(void)
3501 cnt = strlen((char *) status_buffer);
3502 place_cursor(rows - 1, 0); // put cursor on status line
3504 write(1, status_buffer, cnt);
3507 place_cursor(crow, ccol); // put cursor back in correct place
3510 //----- format the status buffer, the bottom line of screen ------
3511 // print status buffer, with STANDOUT mode
3512 static void psbs(char *format, ...)
3516 va_start(args, format);
3517 strcpy((char *) status_buffer, "\033[7m"); // Terminal standout mode on
3518 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3520 strcat((char *) status_buffer, "\033[0m"); // Terminal standout mode off
3526 // print status buffer
3527 static void psb(char *format, ...)
3531 va_start(args, format);
3532 vsprintf((char *) status_buffer, format, args);
3537 static void ni(Byte * s) // display messages
3541 print_literal(buf, s);
3542 psbs("\'%s\' is not implemented", buf);
3545 static void edit_status(void) // show file status on status line
3547 int cur, tot, percent;
3549 cur = count_lines(text, dot);
3550 tot = count_lines(text, end - 1);
3551 // current line percent
3552 // ------------- ~~ ----------
3555 percent = (100 * cur) / tot;
3561 #ifdef BB_FEATURE_VI_READONLY
3563 #endif /* BB_FEATURE_VI_READONLY */
3564 "%s line %d of %d --%d%%-- (%dx%d)",
3565 (cfn != 0 ? (char *) cfn : "No file"),
3566 #ifdef BB_FEATURE_VI_READONLY
3567 (readonly == TRUE ? " [Read only]" : ""),
3568 #endif /* BB_FEATURE_VI_READONLY */
3569 (file_modified == TRUE ? " [modified]" : ""),
3570 cur, tot, percent, rows, columns);
3573 //----- Force refresh of all Lines -----------------------------
3574 static void redraw(int full_screen)
3576 place_cursor(0, 0); // put cursor in correct place
3577 clear_to_eos(); // tel terminal to erase display
3578 screen_erase(); // erase the internal screen buffer
3579 refresh(full_screen); // this will redraw the entire display
3582 //----- Refresh the changed screen lines -----------------------
3583 // Copy the source line from text[] into the buffer and note
3584 // if the current screenline is different from the new buffer.
3585 // If they differ then that line needs redrawing on the terminal.
3587 static void refresh(int full_screen)
3589 static int old_offset;
3590 int li, co, changed;
3591 Byte c, buf[MAX_SCR_COLS];
3592 Byte *tp, *sp; // pointer into text[] and screen[]
3594 #ifdef BB_FEATURE_VI_WIN_RESIZE
3596 #endif /* BB_FEATURE_VI_WIN_RESIZE */
3597 sync_cursor(dot, &crow, &ccol);
3598 tp = screenbegin; // index into text[] of top line
3599 // compare text[] to screen[] and mark screen[] lines that need updating
3600 for (li = 0; li < rows - 1; li++) {
3601 // format current text line into buf with "columns" wide
3602 for (co = 0; co < columns + offset;) {
3603 c = ' '; // assume blank
3604 if (li > 0 && co == 0) {
3605 c = '~'; // not first line, assume Tilde
3607 // are there chars in text[]
3608 // and have we gone past the end
3609 if (text < end && tp < end) {
3614 if (c < ' ' || c > '~') {
3618 for (; (co % tabstop) != (tabstop - 1); co++) {
3623 c |= '@'; // make it visible
3624 c &= 0x7f; // get rid of hi bit
3627 // the co++ is done here so that the column will
3628 // not be overwritten when we blank-out the rest of line
3633 if (co >= columns + offset) {
3634 // skip to the end of the current text[] line
3635 while (tp < end && *tp++ != '\n');
3637 // try to keep the cursor near it's current position
3638 // remember how many chars in this row- where the cursor sits
3639 // blank out the rest of the buffer
3640 while (co < MAX_SCR_COLS - 1) {
3643 buf[co++] = 0; // NULL terminate the buffer
3645 // if necessary, update virtual screen[] and terminal from buf[]
3646 changed = FALSE; // assume no change
3647 sp = &screen[li * columns]; // start of screen line
3648 for (co = 0; co < columns; co++) {
3649 if (sp[co] != buf[co + offset]) {
3650 sp[co] = buf[co + offset];
3651 changed = TRUE; // mark for redraw
3654 // if horz offset has changed, force a redraw
3655 if (offset != old_offset)
3658 // write all marked screen lines out to terminal
3659 if (changed == TRUE) {
3660 place_cursor(li, 0); // put cursor in correct place
3661 clear_to_eol(); // Erase to end of line
3662 if (full_screen == FALSE) {
3663 // don't redraw every column on terminal
3664 // look backwards for last non-blank
3665 for (co = columns + offset; co >= 0; co--) {
3672 // redraw every column on terminal
3675 // write line out to terminal
3676 write(1, buf + offset, co);
3680 place_cursor(crow, ccol);
3681 if (offset != old_offset)
3682 old_offset = offset;