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.
21 static const char vi_Version[] =
22 "$Id: vi.c,v 1.36 2004/04/14 17:51:09 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -s -DSTANDALONE -DCONFIG_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 implement
51 #define CONFIG_FEATURE_VI_COLON // 4288
52 #define CONFIG_FEATURE_VI_YANKMARK // 1408
53 #define CONFIG_FEATURE_VI_SEARCH // 1088
54 #define CONFIG_FEATURE_VI_USE_SIGNALS // 1056
55 #define CONFIG_FEATURE_VI_DOT_CMD // 576
56 #define CONFIG_FEATURE_VI_READONLY // 128
57 #define CONFIG_FEATURE_VI_SETOPTS // 576
58 #define CONFIG_FEATURE_VI_SET // 224
59 #define CONFIG_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 CONFIG_FEATURE_VI_CRASHME // randomly pick commands to execute
66 #endif /* STANDALONE */
73 #include <sys/ioctl.h>
75 #include <sys/types.h>
88 #endif /* STANDALONE */
90 #ifdef CONFIG_LOCALE_SUPPORT
91 #define Isprint(c) isprint((c))
93 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
98 #define FALSE ((int)0)
100 #define MAX_SCR_COLS BUFSIZ
102 // Misc. non-Ascii keys that report an escape sequence
103 #define VI_K_UP 128 // cursor key Up
104 #define VI_K_DOWN 129 // cursor key Down
105 #define VI_K_RIGHT 130 // Cursor Key Right
106 #define VI_K_LEFT 131 // cursor key Left
107 #define VI_K_HOME 132 // Cursor Key Home
108 #define VI_K_END 133 // Cursor Key End
109 #define VI_K_INSERT 134 // Cursor Key Insert
110 #define VI_K_PAGEUP 135 // Cursor Key Page Up
111 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
112 #define VI_K_FUN1 137 // Function Key F1
113 #define VI_K_FUN2 138 // Function Key F2
114 #define VI_K_FUN3 139 // Function Key F3
115 #define VI_K_FUN4 140 // Function Key F4
116 #define VI_K_FUN5 141 // Function Key F5
117 #define VI_K_FUN6 142 // Function Key F6
118 #define VI_K_FUN7 143 // Function Key F7
119 #define VI_K_FUN8 144 // Function Key F8
120 #define VI_K_FUN9 145 // Function Key F9
121 #define VI_K_FUN10 146 // Function Key F10
122 #define VI_K_FUN11 147 // Function Key F11
123 #define VI_K_FUN12 148 // Function Key F12
125 /* vt102 typical ESC sequence */
126 /* terminal standout start/normal ESC sequence */
127 static const char SOs[] = "\033[7m";
128 static const char SOn[] = "\033[0m";
129 /* terminal bell sequence */
130 static const char bell[] = "\007";
131 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
132 static const char Ceol[] = "\033[0K";
133 static const char Ceos [] = "\033[0J";
134 /* Cursor motion arbitrary destination ESC sequence */
135 static const char CMrc[] = "\033[%d;%dH";
136 /* Cursor motion up and down ESC sequence */
137 static const char CMup[] = "\033[A";
138 static const char CMdown[] = "\n";
141 static const int YANKONLY = FALSE;
142 static const int YANKDEL = TRUE;
143 static const int FORWARD = 1; // code depends on "1" for array index
144 static const int BACK = -1; // code depends on "-1" for array index
145 static const int LIMITED = 0; // how much of text[] in char_search
146 static const int FULL = 1; // how much of text[] in char_search
148 static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
149 static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
150 static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
151 static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
152 static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
154 typedef unsigned char Byte;
156 static int vi_setops;
157 #define VI_AUTOINDENT 1
158 #define VI_SHOWMATCH 2
159 #define VI_IGNORECASE 4
160 #define VI_ERR_METHOD 8
161 #define autoindent (vi_setops & VI_AUTOINDENT)
162 #define showmatch (vi_setops & VI_SHOWMATCH )
163 #define ignorecase (vi_setops & VI_IGNORECASE)
164 /* indicate error with beep or flash */
165 #define err_method (vi_setops & VI_ERR_METHOD)
168 static int editing; // >0 while we are editing a file
169 static int cmd_mode; // 0=command 1=insert
170 static int file_modified; // buffer contents changed
171 static int fn_start; // index of first cmd line file name
172 static int save_argc; // how many file names on cmd line
173 static int cmdcnt; // repetition count
174 static fd_set rfds; // use select() for small sleeps
175 static struct timeval tv; // use select() for small sleeps
176 static int rows, columns; // the terminal screen is this size
177 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
178 static Byte *status_buffer; // mesages to the user
179 static Byte *cfn; // previous, current, and next file name
180 static Byte *text, *end, *textend; // pointers to the user data in memory
181 static Byte *screen; // pointer to the virtual screen buffer
182 static int screensize; // and its size
183 static Byte *screenbegin; // index into text[], of top line on the screen
184 static Byte *dot; // where all the action takes place
186 static struct termios term_orig, term_vi; // remember what the cooked mode was
187 static Byte erase_char; // the users erase character
188 static Byte last_input_char; // last char read from user
189 static Byte last_forward_char; // last char searched for with 'f'
191 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
192 static int last_row; // where the cursor was last moved to
193 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
194 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
195 static jmp_buf restart; // catch_sig()
196 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
197 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
200 #ifdef CONFIG_FEATURE_VI_DOT_CMD
201 static int adding2q; // are we currently adding user input to q
202 static Byte *last_modifying_cmd; // last modifying cmd for "."
203 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
204 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
205 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
206 static Byte *modifying_cmds; // cmds that modify text[]
207 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
208 #ifdef CONFIG_FEATURE_VI_READONLY
209 static int vi_readonly, readonly;
210 #endif /* CONFIG_FEATURE_VI_READONLY */
211 #ifdef CONFIG_FEATURE_VI_YANKMARK
212 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
213 static int YDreg, Ureg; // default delete register and orig line for "U"
214 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
215 static Byte *context_start, *context_end;
216 #endif /* CONFIG_FEATURE_VI_YANKMARK */
217 #ifdef CONFIG_FEATURE_VI_SEARCH
218 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
219 #endif /* CONFIG_FEATURE_VI_SEARCH */
222 static void edit_file(Byte *); // edit one file
223 static void do_cmd(Byte); // execute a command
224 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
225 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
226 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
227 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
228 static Byte *next_line(Byte *); // return pointer to next line B-o-l
229 static Byte *end_screen(void); // get pointer to last char on screen
230 static int count_lines(Byte *, Byte *); // count line from start to stop
231 static Byte *find_line(int); // find begining of line #li
232 static Byte *move_to_col(Byte *, int); // move "p" to column l
233 static int isblnk(Byte); // is the char a blank or tab
234 static void dot_left(void); // move dot left- dont leave line
235 static void dot_right(void); // move dot right- dont leave line
236 static void dot_begin(void); // move dot to B-o-l
237 static void dot_end(void); // move dot to E-o-l
238 static void dot_next(void); // move dot to next line B-o-l
239 static void dot_prev(void); // move dot to prev line B-o-l
240 static void dot_scroll(int, int); // move the screen up or down
241 static void dot_skip_over_ws(void); // move dot pat WS
242 static void dot_delete(void); // delete the char at 'dot'
243 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
244 static Byte *new_screen(int, int); // malloc virtual screen memory
245 static Byte *new_text(int); // malloc memory for text[] buffer
246 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
247 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
248 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
249 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
250 static Byte *skip_thing(Byte *, int, int, int); // skip some object
251 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
252 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
253 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
254 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
255 static void show_help(void); // display some help info
256 static void rawmode(void); // set "raw" mode on tty
257 static void cookmode(void); // return to "cooked" mode on tty
258 static int mysleep(int); // sleep for 'h' 1/100 seconds
259 static Byte readit(void); // read (maybe cursor) key from stdin
260 static Byte get_one_char(void); // read 1 char from stdin
261 static int file_size(const Byte *); // what is the byte size of "fn"
262 static int file_insert(Byte *, Byte *, int);
263 static int file_write(Byte *, Byte *, Byte *);
264 static void place_cursor(int, int, int);
265 static void screen_erase(void);
266 static void clear_to_eol(void);
267 static void clear_to_eos(void);
268 static void standout_start(void); // send "start reverse video" sequence
269 static void standout_end(void); // send "end reverse video" sequence
270 static void flash(int); // flash the terminal screen
271 static void show_status_line(void); // put a message on the bottom line
272 static void psb(const char *, ...); // Print Status Buf
273 static void psbs(const char *, ...); // Print Status Buf in standout mode
274 static void ni(Byte *); // display messages
275 static void edit_status(void); // show file status on status line
276 static void redraw(int); // force a full screen refresh
277 static void format_line(Byte*, Byte*, int);
278 static void refresh(int); // update the terminal from screen[]
280 static void Indicate_Error(void); // use flash or beep to indicate error
281 #define indicate_error(c) Indicate_Error()
283 #ifdef CONFIG_FEATURE_VI_SEARCH
284 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
285 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
286 #endif /* CONFIG_FEATURE_VI_SEARCH */
287 #ifdef CONFIG_FEATURE_VI_COLON
288 static void Hit_Return(void);
289 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
290 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
291 static void colon(Byte *); // execute the "colon" mode cmds
292 #endif /* CONFIG_FEATURE_VI_COLON */
293 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
294 static void winch_sig(int); // catch window size changes
295 static void suspend_sig(int); // catch ctrl-Z
296 static void catch_sig(int); // catch ctrl-C and alarm time-outs
297 static void core_sig(int); // catch a core dump signal
298 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
299 #ifdef CONFIG_FEATURE_VI_DOT_CMD
300 static void start_new_cmd_q(Byte); // new queue for command
301 static void end_cmd_q(void); // stop saving input chars
302 #else /* CONFIG_FEATURE_VI_DOT_CMD */
304 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
305 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
306 static void window_size_get(int); // find out what size the window is
307 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
308 #ifdef CONFIG_FEATURE_VI_SETOPTS
309 static void showmatching(Byte *); // show the matching pair () [] {}
310 #endif /* CONFIG_FEATURE_VI_SETOPTS */
311 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
312 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
313 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
314 #ifdef CONFIG_FEATURE_VI_YANKMARK
315 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
316 static Byte what_reg(void); // what is letter of current YDreg
317 static void check_context(Byte); // remember context for '' command
318 #endif /* CONFIG_FEATURE_VI_YANKMARK */
319 #ifdef CONFIG_FEATURE_VI_CRASHME
320 static void crash_dummy();
321 static void crash_test();
322 static int crashme = 0;
323 #endif /* CONFIG_FEATURE_VI_CRASHME */
326 static void write1(const char *out)
331 extern int vi_main(int argc, char **argv)
334 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, 200);
336 #ifdef CONFIG_FEATURE_VI_YANKMARK
338 #endif /* CONFIG_FEATURE_VI_YANKMARK */
339 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
342 #ifdef CONFIG_FEATURE_VI_CRASHME
343 (void) srand((long) my_pid);
344 #endif /* CONFIG_FEATURE_VI_CRASHME */
346 status_buffer = STATUS_BUFFER;
348 #ifdef CONFIG_FEATURE_VI_READONLY
349 vi_readonly = readonly = FALSE;
350 if (strncmp(argv[0], "view", 4) == 0) {
354 #endif /* CONFIG_FEATURE_VI_READONLY */
355 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
356 #ifdef CONFIG_FEATURE_VI_YANKMARK
357 for (i = 0; i < 28; i++) {
359 } // init the yank regs
360 #endif /* CONFIG_FEATURE_VI_YANKMARK */
361 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
362 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
363 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
365 // 1- process $HOME/.exrc file
366 // 2- process EXINIT variable from environment
367 // 3- process command line args
368 while ((c = getopt(argc, argv, "hCR")) != -1) {
370 #ifdef CONFIG_FEATURE_VI_CRASHME
374 #endif /* CONFIG_FEATURE_VI_CRASHME */
375 #ifdef CONFIG_FEATURE_VI_READONLY
376 case 'R': // Read-only flag
380 #endif /* CONFIG_FEATURE_VI_READONLY */
381 //case 'r': // recover flag- ignore- we don't use tmp file
382 //case 'x': // encryption flag- ignore
383 //case 'c': // execute command first
384 //case 'h': // help -- just use default
391 // The argv array can be used by the ":next" and ":rewind" commands
393 fn_start = optind; // remember first file name for :next and :rew
396 //----- This is the main file handling loop --------------
397 if (optind >= argc) {
398 editing = 1; // 0= exit, 1= one file, 2= multiple files
401 for (; optind < argc; optind++) {
402 editing = 1; // 0=exit, 1=one file, 2+ =many files
404 cfn = (Byte *) bb_xstrdup(argv[optind]);
408 //-----------------------------------------------------------
413 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
414 //----- See what the window size currently is --------------------
415 static inline void window_size_get(int fd)
417 get_terminal_width_height(fd, &columns, &rows);
419 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
421 static void edit_file(Byte * fn)
426 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
428 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
429 #ifdef CONFIG_FEATURE_VI_YANKMARK
430 static Byte *cur_line;
431 #endif /* CONFIG_FEATURE_VI_YANKMARK */
437 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
439 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
440 new_screen(rows, columns); // get memory for virtual screen
442 cnt = file_size(fn); // file size
443 size = 2 * cnt; // 200% of file size
444 new_text(size); // get a text[] buffer
445 screenbegin = dot = end = text;
447 ch= file_insert(fn, text, cnt);
450 (void) char_insert(text, '\n'); // start empty buf with dummy line
452 file_modified = FALSE;
453 #ifdef CONFIG_FEATURE_VI_YANKMARK
454 YDreg = 26; // default Yank/Delete reg
455 Ureg = 27; // hold orig line for "U" cmd
456 for (cnt = 0; cnt < 28; cnt++) {
459 mark[26] = mark[27] = text; // init "previous context"
460 #endif /* CONFIG_FEATURE_VI_YANKMARK */
462 last_forward_char = last_input_char = '\0';
467 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
470 signal(SIGWINCH, winch_sig);
471 signal(SIGTSTP, suspend_sig);
472 sig = setjmp(restart);
474 const char *msg = "";
477 msg = "(window resize)";
487 msg = "(I tried to touch invalid memory)";
491 psbs("-- caught signal %d %s--", sig, msg);
492 screenbegin = dot = text;
494 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
497 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
500 offset = 0; // no horizontal offset
502 #ifdef CONFIG_FEATURE_VI_DOT_CMD
503 free(last_modifying_cmd);
505 ioq = ioq_start = last_modifying_cmd = 0;
507 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
508 redraw(FALSE); // dont force every col re-draw
512 //------This is the main Vi cmd handling loop -----------------------
513 while (editing > 0) {
514 #ifdef CONFIG_FEATURE_VI_CRASHME
516 if ((end - text) > 1) {
517 crash_dummy(); // generate a random command
521 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
525 #endif /* CONFIG_FEATURE_VI_CRASHME */
526 last_input_char = c = get_one_char(); // get a cmd from user
527 #ifdef CONFIG_FEATURE_VI_YANKMARK
528 // save a copy of the current line- for the 'U" command
529 if (begin_line(dot) != cur_line) {
530 cur_line = begin_line(dot);
531 text_yank(begin_line(dot), end_line(dot), Ureg);
533 #endif /* CONFIG_FEATURE_VI_YANKMARK */
534 #ifdef CONFIG_FEATURE_VI_DOT_CMD
535 // These are commands that change text[].
536 // Remember the input for the "." command
537 if (!adding2q && ioq_start == 0
538 && strchr((char *) modifying_cmds, c) != NULL) {
541 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
542 do_cmd(c); // execute the user command
544 // poll to see if there is input already waiting. if we are
545 // not able to display output fast enough to keep up, skip
546 // the display update until we catch up with input.
547 if (mysleep(0) == 0) {
548 // no input pending- so update output
552 #ifdef CONFIG_FEATURE_VI_CRASHME
554 crash_test(); // test editor variables
555 #endif /* CONFIG_FEATURE_VI_CRASHME */
557 //-------------------------------------------------------------------
559 place_cursor(rows, 0, FALSE); // go to bottom of screen
560 clear_to_eol(); // Erase to end of line
564 //----- The Colon commands -------------------------------------
565 #ifdef CONFIG_FEATURE_VI_COLON
566 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
571 #ifdef CONFIG_FEATURE_VI_YANKMARK
573 #endif /* CONFIG_FEATURE_VI_YANKMARK */
574 #ifdef CONFIG_FEATURE_VI_SEARCH
575 Byte *pat, buf[BUFSIZ];
576 #endif /* CONFIG_FEATURE_VI_SEARCH */
578 *addr = -1; // assume no addr
579 if (*p == '.') { // the current line
582 *addr = count_lines(text, q);
583 #ifdef CONFIG_FEATURE_VI_YANKMARK
584 } else if (*p == '\'') { // is this a mark addr
588 if (c >= 'a' && c <= 'z') {
592 if (q != NULL) { // is mark valid
593 *addr = count_lines(text, q); // count lines
596 #endif /* CONFIG_FEATURE_VI_YANKMARK */
597 #ifdef CONFIG_FEATURE_VI_SEARCH
598 } else if (*p == '/') { // a search pattern
606 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
609 q = char_search(dot, pat, FORWARD, FULL);
611 *addr = count_lines(text, q);
614 #endif /* CONFIG_FEATURE_VI_SEARCH */
615 } else if (*p == '$') { // the last line in file
617 q = begin_line(end - 1);
618 *addr = count_lines(text, q);
619 } else if (isdigit(*p)) { // specific line number
620 sscanf((char *) p, "%d%n", addr, &st);
622 } else { // I don't reconise this
623 // unrecognised address- assume -1
629 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
631 //----- get the address' i.e., 1,3 'a,'b -----
632 // get FIRST addr, if present
634 p++; // skip over leading spaces
635 if (*p == '%') { // alias for 1,$
638 *e = count_lines(text, end-1);
641 p = get_one_address(p, b);
644 if (*p == ',') { // is there a address separator
648 // get SECOND addr, if present
649 p = get_one_address(p, e);
653 p++; // skip over trailing spaces
657 #ifdef CONFIG_FEATURE_VI_SETOPTS
658 static void setops(const Byte *args, const char *opname, int flg_no,
659 const char *short_opname, int opt)
661 const char *a = (char *) args + flg_no;
662 int l = strlen(opname) - 1; /* opname have + ' ' */
664 if (strncasecmp(a, opname, l) == 0 ||
665 strncasecmp(a, short_opname, 2) == 0) {
674 static void colon(Byte * buf)
676 Byte c, *orig_buf, *buf1, *q, *r;
677 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
678 int i, l, li, ch, st, b, e;
679 int useforce, forced;
682 // :3154 // if (-e line 3154) goto it else stay put
683 // :4,33w! foo // write a portion of buffer to file "foo"
684 // :w // write all of buffer to current file
686 // :q! // quit- dont care about modified file
687 // :'a,'z!sort -u // filter block through sort
688 // :'f // goto mark "f"
689 // :'fl // list literal the mark "f" line
690 // :.r bar // read file "bar" into buffer before dot
691 // :/123/,/abc/d // delete lines from "123" line to "abc" line
692 // :/xyz/ // goto the "xyz" line
693 // :s/find/replace/ // substitute pattern "find" with "replace"
694 // :!<cmd> // run <cmd> then return
696 if (strlen((char *) buf) <= 0)
699 buf++; // move past the ':'
701 forced = useforce = FALSE;
702 li = st = ch = i = 0;
704 q = text; // assume 1,$ for the range
706 li = count_lines(text, end - 1);
707 fn = cfn; // default to current file
708 memset(cmd, '\0', BUFSIZ); // clear cmd[]
709 memset(args, '\0', BUFSIZ); // clear args[]
711 // look for optional address(es) :. :1 :1,9 :'q,'a :%
712 buf = get_address(buf, &b, &e);
714 // remember orig command line
717 // get the COMMAND into cmd[]
719 while (*buf != '\0') {
727 strcpy((char *) args, (char *) buf);
728 buf1 = last_char_is((char *)cmd, '!');
731 *buf1 = '\0'; // get rid of !
734 // if there is only one addr, then the addr
735 // is the line number of the single line the
736 // user wants. So, reset the end
737 // pointer to point at end of the "b" line
738 q = find_line(b); // what line is #b
743 // we were given two addrs. change the
744 // end pointer to the addr given by user.
745 r = find_line(e); // what line is #e
749 // ------------ now look for the command ------------
750 i = strlen((char *) cmd);
751 if (i == 0) { // :123CR goto line #123
753 dot = find_line(b); // what line is #b
756 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
757 // :!ls run the <cmd>
758 (void) alarm(0); // wait for input- no alarms
759 place_cursor(rows - 1, 0, FALSE); // go to Status line
760 clear_to_eol(); // clear the line
762 system(orig_buf+1); // run the cmd
764 Hit_Return(); // let user see results
765 (void) alarm(3); // done waiting for input
766 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
767 if (b < 0) { // no addr given- use defaults
768 b = e = count_lines(text, dot);
771 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
772 if (b < 0) { // no addr given- use defaults
773 q = begin_line(dot); // assume .,. for the range
776 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
778 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
781 // don't edit, if the current file has been modified
782 if (file_modified && ! useforce) {
783 psbs("No write since last change (:edit! overrides)");
786 if (strlen(args) > 0) {
787 // the user supplied a file name
789 } else if (cfn != 0 && strlen(cfn) > 0) {
790 // no user supplied name- use the current filename
794 // no user file name, no current name- punt
795 psbs("No current filename");
799 // see if file exists- if not, its just a new file request
800 if ((sr=stat((char*)fn, &st_buf)) < 0) {
801 // This is just a request for a new file creation.
802 // The file_insert below will fail but we get
803 // an empty buffer with a file name. Then the "write"
804 // command can do the create.
806 if ((st_buf.st_mode & (S_IFREG)) == 0) {
807 // This is not a regular file
808 psbs("\"%s\" is not a regular file", fn);
811 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
812 // dont have any read permissions
813 psbs("\"%s\" is not readable", fn);
818 // There is a read-able regular file
819 // make this the current file
820 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
821 free(cfn); // free the old name
822 cfn = q; // remember new cfn
825 // delete all the contents of text[]
826 new_text(2 * file_size(fn));
827 screenbegin = dot = end = text;
830 ch = file_insert(fn, text, file_size(fn));
833 // start empty buf with dummy line
834 (void) char_insert(text, '\n');
837 file_modified = FALSE;
838 #ifdef CONFIG_FEATURE_VI_YANKMARK
839 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
840 free(reg[Ureg]); // free orig line reg- for 'U'
843 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
844 free(reg[YDreg]); // free default yank/delete register
847 for (li = 0; li < 28; li++) {
850 #endif /* CONFIG_FEATURE_VI_YANKMARK */
851 // how many lines in text[]?
852 li = count_lines(text, end - 1);
854 #ifdef CONFIG_FEATURE_VI_READONLY
856 #endif /* CONFIG_FEATURE_VI_READONLY */
858 (sr < 0 ? " [New file]" : ""),
859 #ifdef CONFIG_FEATURE_VI_READONLY
860 ((vi_readonly || readonly) ? " [Read only]" : ""),
861 #endif /* CONFIG_FEATURE_VI_READONLY */
863 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
864 if (b != -1 || e != -1) {
865 ni((Byte *) "No address allowed on this command");
868 if (strlen((char *) args) > 0) {
869 // user wants a new filename
871 cfn = (Byte *) bb_xstrdup((char *) args);
873 // user wants file status info
876 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
877 // print out values of all features
878 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
879 clear_to_eol(); // clear the line
884 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
885 if (b < 0) { // no addr given- use defaults
886 q = begin_line(dot); // assume .,. for the range
889 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
890 clear_to_eol(); // clear the line
892 for (; q <= r; q++) {
896 c_is_no_print = c > 127 && !Isprint(c);
903 } else if (c < ' ' || c == 127) {
914 #ifdef CONFIG_FEATURE_VI_SET
916 #endif /* CONFIG_FEATURE_VI_SET */
918 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
919 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
921 // force end of argv list
928 // don't exit if the file been modified
930 psbs("No write since last change (:%s! overrides)",
931 (*cmd == 'q' ? "quit" : "next"));
934 // are there other file to edit
935 if (*cmd == 'q' && optind < save_argc - 1) {
936 psbs("%d more file to edit", (save_argc - optind - 1));
939 if (*cmd == 'n' && optind >= save_argc - 1) {
940 psbs("No more files to edit");
944 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
946 if (strlen((char *) fn) <= 0) {
947 psbs("No filename given");
950 if (b < 0) { // no addr given- use defaults
951 q = begin_line(dot); // assume "dot"
953 // read after current line- unless user said ":0r foo"
956 #ifdef CONFIG_FEATURE_VI_READONLY
957 l= readonly; // remember current files' status
959 ch = file_insert(fn, q, file_size(fn));
960 #ifdef CONFIG_FEATURE_VI_READONLY
964 goto vc1; // nothing was inserted
965 // how many lines in text[]?
966 li = count_lines(q, q + ch - 1);
968 #ifdef CONFIG_FEATURE_VI_READONLY
970 #endif /* CONFIG_FEATURE_VI_READONLY */
972 #ifdef CONFIG_FEATURE_VI_READONLY
973 ((vi_readonly || readonly) ? " [Read only]" : ""),
974 #endif /* CONFIG_FEATURE_VI_READONLY */
977 // if the insert is before "dot" then we need to update
980 file_modified = TRUE;
982 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
983 if (file_modified && ! useforce) {
984 psbs("No write since last change (:rewind! overrides)");
986 // reset the filenames to edit
987 optind = fn_start - 1;
990 #ifdef CONFIG_FEATURE_VI_SET
991 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
992 i = 0; // offset into args
993 if (strlen((char *) args) == 0) {
994 // print out values of all options
995 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
996 clear_to_eol(); // clear the line
997 printf("----------------------------------------\r\n");
998 #ifdef CONFIG_FEATURE_VI_SETOPTS
1001 printf("autoindent ");
1007 printf("ignorecase ");
1010 printf("showmatch ");
1011 printf("tabstop=%d ", tabstop);
1012 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1016 if (strncasecmp((char *) args, "no", 2) == 0)
1017 i = 2; // ":set noautoindent"
1018 #ifdef CONFIG_FEATURE_VI_SETOPTS
1019 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1020 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1021 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1022 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1023 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1024 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1025 if (ch > 0 && ch < columns - 1)
1028 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1029 #endif /* CONFIG_FEATURE_VI_SET */
1030 #ifdef CONFIG_FEATURE_VI_SEARCH
1031 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1035 // F points to the "find" pattern
1036 // R points to the "replace" pattern
1037 // replace the cmd line delimiters "/" with NULLs
1038 gflag = 0; // global replace flag
1039 c = orig_buf[1]; // what is the delimiter
1040 F = orig_buf + 2; // start of "find"
1041 R = (Byte *) strchr((char *) F, c); // middle delimiter
1042 if (!R) goto colon_s_fail;
1043 *R++ = '\0'; // terminate "find"
1044 buf1 = (Byte *) strchr((char *) R, c);
1045 if (!buf1) goto colon_s_fail;
1046 *buf1++ = '\0'; // terminate "replace"
1047 if (*buf1 == 'g') { // :s/foo/bar/g
1049 gflag++; // turn on gflag
1052 if (b < 0) { // maybe :s/foo/bar/
1053 q = begin_line(dot); // start with cur line
1054 b = count_lines(text, q); // cur line number
1057 e = b; // maybe :.s/foo/bar/
1058 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1059 ls = q; // orig line start
1061 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1063 // we found the "find" pattern- delete it
1064 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1065 // inset the "replace" patern
1066 (void) string_insert(buf1, R); // insert the string
1067 // check for "global" :s/foo/bar/g
1069 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1070 q = buf1 + strlen((char *) R);
1071 goto vc4; // don't let q move past cur line
1077 #endif /* CONFIG_FEATURE_VI_SEARCH */
1078 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1079 psb("%s", vi_Version);
1080 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1081 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1082 (strncasecmp((char *) cmd, "x", i) == 0)) {
1083 // is there a file name to write to?
1084 if (strlen((char *) args) > 0) {
1087 #ifdef CONFIG_FEATURE_VI_READONLY
1088 if ((vi_readonly || readonly) && ! useforce) {
1089 psbs("\"%s\" File is read only", fn);
1092 #endif /* CONFIG_FEATURE_VI_READONLY */
1093 // how many lines in text[]?
1094 li = count_lines(q, r);
1096 // see if file exists- if not, its just a new file request
1098 // if "fn" is not write-able, chmod u+w
1099 // sprintf(syscmd, "chmod u+w %s", fn);
1103 l = file_write(fn, q, r);
1104 if (useforce && forced) {
1106 // sprintf(syscmd, "chmod u-w %s", fn);
1110 psb("\"%s\" %dL, %dC", fn, li, l);
1111 if (q == text && r == end - 1 && l == ch)
1112 file_modified = FALSE;
1113 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1116 #ifdef CONFIG_FEATURE_VI_READONLY
1118 #endif /* CONFIG_FEATURE_VI_READONLY */
1119 #ifdef CONFIG_FEATURE_VI_YANKMARK
1120 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1121 if (b < 0) { // no addr given- use defaults
1122 q = begin_line(dot); // assume .,. for the range
1125 text_yank(q, r, YDreg);
1126 li = count_lines(q, r);
1127 psb("Yank %d lines (%d chars) into [%c]",
1128 li, strlen((char *) reg[YDreg]), what_reg());
1129 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1135 dot = bound_dot(dot); // make sure "dot" is valid
1137 #ifdef CONFIG_FEATURE_VI_SEARCH
1139 psb(":s expression missing delimiters");
1143 static void Hit_Return(void)
1147 standout_start(); // start reverse video
1148 write1("[Hit return to continue]");
1149 standout_end(); // end reverse video
1150 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1152 redraw(TRUE); // force redraw all
1154 #endif /* CONFIG_FEATURE_VI_COLON */
1156 //----- Synchronize the cursor to Dot --------------------------
1157 static void sync_cursor(Byte * d, int *row, int *col)
1159 Byte *beg_cur, *end_cur; // begin and end of "d" line
1160 Byte *beg_scr, *end_scr; // begin and end of screen
1164 beg_cur = begin_line(d); // first char of cur line
1165 end_cur = end_line(d); // last char of cur line
1167 beg_scr = end_scr = screenbegin; // first char of screen
1168 end_scr = end_screen(); // last char of screen
1170 if (beg_cur < screenbegin) {
1171 // "d" is before top line on screen
1172 // how many lines do we have to move
1173 cnt = count_lines(beg_cur, screenbegin);
1175 screenbegin = beg_cur;
1176 if (cnt > (rows - 1) / 2) {
1177 // we moved too many lines. put "dot" in middle of screen
1178 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1179 screenbegin = prev_line(screenbegin);
1182 } else if (beg_cur > end_scr) {
1183 // "d" is after bottom line on screen
1184 // how many lines do we have to move
1185 cnt = count_lines(end_scr, beg_cur);
1186 if (cnt > (rows - 1) / 2)
1187 goto sc1; // too many lines
1188 for (ro = 0; ro < cnt - 1; ro++) {
1189 // move screen begin the same amount
1190 screenbegin = next_line(screenbegin);
1191 // now, move the end of screen
1192 end_scr = next_line(end_scr);
1193 end_scr = end_line(end_scr);
1196 // "d" is on screen- find out which row
1198 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1204 // find out what col "d" is on
1206 do { // drive "co" to correct column
1207 if (*tp == '\n' || *tp == '\0')
1211 co += ((tabstop - 1) - (co % tabstop));
1212 } else if (*tp < ' ' || *tp == 127) {
1213 co++; // display as ^X, use 2 columns
1215 } while (tp++ < d && ++co);
1217 // "co" is the column where "dot" is.
1218 // The screen has "columns" columns.
1219 // The currently displayed columns are 0+offset -- columns+ofset
1220 // |-------------------------------------------------------------|
1222 // offset | |------- columns ----------------|
1224 // If "co" is already in this range then we do not have to adjust offset
1225 // but, we do have to subtract the "offset" bias from "co".
1226 // If "co" is outside this range then we have to change "offset".
1227 // If the first char of a line is a tab the cursor will try to stay
1228 // in column 7, but we have to set offset to 0.
1230 if (co < 0 + offset) {
1233 if (co >= columns + offset) {
1234 offset = co - columns + 1;
1236 // if the first char of the line is a tab, and "dot" is sitting on it
1237 // force offset to 0.
1238 if (d == beg_cur && *d == '\t') {
1247 //----- Text Movement Routines ---------------------------------
1248 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1250 while (p > text && p[-1] != '\n')
1251 p--; // go to cur line B-o-l
1255 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1257 while (p < end - 1 && *p != '\n')
1258 p++; // go to cur line E-o-l
1262 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1264 while (p < end - 1 && *p != '\n')
1265 p++; // go to cur line E-o-l
1266 // Try to stay off of the Newline
1267 if (*p == '\n' && (p - begin_line(p)) > 0)
1272 static Byte *prev_line(Byte * p) // return pointer first char prev line
1274 p = begin_line(p); // goto begining of cur line
1275 if (p[-1] == '\n' && p > text)
1276 p--; // step to prev line
1277 p = begin_line(p); // goto begining of prev line
1281 static Byte *next_line(Byte * p) // return pointer first char next line
1284 if (*p == '\n' && p < end - 1)
1285 p++; // step to next line
1289 //----- Text Information Routines ------------------------------
1290 static Byte *end_screen(void)
1295 // find new bottom line
1297 for (cnt = 0; cnt < rows - 2; cnt++)
1303 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1308 if (stop < start) { // start and stop are backwards- reverse them
1314 stop = end_line(stop); // get to end of this line
1315 for (q = start; q <= stop && q <= end - 1; q++) {
1322 static Byte *find_line(int li) // find begining of line #li
1326 for (q = text; li > 1; li--) {
1332 //----- Dot Movement Routines ----------------------------------
1333 static void dot_left(void)
1335 if (dot > text && dot[-1] != '\n')
1339 static void dot_right(void)
1341 if (dot < end - 1 && *dot != '\n')
1345 static void dot_begin(void)
1347 dot = begin_line(dot); // return pointer to first char cur line
1350 static void dot_end(void)
1352 dot = end_line(dot); // return pointer to last char cur line
1355 static Byte *move_to_col(Byte * p, int l)
1362 if (*p == '\n' || *p == '\0')
1366 co += ((tabstop - 1) - (co % tabstop));
1367 } else if (*p < ' ' || *p == 127) {
1368 co++; // display as ^X, use 2 columns
1370 } while (++co <= l && p++ < end);
1374 static void dot_next(void)
1376 dot = next_line(dot);
1379 static void dot_prev(void)
1381 dot = prev_line(dot);
1384 static void dot_scroll(int cnt, int dir)
1388 for (; cnt > 0; cnt--) {
1391 // ctrl-Y scroll up one line
1392 screenbegin = prev_line(screenbegin);
1395 // ctrl-E scroll down one line
1396 screenbegin = next_line(screenbegin);
1399 // make sure "dot" stays on the screen so we dont scroll off
1400 if (dot < screenbegin)
1402 q = end_screen(); // find new bottom line
1404 dot = begin_line(q); // is dot is below bottom line?
1408 static void dot_skip_over_ws(void)
1411 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1415 static void dot_delete(void) // delete the char at 'dot'
1417 (void) text_hole_delete(dot, dot);
1420 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1422 if (p >= end && end > text) {
1424 indicate_error('1');
1428 indicate_error('2');
1433 //----- Helper Utility Routines --------------------------------
1435 //----------------------------------------------------------------
1436 //----- Char Routines --------------------------------------------
1437 /* Chars that are part of a word-
1438 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1439 * Chars that are Not part of a word (stoppers)
1440 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1441 * Chars that are WhiteSpace
1442 * TAB NEWLINE VT FF RETURN SPACE
1443 * DO NOT COUNT NEWLINE AS WHITESPACE
1446 static Byte *new_screen(int ro, int co)
1451 screensize = ro * co + 8;
1452 screen = (Byte *) xmalloc(screensize);
1453 // initialize the new screen. assume this will be a empty file.
1455 // non-existent text[] lines start with a tilde (~).
1456 for (li = 1; li < ro - 1; li++) {
1457 screen[(li * co) + 0] = '~';
1462 static Byte *new_text(int size)
1465 size = 10240; // have a minimum size for new files
1467 text = (Byte *) xmalloc(size + 8);
1468 memset(text, '\0', size); // clear new text[]
1469 //text += 4; // leave some room for "oops"
1470 textend = text + size - 1;
1471 //textend -= 4; // leave some root for "oops"
1475 #ifdef CONFIG_FEATURE_VI_SEARCH
1476 static int mycmp(Byte * s1, Byte * s2, int len)
1480 i = strncmp((char *) s1, (char *) s2, len);
1481 #ifdef CONFIG_FEATURE_VI_SETOPTS
1483 i = strncasecmp((char *) s1, (char *) s2, len);
1485 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1489 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1491 #ifndef REGEX_SEARCH
1495 len = strlen((char *) pat);
1496 if (dir == FORWARD) {
1497 stop = end - 1; // assume range is p - end-1
1498 if (range == LIMITED)
1499 stop = next_line(p); // range is to next line
1500 for (start = p; start < stop; start++) {
1501 if (mycmp(start, pat, len) == 0) {
1505 } else if (dir == BACK) {
1506 stop = text; // assume range is text - p
1507 if (range == LIMITED)
1508 stop = prev_line(p); // range is to prev line
1509 for (start = p - len; start >= stop; start--) {
1510 if (mycmp(start, pat, len) == 0) {
1515 // pattern not found
1517 #else /*REGEX_SEARCH */
1519 struct re_pattern_buffer preg;
1523 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1529 // assume a LIMITED forward search
1537 // count the number of chars to search over, forward or backward
1541 // RANGE could be negative if we are searching backwards
1544 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1546 // The pattern was not compiled
1547 psbs("bad search pattern: \"%s\": %s", pat, q);
1548 i = 0; // return p if pattern not compiled
1558 // search for the compiled pattern, preg, in p[]
1559 // range < 0- search backward
1560 // range > 0- search forward
1562 // re_search() < 0 not found or error
1563 // re_search() > 0 index of found pattern
1564 // struct pattern char int int int struct reg
1565 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1566 i = re_search(&preg, q, size, 0, range, 0);
1569 i = 0; // return NULL if pattern not found
1572 if (dir == FORWARD) {
1578 #endif /*REGEX_SEARCH */
1580 #endif /* CONFIG_FEATURE_VI_SEARCH */
1582 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1584 if (c == 22) { // Is this an ctrl-V?
1585 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1586 p--; // backup onto ^
1587 refresh(FALSE); // show the ^
1591 file_modified = TRUE; // has the file been modified
1592 } else if (c == 27) { // Is this an ESC?
1595 end_cmd_q(); // stop adding to q
1596 strcpy((char *) status_buffer, " "); // clear the status buffer
1597 if ((p[-1] != '\n') && (dot>text)) {
1600 } else if (c == erase_char) { // Is this a BS
1602 if ((p[-1] != '\n') && (dot>text)) {
1604 p = text_hole_delete(p, p); // shrink buffer 1 char
1605 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1606 // also rmove char from last_modifying_cmd
1607 if (last_modifying_cmd != 0 && strlen((char *) last_modifying_cmd) > 0) {
1610 q = last_modifying_cmd;
1611 q[strlen((char *) q) - 1] = '\0'; // erase BS
1612 q[strlen((char *) q) - 1] = '\0'; // erase prev char
1614 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1617 // insert a char into text[]
1618 Byte *sp; // "save p"
1621 c = '\n'; // translate \r to \n
1622 sp = p; // remember addr of insert
1623 p = stupid_insert(p, c); // insert the char
1624 #ifdef CONFIG_FEATURE_VI_SETOPTS
1625 if (showmatch && strchr(")]}", *sp) != NULL) {
1628 if (autoindent && c == '\n') { // auto indent the new line
1631 q = prev_line(p); // use prev line as templet
1632 for (; isblnk(*q); q++) {
1633 p = stupid_insert(p, *q); // insert the char
1636 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1641 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1643 p = text_hole_make(p, 1);
1646 file_modified = TRUE; // has the file been modified
1652 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1654 Byte *save_dot, *p, *q;
1660 if (strchr("cdy><", c)) {
1661 // these cmds operate on whole lines
1662 p = q = begin_line(p);
1663 for (cnt = 1; cnt < cmdcnt; cnt++) {
1667 } else if (strchr("^%$0bBeEft", c)) {
1668 // These cmds operate on char positions
1669 do_cmd(c); // execute movement cmd
1671 } else if (strchr("wW", c)) {
1672 do_cmd(c); // execute movement cmd
1673 // if we are at the next word's first char
1674 // step back one char
1675 // but check the possibilities when it is true
1676 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1677 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1678 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1679 dot--; // move back off of next word
1680 if (dot > text && *dot == '\n')
1681 dot--; // stay off NL
1683 } else if (strchr("H-k{", c)) {
1684 // these operate on multi-lines backwards
1685 q = end_line(dot); // find NL
1686 do_cmd(c); // execute movement cmd
1689 } else if (strchr("L+j}\r\n", c)) {
1690 // these operate on multi-lines forwards
1691 p = begin_line(dot);
1692 do_cmd(c); // execute movement cmd
1693 dot_end(); // find NL
1696 c = 27; // error- return an ESC char
1709 static int st_test(Byte * p, int type, int dir, Byte * tested)
1719 if (type == S_BEFORE_WS) {
1721 test = ((!isspace(c)) || c == '\n');
1723 if (type == S_TO_WS) {
1725 test = ((!isspace(c)) || c == '\n');
1727 if (type == S_OVER_WS) {
1729 test = ((isspace(c)));
1731 if (type == S_END_PUNCT) {
1733 test = ((ispunct(c)));
1735 if (type == S_END_ALNUM) {
1737 test = ((isalnum(c)) || c == '_');
1743 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1747 while (st_test(p, type, dir, &c)) {
1748 // make sure we limit search to correct number of lines
1749 if (c == '\n' && --linecnt < 1)
1751 if (dir >= 0 && p >= end - 1)
1753 if (dir < 0 && p <= text)
1755 p += dir; // move to next char
1760 // find matching char of pair () [] {}
1761 static Byte *find_pair(Byte * p, Byte c)
1768 dir = 1; // assume forward
1792 for (q = p + dir; text <= q && q < end; q += dir) {
1793 // look for match, count levels of pairs (( ))
1795 level++; // increase pair levels
1797 level--; // reduce pair level
1799 break; // found matching pair
1802 q = NULL; // indicate no match
1806 #ifdef CONFIG_FEATURE_VI_SETOPTS
1807 // show the matching char of a pair, () [] {}
1808 static void showmatching(Byte * p)
1812 // we found half of a pair
1813 q = find_pair(p, *p); // get loc of matching char
1815 indicate_error('3'); // no matching char
1817 // "q" now points to matching pair
1818 save_dot = dot; // remember where we are
1819 dot = q; // go to new loc
1820 refresh(FALSE); // let the user see it
1821 (void) mysleep(40); // give user some time
1822 dot = save_dot; // go back to old loc
1826 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1828 // open a hole in text[]
1829 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1838 cnt = end - src; // the rest of buffer
1839 if (memmove(dest, src, cnt) != dest) {
1840 psbs("can't create room for new characters");
1842 memset(p, ' ', size); // clear new hole
1843 end = end + size; // adjust the new END
1844 file_modified = TRUE; // has the file been modified
1849 // close a hole in text[]
1850 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1855 // move forwards, from beginning
1859 if (q < p) { // they are backward- swap them
1863 hole_size = q - p + 1;
1865 if (src < text || src > end)
1867 if (dest < text || dest >= end)
1870 goto thd_atend; // just delete the end of the buffer
1871 if (memmove(dest, src, cnt) != dest) {
1872 psbs("can't delete the character");
1875 end = end - hole_size; // adjust the new END
1877 dest = end - 1; // make sure dest in below end-1
1879 dest = end = text; // keep pointers valid
1880 file_modified = TRUE; // has the file been modified
1885 // copy text into register, then delete text.
1886 // if dist <= 0, do not include, or go past, a NewLine
1888 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1892 // make sure start <= stop
1894 // they are backwards, reverse them
1900 // we can not cross NL boundaries
1904 // dont go past a NewLine
1905 for (; p + 1 <= stop; p++) {
1907 stop = p; // "stop" just before NewLine
1913 #ifdef CONFIG_FEATURE_VI_YANKMARK
1914 text_yank(start, stop, YDreg);
1915 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1916 if (yf == YANKDEL) {
1917 p = text_hole_delete(start, stop);
1922 static void show_help(void)
1924 puts("These features are available:"
1925 #ifdef CONFIG_FEATURE_VI_SEARCH
1926 "\n\tPattern searches with / and ?"
1927 #endif /* CONFIG_FEATURE_VI_SEARCH */
1928 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1929 "\n\tLast command repeat with \'.\'"
1930 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1931 #ifdef CONFIG_FEATURE_VI_YANKMARK
1932 "\n\tLine marking with 'x"
1933 "\n\tNamed buffers with \"x"
1934 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1935 #ifdef CONFIG_FEATURE_VI_READONLY
1936 "\n\tReadonly if vi is called as \"view\""
1937 "\n\tReadonly with -R command line arg"
1938 #endif /* CONFIG_FEATURE_VI_READONLY */
1939 #ifdef CONFIG_FEATURE_VI_SET
1940 "\n\tSome colon mode commands with \':\'"
1941 #endif /* CONFIG_FEATURE_VI_SET */
1942 #ifdef CONFIG_FEATURE_VI_SETOPTS
1943 "\n\tSettable options with \":set\""
1944 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1945 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1946 "\n\tSignal catching- ^C"
1947 "\n\tJob suspend and resume with ^Z"
1948 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1949 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1950 "\n\tAdapt to window re-sizes"
1951 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1955 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1960 strcpy((char *) buf, ""); // init buf
1961 if (strlen((char *) s) <= 0)
1962 s = (Byte *) "(NULL)";
1963 for (; *s > '\0'; s++) {
1967 c_is_no_print = c > 127 && !Isprint(c);
1968 if (c_is_no_print) {
1969 strcat((char *) buf, SOn);
1972 if (c < ' ' || c == 127) {
1973 strcat((char *) buf, "^");
1980 strcat((char *) buf, (char *) b);
1982 strcat((char *) buf, SOs);
1984 strcat((char *) buf, "$");
1989 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1990 static void start_new_cmd_q(Byte c)
1993 free(last_modifying_cmd);
1994 // get buffer for new cmd
1995 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1996 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1997 // if there is a current cmd count put it in the buffer first
1999 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2000 // save char c onto queue
2001 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2006 static void end_cmd_q(void)
2008 #ifdef CONFIG_FEATURE_VI_YANKMARK
2009 YDreg = 26; // go back to default Yank/Delete reg
2010 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2014 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2016 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2017 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2021 i = strlen((char *) s);
2022 p = text_hole_make(p, i);
2023 strncpy((char *) p, (char *) s, i);
2024 for (cnt = 0; *s != '\0'; s++) {
2028 #ifdef CONFIG_FEATURE_VI_YANKMARK
2029 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2030 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2033 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2035 #ifdef CONFIG_FEATURE_VI_YANKMARK
2036 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2041 if (q < p) { // they are backwards- reverse them
2048 free(t); // if already a yank register, free it
2049 t = (Byte *) xmalloc(cnt + 1); // get a new register
2050 memset(t, '\0', cnt + 1); // clear new text[]
2051 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2056 static Byte what_reg(void)
2062 c = 'D'; // default to D-reg
2063 if (0 <= YDreg && YDreg <= 25)
2064 c = 'a' + (Byte) YDreg;
2072 static void check_context(Byte cmd)
2074 // A context is defined to be "modifying text"
2075 // Any modifying command establishes a new context.
2077 if (dot < context_start || dot > context_end) {
2078 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2079 // we are trying to modify text[]- make this the current context
2080 mark[27] = mark[26]; // move cur to prev
2081 mark[26] = dot; // move local to cur
2082 context_start = prev_line(prev_line(dot));
2083 context_end = next_line(next_line(dot));
2084 //loiter= start_loiter= now;
2090 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2094 // the current context is in mark[26]
2095 // the previous context is in mark[27]
2096 // only swap context if other context is valid
2097 if (text <= mark[27] && mark[27] <= end - 1) {
2099 mark[27] = mark[26];
2101 p = mark[26]; // where we are going- previous context
2102 context_start = prev_line(prev_line(prev_line(p)));
2103 context_end = next_line(next_line(next_line(p)));
2107 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2109 static int isblnk(Byte c) // is the char a blank or tab
2111 return (c == ' ' || c == '\t');
2114 //----- Set terminal attributes --------------------------------
2115 static void rawmode(void)
2117 tcgetattr(0, &term_orig);
2118 term_vi = term_orig;
2119 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2120 term_vi.c_iflag &= (~IXON & ~ICRNL);
2121 term_vi.c_oflag &= (~ONLCR);
2122 term_vi.c_cc[VMIN] = 1;
2123 term_vi.c_cc[VTIME] = 0;
2124 erase_char = term_vi.c_cc[VERASE];
2125 tcsetattr(0, TCSANOW, &term_vi);
2128 static void cookmode(void)
2131 tcsetattr(0, TCSANOW, &term_orig);
2134 //----- Come here when we get a window resize signal ---------
2135 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2136 static void winch_sig(int sig)
2138 signal(SIGWINCH, winch_sig);
2139 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2141 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2142 new_screen(rows, columns); // get memory for virtual screen
2143 redraw(TRUE); // re-draw the screen
2146 //----- Come here when we get a continue signal -------------------
2147 static void cont_sig(int sig)
2149 rawmode(); // terminal to "raw"
2150 *status_buffer = '\0'; // clear the status buffer
2151 redraw(TRUE); // re-draw the screen
2153 signal(SIGTSTP, suspend_sig);
2154 signal(SIGCONT, SIG_DFL);
2155 kill(my_pid, SIGCONT);
2158 //----- Come here when we get a Suspend signal -------------------
2159 static void suspend_sig(int sig)
2161 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2162 clear_to_eol(); // Erase to end of line
2163 cookmode(); // terminal to "cooked"
2165 signal(SIGCONT, cont_sig);
2166 signal(SIGTSTP, SIG_DFL);
2167 kill(my_pid, SIGTSTP);
2170 //----- Come here when we get a signal ---------------------------
2171 static void catch_sig(int sig)
2173 signal(SIGHUP, catch_sig);
2174 signal(SIGINT, catch_sig);
2175 signal(SIGTERM, catch_sig);
2176 signal(SIGALRM, catch_sig);
2178 longjmp(restart, sig);
2181 //----- Come here when we get a core dump signal -----------------
2182 static void core_sig(int sig)
2184 signal(SIGQUIT, core_sig);
2185 signal(SIGILL, core_sig);
2186 signal(SIGTRAP, core_sig);
2187 signal(SIGIOT, core_sig);
2188 signal(SIGABRT, core_sig);
2189 signal(SIGFPE, core_sig);
2190 signal(SIGBUS, core_sig);
2191 signal(SIGSEGV, core_sig);
2193 signal(SIGSYS, core_sig);
2196 if(sig) { // signaled
2197 dot = bound_dot(dot); // make sure "dot" is valid
2199 longjmp(restart, sig);
2202 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2204 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2206 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2211 tv.tv_usec = hund * 10000;
2212 select(1, &rfds, NULL, NULL, &tv);
2213 return (FD_ISSET(0, &rfds));
2216 static Byte readbuffer[BUFSIZ];
2217 static int readed_for_parse;
2219 //----- IO Routines --------------------------------------------
2220 static Byte readit(void) // read (maybe cursor) key from stdin
2229 static const struct esc_cmds esccmds[] = {
2230 {"OA", (Byte) VI_K_UP}, // cursor key Up
2231 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2232 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2233 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2234 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2235 {"OF", (Byte) VI_K_END}, // Cursor Key End
2236 {"[A", (Byte) VI_K_UP}, // cursor key Up
2237 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2238 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2239 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2240 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2241 {"[F", (Byte) VI_K_END}, // Cursor Key End
2242 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2243 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2244 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2245 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2246 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2247 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2248 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2249 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2250 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2251 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2252 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2253 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2254 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2255 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2256 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2257 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2258 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2259 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2260 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2261 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2262 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2265 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2267 (void) alarm(0); // turn alarm OFF while we wait for input
2269 n = readed_for_parse;
2270 // get input from User- are there already input chars in Q?
2273 // the Q is empty, wait for a typed char
2274 n = read(0, readbuffer, BUFSIZ - 1);
2277 goto ri0; // interrupted sys call
2280 if (errno == EFAULT)
2282 if (errno == EINVAL)
2290 if (readbuffer[0] == 27) {
2291 // This is an ESC char. Is this Esc sequence?
2292 // Could be bare Esc key. See if there are any
2293 // more chars to read after the ESC. This would
2294 // be a Function or Cursor Key sequence.
2298 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2300 // keep reading while there are input chars and room in buffer
2301 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2302 // read the rest of the ESC string
2303 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2309 readed_for_parse = n;
2312 if(c == 27 && n > 1) {
2313 // Maybe cursor or function key?
2314 const struct esc_cmds *eindex;
2316 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2317 int cnt = strlen(eindex->seq);
2321 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2323 // is a Cursor key- put derived value back into Q
2325 // for squeeze out the ESC sequence
2329 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2330 /* defined ESC sequence not found, set only one ESC */
2336 // remove key sequence from Q
2337 readed_for_parse -= n;
2338 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2339 (void) alarm(3); // we are done waiting for input, turn alarm ON
2343 //----- IO Routines --------------------------------------------
2344 static Byte get_one_char()
2348 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2349 // ! adding2q && ioq == 0 read()
2350 // ! adding2q && ioq != 0 *ioq
2351 // adding2q *last_modifying_cmd= read()
2353 // we are not adding to the q.
2354 // but, we may be reading from a q
2356 // there is no current q, read from STDIN
2357 c = readit(); // get the users input
2359 // there is a queue to get chars from first
2362 // the end of the q, read from STDIN
2364 ioq_start = ioq = 0;
2365 c = readit(); // get the users input
2369 // adding STDIN chars to q
2370 c = readit(); // get the users input
2371 if (last_modifying_cmd != 0) {
2372 int len = strlen((char *) last_modifying_cmd);
2373 if (len + 1 >= BUFSIZ) {
2374 psbs("last_modifying_cmd overrun");
2376 // add new char to q
2377 last_modifying_cmd[len] = c;
2381 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2382 c = readit(); // get the users input
2383 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2384 return (c); // return the char, where ever it came from
2387 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2392 static Byte *obufp = NULL;
2394 strcpy((char *) buf, (char *) prompt);
2395 *status_buffer = '\0'; // clear the status buffer
2396 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2397 clear_to_eol(); // clear the line
2398 write1(prompt); // write out the :, /, or ? prompt
2400 for (i = strlen((char *) buf); i < BUFSIZ;) {
2401 c = get_one_char(); // read user input
2402 if (c == '\n' || c == '\r' || c == 27)
2403 break; // is this end of input
2404 if (c == erase_char) { // user wants to erase prev char
2405 i--; // backup to prev char
2406 buf[i] = '\0'; // erase the char
2407 buf[i + 1] = '\0'; // null terminate buffer
2408 write1("\b \b"); // erase char on screen
2409 if (i <= 0) { // user backs up before b-o-l, exit
2413 buf[i] = c; // save char in buffer
2414 buf[i + 1] = '\0'; // make sure buffer is null terminated
2415 putchar(c); // echo the char back to user
2421 obufp = (Byte *) bb_xstrdup((char *) buf);
2425 static int file_size(const Byte * fn) // what is the byte size of "fn"
2430 if (fn == 0 || strlen(fn) <= 0)
2433 sr = stat((char *) fn, &st_buf); // see if file exists
2435 cnt = (int) st_buf.st_size;
2440 static int file_insert(Byte * fn, Byte * p, int size)
2445 #ifdef CONFIG_FEATURE_VI_READONLY
2447 #endif /* CONFIG_FEATURE_VI_READONLY */
2448 if (fn == 0 || strlen((char*) fn) <= 0) {
2449 psbs("No filename given");
2453 // OK- this is just a no-op
2458 psbs("Trying to insert a negative number (%d) of characters", size);
2461 if (p < text || p > end) {
2462 psbs("Trying to insert file outside of memory");
2466 // see if we can open the file
2467 #ifdef CONFIG_FEATURE_VI_READONLY
2468 if (vi_readonly) goto fi1; // do not try write-mode
2470 fd = open((char *) fn, O_RDWR); // assume read & write
2472 // could not open for writing- maybe file is read only
2473 #ifdef CONFIG_FEATURE_VI_READONLY
2476 fd = open((char *) fn, O_RDONLY); // try read-only
2478 psbs("\"%s\" %s", fn, "could not open file");
2481 #ifdef CONFIG_FEATURE_VI_READONLY
2482 // got the file- read-only
2484 #endif /* CONFIG_FEATURE_VI_READONLY */
2486 p = text_hole_make(p, size);
2487 cnt = read(fd, p, size);
2491 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2492 psbs("could not read file \"%s\"", fn);
2493 } else if (cnt < size) {
2494 // There was a partial read, shrink unused space text[]
2495 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2496 psbs("could not read all of file \"%s\"", fn);
2499 file_modified = TRUE;
2504 static int file_write(Byte * fn, Byte * first, Byte * last)
2506 int fd, cnt, charcnt;
2509 psbs("No current filename");
2513 // FIXIT- use the correct umask()
2514 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2517 cnt = last - first + 1;
2518 charcnt = write(fd, first, cnt);
2519 if (charcnt == cnt) {
2521 //file_modified= FALSE; // the file has not been modified
2529 //----- Terminal Drawing ---------------------------------------
2530 // The terminal is made up of 'rows' line of 'columns' columns.
2531 // classically this would be 24 x 80.
2532 // screen coordinates
2538 // 23,0 ... 23,79 status line
2541 //----- Move the cursor to row x col (count from 0, not 1) -------
2542 static void place_cursor(int row, int col, int opti)
2546 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2549 // char cm3[BUFSIZ];
2551 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2553 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2555 if (row < 0) row = 0;
2556 if (row >= rows) row = rows - 1;
2557 if (col < 0) col = 0;
2558 if (col >= columns) col = columns - 1;
2560 //----- 1. Try the standard terminal ESC sequence
2561 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2563 if (! opti) goto pc0;
2565 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2566 //----- find the minimum # of chars to move cursor -------------
2567 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2568 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2570 // move to the correct row
2571 while (row < Rrow) {
2572 // the cursor has to move up
2576 while (row > Rrow) {
2577 // the cursor has to move down
2578 strcat(cm2, CMdown);
2582 // now move to the correct column
2583 strcat(cm2, "\r"); // start at col 0
2584 // just send out orignal source char to get to correct place
2585 screenp = &screen[row * columns]; // start of screen line
2586 strncat(cm2, screenp, col);
2588 //----- 3. Try some other way of moving cursor
2589 //---------------------------------------------
2591 // pick the shortest cursor motion to send out
2593 if (strlen(cm2) < strlen(cm)) {
2595 } /* else if (strlen(cm3) < strlen(cm)) {
2598 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2600 write1(cm); // move the cursor
2603 //----- Erase from cursor to end of line -----------------------
2604 static void clear_to_eol()
2606 write1(Ceol); // Erase from cursor to end of line
2609 //----- Erase from cursor to end of screen -----------------------
2610 static void clear_to_eos()
2612 write1(Ceos); // Erase from cursor to end of screen
2615 //----- Start standout mode ------------------------------------
2616 static void standout_start() // send "start reverse video" sequence
2618 write1(SOs); // Start reverse video mode
2621 //----- End standout mode --------------------------------------
2622 static void standout_end() // send "end reverse video" sequence
2624 write1(SOn); // End reverse video mode
2627 //----- Flash the screen --------------------------------------
2628 static void flash(int h)
2630 standout_start(); // send "start reverse video" sequence
2633 standout_end(); // send "end reverse video" sequence
2637 static void Indicate_Error(void)
2639 #ifdef CONFIG_FEATURE_VI_CRASHME
2641 return; // generate a random command
2642 #endif /* CONFIG_FEATURE_VI_CRASHME */
2644 write1(bell); // send out a bell character
2650 //----- Screen[] Routines --------------------------------------
2651 //----- Erase the Screen[] memory ------------------------------
2652 static void screen_erase()
2654 memset(screen, ' ', screensize); // clear new screen
2657 //----- Draw the status line at bottom of the screen -------------
2658 static void show_status_line(void)
2660 static int last_cksum;
2663 cnt = strlen((char *) status_buffer);
2664 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
2665 // don't write the status line unless it changes
2666 if (cnt > 0 && last_cksum != cksum) {
2667 last_cksum= cksum; // remember if we have seen this line
2668 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2669 write1(status_buffer);
2671 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2675 //----- format the status buffer, the bottom line of screen ------
2676 // print status buffer, with STANDOUT mode
2677 static void psbs(const char *format, ...)
2681 va_start(args, format);
2682 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2683 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
2685 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2691 // print status buffer
2692 static void psb(const char *format, ...)
2696 va_start(args, format);
2697 vsprintf((char *) status_buffer, format, args);
2702 static void ni(Byte * s) // display messages
2706 print_literal(buf, s);
2707 psbs("\'%s\' is not implemented", buf);
2710 static void edit_status(void) // show file status on status line
2712 int cur, tot, percent;
2714 cur = count_lines(text, dot);
2715 tot = count_lines(text, end - 1);
2716 // current line percent
2717 // ------------- ~~ ----------
2720 percent = (100 * cur) / tot;
2726 #ifdef CONFIG_FEATURE_VI_READONLY
2728 #endif /* CONFIG_FEATURE_VI_READONLY */
2729 "%s line %d of %d --%d%%--",
2730 (cfn != 0 ? (char *) cfn : "No file"),
2731 #ifdef CONFIG_FEATURE_VI_READONLY
2732 ((vi_readonly || readonly) ? " [Read only]" : ""),
2733 #endif /* CONFIG_FEATURE_VI_READONLY */
2734 (file_modified ? " [modified]" : ""),
2738 //----- Force refresh of all Lines -----------------------------
2739 static void redraw(int full_screen)
2741 place_cursor(0, 0, FALSE); // put cursor in correct place
2742 clear_to_eos(); // tel terminal to erase display
2743 screen_erase(); // erase the internal screen buffer
2744 refresh(full_screen); // this will redraw the entire display
2747 //----- Format a text[] line into a buffer ---------------------
2748 static void format_line(Byte *dest, Byte *src, int li)
2753 for (co= 0; co < MAX_SCR_COLS; co++) {
2754 c= ' '; // assume blank
2755 if (li > 0 && co == 0) {
2756 c = '~'; // not first line, assume Tilde
2758 // are there chars in text[] and have we gone past the end
2759 if (text < end && src < end) {
2764 if (c > 127 && !Isprint(c)) {
2767 if (c < ' ' || c == 127) {
2771 for (; (co % tabstop) != (tabstop - 1); co++) {
2779 c += '@'; // make it visible
2782 // the co++ is done here so that the column will
2783 // not be overwritten when we blank-out the rest of line
2790 //----- Refresh the changed screen lines -----------------------
2791 // Copy the source line from text[] into the buffer and note
2792 // if the current screenline is different from the new buffer.
2793 // If they differ then that line needs redrawing on the terminal.
2795 static void refresh(int full_screen)
2797 static int old_offset;
2799 Byte buf[MAX_SCR_COLS];
2800 Byte *tp, *sp; // pointer into text[] and screen[]
2801 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2802 int last_li= -2; // last line that changed- for optimizing cursor movement
2803 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2805 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2807 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2808 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2809 tp = screenbegin; // index into text[] of top line
2811 // compare text[] to screen[] and mark screen[] lines that need updating
2812 for (li = 0; li < rows - 1; li++) {
2813 int cs, ce; // column start & end
2814 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2815 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2816 // format current text line into buf
2817 format_line(buf, tp, li);
2819 // skip to the end of the current text[] line
2820 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2822 // see if there are any changes between vitual screen and buf
2823 changed = FALSE; // assume no change
2826 sp = &screen[li * columns]; // start of screen line
2828 // force re-draw of every single column from 0 - columns-1
2831 // compare newly formatted buffer with virtual screen
2832 // look forward for first difference between buf and screen
2833 for ( ; cs <= ce; cs++) {
2834 if (buf[cs + offset] != sp[cs]) {
2835 changed = TRUE; // mark for redraw
2840 // look backward for last difference between buf and screen
2841 for ( ; ce >= cs; ce--) {
2842 if (buf[ce + offset] != sp[ce]) {
2843 changed = TRUE; // mark for redraw
2847 // now, cs is index of first diff, and ce is index of last diff
2849 // if horz offset has changed, force a redraw
2850 if (offset != old_offset) {
2855 // make a sanity check of columns indexes
2857 if (ce > columns-1) ce= columns-1;
2858 if (cs > ce) { cs= 0; ce= columns-1; }
2859 // is there a change between vitual screen and buf
2861 // copy changed part of buffer to virtual screen
2862 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2864 // move cursor to column of first change
2865 if (offset != old_offset) {
2866 // opti_cur_move is still too stupid
2867 // to handle offsets correctly
2868 place_cursor(li, cs, FALSE);
2870 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2871 // if this just the next line
2872 // try to optimize cursor movement
2873 // otherwise, use standard ESC sequence
2874 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2876 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2877 place_cursor(li, cs, FALSE); // use standard ESC sequence
2878 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2881 // write line out to terminal
2891 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2893 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2897 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2898 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2901 place_cursor(crow, ccol, FALSE);
2902 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2904 if (offset != old_offset)
2905 old_offset = offset;
2908 //---------------------------------------------------------------------
2909 //----- the Ascii Chart -----------------------------------------------
2911 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2912 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2913 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2914 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2915 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2916 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2917 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2918 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2919 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2920 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2921 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2922 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2923 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2924 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2925 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2926 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2927 //---------------------------------------------------------------------
2929 //----- Execute a Vi Command -----------------------------------
2930 static void do_cmd(Byte c)
2932 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2933 int cnt, i, j, dir, yf;
2935 c1 = c; // quiet the compiler
2936 cnt = yf = dir = 0; // quiet the compiler
2937 p = q = save_dot = msg = buf; // quiet the compiler
2938 memset(buf, '\0', 9); // clear buf
2940 /* if this is a cursor key, skip these checks */
2953 if (cmd_mode == 2) {
2954 // flip-flop Insert/Replace mode
2955 if (c == VI_K_INSERT) goto dc_i;
2956 // we are 'R'eplacing the current *dot with new char
2958 // don't Replace past E-o-l
2959 cmd_mode = 1; // convert to insert
2961 if (1 <= c || Isprint(c)) {
2963 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2964 dot = char_insert(dot, c); // insert new char
2969 if (cmd_mode == 1) {
2970 // hitting "Insert" twice means "R" replace mode
2971 if (c == VI_K_INSERT) goto dc5;
2972 // insert the char c at "dot"
2973 if (1 <= c || Isprint(c)) {
2974 dot = char_insert(dot, c);
2989 #ifdef CONFIG_FEATURE_VI_CRASHME
2990 case 0x14: // dc4 ctrl-T
2991 crashme = (crashme == 0) ? 1 : 0;
2993 #endif /* CONFIG_FEATURE_VI_CRASHME */
3022 //case 'u': // u- FIXME- there is no undo
3024 default: // unrecognised command
3033 end_cmd_q(); // stop adding to q
3034 case 0x00: // nul- ignore
3036 case 2: // ctrl-B scroll up full screen
3037 case VI_K_PAGEUP: // Cursor Key Page Up
3038 dot_scroll(rows - 2, -1);
3040 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3041 case 0x03: // ctrl-C interrupt
3042 longjmp(restart, 1);
3044 case 26: // ctrl-Z suspend
3045 suspend_sig(SIGTSTP);
3047 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3048 case 4: // ctrl-D scroll down half screen
3049 dot_scroll((rows - 2) / 2, 1);
3051 case 5: // ctrl-E scroll down one line
3054 case 6: // ctrl-F scroll down full screen
3055 case VI_K_PAGEDOWN: // Cursor Key Page Down
3056 dot_scroll(rows - 2, 1);
3058 case 7: // ctrl-G show current status
3061 case 'h': // h- move left
3062 case VI_K_LEFT: // cursor key Left
3063 case 8: // ctrl-H- move left (This may be ERASE char)
3064 case 127: // DEL- move left (This may be ERASE char)
3070 case 10: // Newline ^J
3071 case 'j': // j- goto next line, same col
3072 case VI_K_DOWN: // cursor key Down
3076 dot_next(); // go to next B-o-l
3077 dot = move_to_col(dot, ccol + offset); // try stay in same col
3079 case 12: // ctrl-L force redraw whole screen
3080 case 18: // ctrl-R force redraw
3081 place_cursor(0, 0, FALSE); // put cursor in correct place
3082 clear_to_eos(); // tel terminal to erase display
3084 screen_erase(); // erase the internal screen buffer
3085 refresh(TRUE); // this will redraw the entire display
3087 case 13: // Carriage Return ^M
3088 case '+': // +- goto next line
3095 case 21: // ctrl-U scroll up half screen
3096 dot_scroll((rows - 2) / 2, -1);
3098 case 25: // ctrl-Y scroll up one line
3104 cmd_mode = 0; // stop insrting
3106 *status_buffer = '\0'; // clear status buffer
3108 case ' ': // move right
3109 case 'l': // move right
3110 case VI_K_RIGHT: // Cursor Key Right
3116 #ifdef CONFIG_FEATURE_VI_YANKMARK
3117 case '"': // "- name a register to use for Delete/Yank
3118 c1 = get_one_char();
3126 case '\'': // '- goto a specific mark
3127 c1 = get_one_char();
3133 if (text <= q && q < end) {
3135 dot_begin(); // go to B-o-l
3138 } else if (c1 == '\'') { // goto previous context
3139 dot = swap_context(dot); // swap current and previous context
3140 dot_begin(); // go to B-o-l
3146 case 'm': // m- Mark a line
3147 // this is really stupid. If there are any inserts or deletes
3148 // between text[0] and dot then this mark will not point to the
3149 // correct location! It could be off by many lines!
3150 // Well..., at least its quick and dirty.
3151 c1 = get_one_char();
3155 // remember the line
3156 mark[(int) c1] = dot;
3161 case 'P': // P- Put register before
3162 case 'p': // p- put register after
3165 psbs("Nothing in register %c", what_reg());
3168 // are we putting whole lines or strings
3169 if (strchr((char *) p, '\n') != NULL) {
3171 dot_begin(); // putting lines- Put above
3174 // are we putting after very last line?
3175 if (end_line(dot) == (end - 1)) {
3176 dot = end; // force dot to end of text[]
3178 dot_next(); // next line, then put before
3183 dot_right(); // move to right, can move to NL
3185 dot = string_insert(dot, p); // insert the string
3186 end_cmd_q(); // stop adding to q
3188 case 'U': // U- Undo; replace current line with original version
3189 if (reg[Ureg] != 0) {
3190 p = begin_line(dot);
3192 p = text_hole_delete(p, q); // delete cur line
3193 p = string_insert(p, reg[Ureg]); // insert orig line
3198 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3199 case '$': // $- goto end of line
3200 case VI_K_END: // Cursor Key End
3204 dot = end_line(dot);
3206 case '%': // %- find matching char of pair () [] {}
3207 for (q = dot; q < end && *q != '\n'; q++) {
3208 if (strchr("()[]{}", *q) != NULL) {
3209 // we found half of a pair
3210 p = find_pair(q, *q);
3222 case 'f': // f- forward to a user specified char
3223 last_forward_char = get_one_char(); // get the search char
3225 // dont separate these two commands. 'f' depends on ';'
3227 //**** fall thru to ... 'i'
3228 case ';': // ;- look at rest of line for last forward char
3232 if (last_forward_char == 0) break;
3234 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3237 if (*q == last_forward_char)
3240 case '-': // -- goto prev line
3247 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3248 case '.': // .- repeat the last modifying command
3249 // Stuff the last_modifying_cmd back into stdin
3250 // and let it be re-executed.
3251 if (last_modifying_cmd != 0) {
3252 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3255 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3256 #ifdef CONFIG_FEATURE_VI_SEARCH
3257 case '?': // /- search for a pattern
3258 case '/': // /- search for a pattern
3261 q = get_input_line(buf); // get input line- use "status line"
3262 if (strlen((char *) q) == 1)
3263 goto dc3; // if no pat re-use old pat
3264 if (strlen((char *) q) > 1) { // new pat- save it and find
3265 // there is a new pat
3266 free(last_search_pattern);
3267 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3268 goto dc3; // now find the pattern
3270 // user changed mind and erased the "/"- do nothing
3272 case 'N': // N- backward search for last pattern
3276 dir = BACK; // assume BACKWARD search
3278 if (last_search_pattern[0] == '?') {
3282 goto dc4; // now search for pattern
3284 case 'n': // n- repeat search for last pattern
3285 // search rest of text[] starting at next char
3286 // if search fails return orignal "p" not the "p+1" address
3291 if (last_search_pattern == 0) {
3292 msg = (Byte *) "No previous regular expression";
3295 if (last_search_pattern[0] == '/') {
3296 dir = FORWARD; // assume FORWARD search
3299 if (last_search_pattern[0] == '?') {
3304 q = char_search(p, last_search_pattern + 1, dir, FULL);
3306 dot = q; // good search, update "dot"
3310 // no pattern found between "dot" and "end"- continue at top
3315 q = char_search(p, last_search_pattern + 1, dir, FULL);
3316 if (q != NULL) { // found something
3317 dot = q; // found new pattern- goto it
3318 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3320 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3323 msg = (Byte *) "Pattern not found";
3328 case '{': // {- move backward paragraph
3329 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3330 if (q != NULL) { // found blank line
3331 dot = next_line(q); // move to next blank line
3334 case '}': // }- move forward paragraph
3335 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3336 if (q != NULL) { // found blank line
3337 dot = next_line(q); // move to next blank line
3340 #endif /* CONFIG_FEATURE_VI_SEARCH */
3341 case '0': // 0- goto begining of line
3351 if (c == '0' && cmdcnt < 1) {
3352 dot_begin(); // this was a standalone zero
3354 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3357 case ':': // :- the colon mode commands
3358 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3359 #ifdef CONFIG_FEATURE_VI_COLON
3360 colon(p); // execute the command
3361 #else /* CONFIG_FEATURE_VI_COLON */
3363 p++; // move past the ':'
3364 cnt = strlen((char *) p);
3367 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3368 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3369 if (file_modified && p[1] != '!') {
3370 psbs("No write since last change (:quit! overrides)");
3374 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3375 strncasecmp((char *) p, "wq", cnt) == 0 ||
3376 strncasecmp((char *) p, "x", cnt) == 0) {
3377 cnt = file_write(cfn, text, end - 1);
3378 file_modified = FALSE;
3379 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3380 if (p[0] == 'x' || p[1] == 'q') {
3383 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3384 edit_status(); // show current file status
3385 } else if (sscanf((char *) p, "%d", &j) > 0) {
3386 dot = find_line(j); // go to line # j
3388 } else { // unrecognised cmd
3391 #endif /* CONFIG_FEATURE_VI_COLON */
3393 case '<': // <- Left shift something
3394 case '>': // >- Right shift something
3395 cnt = count_lines(text, dot); // remember what line we are on
3396 c1 = get_one_char(); // get the type of thing to delete
3397 find_range(&p, &q, c1);
3398 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3401 i = count_lines(p, q); // # of lines we are shifting
3402 for ( ; i > 0; i--, p = next_line(p)) {
3404 // shift left- remove tab or 8 spaces
3406 // shrink buffer 1 char
3407 (void) text_hole_delete(p, p);
3408 } else if (*p == ' ') {
3409 // we should be calculating columns, not just SPACE
3410 for (j = 0; *p == ' ' && j < tabstop; j++) {
3411 (void) text_hole_delete(p, p);
3414 } else if (c == '>') {
3415 // shift right -- add tab or 8 spaces
3416 (void) char_insert(p, '\t');
3419 dot = find_line(cnt); // what line were we on
3421 end_cmd_q(); // stop adding to q
3423 case 'A': // A- append at e-o-l
3424 dot_end(); // go to e-o-l
3425 //**** fall thru to ... 'a'
3426 case 'a': // a- append after current char
3431 case 'B': // B- back a blank-delimited Word
3432 case 'E': // E- end of a blank-delimited word
3433 case 'W': // W- forward a blank-delimited word
3440 if (c == 'W' || isspace(dot[dir])) {
3441 dot = skip_thing(dot, 1, dir, S_TO_WS);
3442 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3445 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3447 case 'C': // C- Change to e-o-l
3448 case 'D': // D- delete to e-o-l
3450 dot = dollar_line(dot); // move to before NL
3451 // copy text into a register and delete
3452 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3454 goto dc_i; // start inserting
3455 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3457 end_cmd_q(); // stop adding to q
3458 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3460 case 'G': // G- goto to a line number (default= E-O-F)
3461 dot = end - 1; // assume E-O-F
3463 dot = find_line(cmdcnt); // what line is #cmdcnt
3467 case 'H': // H- goto top line on screen
3469 if (cmdcnt > (rows - 1)) {
3470 cmdcnt = (rows - 1);
3477 case 'I': // I- insert before first non-blank
3480 //**** fall thru to ... 'i'
3481 case 'i': // i- insert before current char
3482 case VI_K_INSERT: // Cursor Key Insert
3484 cmd_mode = 1; // start insrting
3485 psb("-- Insert --");
3487 case 'J': // J- join current and next lines together
3491 dot_end(); // move to NL
3492 if (dot < end - 1) { // make sure not last char in text[]
3493 *dot++ = ' '; // replace NL with space
3494 while (isblnk(*dot)) { // delete leading WS
3498 end_cmd_q(); // stop adding to q
3500 case 'L': // L- goto bottom line on screen
3502 if (cmdcnt > (rows - 1)) {
3503 cmdcnt = (rows - 1);
3511 case 'M': // M- goto middle line on screen
3513 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3514 dot = next_line(dot);
3516 case 'O': // O- open a empty line above
3518 p = begin_line(dot);
3519 if (p[-1] == '\n') {
3521 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3523 dot = char_insert(dot, '\n');
3526 dot = char_insert(dot, '\n'); // i\n ESC
3531 case 'R': // R- continuous Replace char
3534 psb("-- Replace --");
3536 case 'X': // X- delete char before dot
3537 case 'x': // x- delete the current char
3538 case 's': // s- substitute the current char
3545 if (dot[dir] != '\n') {
3547 dot--; // delete prev char
3548 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3551 goto dc_i; // start insrting
3552 end_cmd_q(); // stop adding to q
3554 case 'Z': // Z- if modified, {write}; exit
3555 // ZZ means to save file (if necessary), then exit
3556 c1 = get_one_char();
3562 #ifdef CONFIG_FEATURE_VI_READONLY
3565 #endif /* CONFIG_FEATURE_VI_READONLY */
3567 cnt = file_write(cfn, text, end - 1);
3568 if (cnt == (end - 1 - text + 1)) {
3575 case '^': // ^- move to first non-blank on line
3579 case 'b': // b- back a word
3580 case 'e': // e- end of word
3587 if ((dot + dir) < text || (dot + dir) > end - 1)
3590 if (isspace(*dot)) {
3591 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3593 if (isalnum(*dot) || *dot == '_') {
3594 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3595 } else if (ispunct(*dot)) {
3596 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3599 case 'c': // c- change something
3600 case 'd': // d- delete something
3601 #ifdef CONFIG_FEATURE_VI_YANKMARK
3602 case 'y': // y- yank something
3603 case 'Y': // Y- Yank a line
3604 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3605 yf = YANKDEL; // assume either "c" or "d"
3606 #ifdef CONFIG_FEATURE_VI_YANKMARK
3607 if (c == 'y' || c == 'Y')
3609 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3612 c1 = get_one_char(); // get the type of thing to delete
3613 find_range(&p, &q, c1);
3614 if (c1 == 27) { // ESC- user changed mind and wants out
3615 c = c1 = 27; // Escape- do nothing
3616 } else if (strchr("wW", c1)) {
3618 // don't include trailing WS as part of word
3619 while (isblnk(*q)) {
3620 if (q <= text || q[-1] == '\n')
3625 dot = yank_delete(p, q, 0, yf); // delete word
3626 } else if (strchr("^0bBeEft$", c1)) {
3627 // single line copy text into a register and delete
3628 dot = yank_delete(p, q, 0, yf); // delete word
3629 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3630 // multiple line copy text into a register and delete
3631 dot = yank_delete(p, q, 1, yf); // delete lines
3633 dot = char_insert(dot, '\n');
3634 // on the last line of file don't move to prev line
3635 if (dot != (end-1)) {
3638 } else if (c == 'd') {
3643 // could not recognize object
3644 c = c1 = 27; // error-
3648 // if CHANGING, not deleting, start inserting after the delete
3650 strcpy((char *) buf, "Change");
3651 goto dc_i; // start inserting
3654 strcpy((char *) buf, "Delete");
3656 #ifdef CONFIG_FEATURE_VI_YANKMARK
3657 if (c == 'y' || c == 'Y') {
3658 strcpy((char *) buf, "Yank");
3661 q = p + strlen((char *) p);
3662 for (cnt = 0; p <= q; p++) {
3666 psb("%s %d lines (%d chars) using [%c]",
3667 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3668 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3669 end_cmd_q(); // stop adding to q
3672 case 'k': // k- goto prev line, same col
3673 case VI_K_UP: // cursor key Up
3678 dot = move_to_col(dot, ccol + offset); // try stay in same col
3680 case 'r': // r- replace the current char with user input
3681 c1 = get_one_char(); // get the replacement char
3684 file_modified = TRUE; // has the file been modified
3686 end_cmd_q(); // stop adding to q
3688 case 't': // t- move to char prior to next x
3689 last_forward_char = get_one_char();
3691 if (*dot == last_forward_char)
3693 last_forward_char= 0;
3695 case 'w': // w- forward a word
3699 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3700 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3701 } else if (ispunct(*dot)) { // we are on PUNCT
3702 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3705 dot++; // move over word
3706 if (isspace(*dot)) {
3707 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3711 c1 = get_one_char(); // get the replacement char
3714 cnt = (rows - 2) / 2; // put dot at center
3716 cnt = rows - 2; // put dot at bottom
3717 screenbegin = begin_line(dot); // start dot at top
3718 dot_scroll(cnt, -1);
3720 case '|': // |- move to column "cmdcnt"
3721 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3723 case '~': // ~- flip the case of letters a-z -> A-Z
3727 if (islower(*dot)) {
3728 *dot = toupper(*dot);
3729 file_modified = TRUE; // has the file been modified
3730 } else if (isupper(*dot)) {
3731 *dot = tolower(*dot);
3732 file_modified = TRUE; // has the file been modified
3735 end_cmd_q(); // stop adding to q
3737 //----- The Cursor and Function Keys -----------------------------
3738 case VI_K_HOME: // Cursor Key Home
3741 // The Fn keys could point to do_macro which could translate them
3742 case VI_K_FUN1: // Function Key F1
3743 case VI_K_FUN2: // Function Key F2
3744 case VI_K_FUN3: // Function Key F3
3745 case VI_K_FUN4: // Function Key F4
3746 case VI_K_FUN5: // Function Key F5
3747 case VI_K_FUN6: // Function Key F6
3748 case VI_K_FUN7: // Function Key F7
3749 case VI_K_FUN8: // Function Key F8
3750 case VI_K_FUN9: // Function Key F9
3751 case VI_K_FUN10: // Function Key F10
3752 case VI_K_FUN11: // Function Key F11
3753 case VI_K_FUN12: // Function Key F12
3758 // if text[] just became empty, add back an empty line
3760 (void) char_insert(text, '\n'); // start empty buf with dummy line
3763 // it is OK for dot to exactly equal to end, otherwise check dot validity
3765 dot = bound_dot(dot); // make sure "dot" is valid
3767 #ifdef CONFIG_FEATURE_VI_YANKMARK
3768 check_context(c); // update the current context
3769 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3772 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3773 cnt = dot - begin_line(dot);
3774 // Try to stay off of the Newline
3775 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3779 #ifdef CONFIG_FEATURE_VI_CRASHME
3780 static int totalcmds = 0;
3781 static int Mp = 85; // Movement command Probability
3782 static int Np = 90; // Non-movement command Probability
3783 static int Dp = 96; // Delete command Probability
3784 static int Ip = 97; // Insert command Probability
3785 static int Yp = 98; // Yank command Probability
3786 static int Pp = 99; // Put command Probability
3787 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3788 char chars[20] = "\t012345 abcdABCD-=.$";
3789 char *words[20] = { "this", "is", "a", "test",
3790 "broadcast", "the", "emergency", "of",
3791 "system", "quick", "brown", "fox",
3792 "jumped", "over", "lazy", "dogs",
3793 "back", "January", "Febuary", "March"
3796 "You should have received a copy of the GNU General Public License\n",
3797 "char c, cm, *cmd, *cmd1;\n",
3798 "generate a command by percentages\n",
3799 "Numbers may be typed as a prefix to some commands.\n",
3800 "Quit, discarding changes!\n",
3801 "Forced write, if permission originally not valid.\n",
3802 "In general, any ex or ed command (such as substitute or delete).\n",
3803 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3804 "Please get w/ me and I will go over it with you.\n",
3805 "The following is a list of scheduled, committed changes.\n",
3806 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3807 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3808 "Any question about transactions please contact Sterling Huxley.\n",
3809 "I will try to get back to you by Friday, December 31.\n",
3810 "This Change will be implemented on Friday.\n",
3811 "Let me know if you have problems accessing this;\n",
3812 "Sterling Huxley recently added you to the access list.\n",
3813 "Would you like to go to lunch?\n",
3814 "The last command will be automatically run.\n",
3815 "This is too much english for a computer geek.\n",
3817 char *multilines[20] = {
3818 "You should have received a copy of the GNU General Public License\n",
3819 "char c, cm, *cmd, *cmd1;\n",
3820 "generate a command by percentages\n",
3821 "Numbers may be typed as a prefix to some commands.\n",
3822 "Quit, discarding changes!\n",
3823 "Forced write, if permission originally not valid.\n",
3824 "In general, any ex or ed command (such as substitute or delete).\n",
3825 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3826 "Please get w/ me and I will go over it with you.\n",
3827 "The following is a list of scheduled, committed changes.\n",
3828 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3829 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3830 "Any question about transactions please contact Sterling Huxley.\n",
3831 "I will try to get back to you by Friday, December 31.\n",
3832 "This Change will be implemented on Friday.\n",
3833 "Let me know if you have problems accessing this;\n",
3834 "Sterling Huxley recently added you to the access list.\n",
3835 "Would you like to go to lunch?\n",
3836 "The last command will be automatically run.\n",
3837 "This is too much english for a computer geek.\n",
3840 // create a random command to execute
3841 static void crash_dummy()
3843 static int sleeptime; // how long to pause between commands
3844 char c, cm, *cmd, *cmd1;
3845 int i, cnt, thing, rbi, startrbi, percent;
3847 // "dot" movement commands
3848 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3850 // is there already a command running?
3851 if (readed_for_parse > 0)
3855 sleeptime = 0; // how long to pause between commands
3856 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3857 // generate a command by percentages
3858 percent = (int) lrand48() % 100; // get a number from 0-99
3859 if (percent < Mp) { // Movement commands
3860 // available commands
3863 } else if (percent < Np) { // non-movement commands
3864 cmd = "mz<>\'\""; // available commands
3866 } else if (percent < Dp) { // Delete commands
3867 cmd = "dx"; // available commands
3869 } else if (percent < Ip) { // Inset commands
3870 cmd = "iIaAsrJ"; // available commands
3872 } else if (percent < Yp) { // Yank commands
3873 cmd = "yY"; // available commands
3875 } else if (percent < Pp) { // Put commands
3876 cmd = "pP"; // available commands
3879 // We do not know how to handle this command, try again
3883 // randomly pick one of the available cmds from "cmd[]"
3884 i = (int) lrand48() % strlen(cmd);
3886 if (strchr(":\024", cm))
3887 goto cd0; // dont allow colon or ctrl-T commands
3888 readbuffer[rbi++] = cm; // put cmd into input buffer
3890 // now we have the command-
3891 // there are 1, 2, and multi char commands
3892 // find out which and generate the rest of command as necessary
3893 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3894 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3895 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3896 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3898 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3900 readbuffer[rbi++] = c; // add movement to input buffer
3902 if (strchr("iIaAsc", cm)) { // multi-char commands
3904 // change some thing
3905 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3907 readbuffer[rbi++] = c; // add movement to input buffer
3909 thing = (int) lrand48() % 4; // what thing to insert
3910 cnt = (int) lrand48() % 10; // how many to insert
3911 for (i = 0; i < cnt; i++) {
3912 if (thing == 0) { // insert chars
3913 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3914 } else if (thing == 1) { // insert words
3915 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3916 strcat((char *) readbuffer, " ");
3917 sleeptime = 0; // how fast to type
3918 } else if (thing == 2) { // insert lines
3919 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3920 sleeptime = 0; // how fast to type
3921 } else { // insert multi-lines
3922 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3923 sleeptime = 0; // how fast to type
3926 strcat((char *) readbuffer, "\033");
3928 readed_for_parse = strlen(readbuffer);
3932 (void) mysleep(sleeptime); // sleep 1/100 sec
3935 // test to see if there are any errors
3936 static void crash_test()
3938 static time_t oldtim;
3940 char d[2], msg[BUFSIZ];
3944 strcat((char *) msg, "end<text ");
3946 if (end > textend) {
3947 strcat((char *) msg, "end>textend ");
3950 strcat((char *) msg, "dot<text ");
3953 strcat((char *) msg, "dot>end ");
3955 if (screenbegin < text) {
3956 strcat((char *) msg, "screenbegin<text ");
3958 if (screenbegin > end - 1) {
3959 strcat((char *) msg, "screenbegin>end-1 ");
3962 if (strlen(msg) > 0) {
3964 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3965 totalcmds, last_input_char, msg, SOs, SOn);
3967 while (read(0, d, 1) > 0) {
3968 if (d[0] == '\n' || d[0] == '\r')
3973 tim = (time_t) time((time_t *) 0);
3974 if (tim >= (oldtim + 3)) {
3975 sprintf((char *) status_buffer,
3976 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3977 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3982 #endif /* CONFIG_FEATURE_VI_CRASHME */