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.37 2004/07/20 06:44:46 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 forced = useforce = FALSE;
698 if (strlen((char *) buf) <= 0)
701 buf++; // move past the ':'
703 li = st = ch = i = 0;
705 q = text; // assume 1,$ for the range
707 li = count_lines(text, end - 1);
708 fn = cfn; // default to current file
709 memset(cmd, '\0', BUFSIZ); // clear cmd[]
710 memset(args, '\0', BUFSIZ); // clear args[]
712 // look for optional address(es) :. :1 :1,9 :'q,'a :%
713 buf = get_address(buf, &b, &e);
715 // remember orig command line
718 // get the COMMAND into cmd[]
720 while (*buf != '\0') {
728 strcpy((char *) args, (char *) buf);
729 buf1 = last_char_is((char *)cmd, '!');
732 *buf1 = '\0'; // get rid of !
735 // if there is only one addr, then the addr
736 // is the line number of the single line the
737 // user wants. So, reset the end
738 // pointer to point at end of the "b" line
739 q = find_line(b); // what line is #b
744 // we were given two addrs. change the
745 // end pointer to the addr given by user.
746 r = find_line(e); // what line is #e
750 // ------------ now look for the command ------------
751 i = strlen((char *) cmd);
752 if (i == 0) { // :123CR goto line #123
754 dot = find_line(b); // what line is #b
757 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
758 // :!ls run the <cmd>
759 (void) alarm(0); // wait for input- no alarms
760 place_cursor(rows - 1, 0, FALSE); // go to Status line
761 clear_to_eol(); // clear the line
763 system(orig_buf+1); // run the cmd
765 Hit_Return(); // let user see results
766 (void) alarm(3); // done waiting for input
767 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
768 if (b < 0) { // no addr given- use defaults
769 b = e = count_lines(text, dot);
772 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
773 if (b < 0) { // no addr given- use defaults
774 q = begin_line(dot); // assume .,. for the range
777 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
779 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
782 // don't edit, if the current file has been modified
783 if (file_modified && ! useforce) {
784 psbs("No write since last change (:edit! overrides)");
787 if (strlen(args) > 0) {
788 // the user supplied a file name
790 } else if (cfn != 0 && strlen(cfn) > 0) {
791 // no user supplied name- use the current filename
795 // no user file name, no current name- punt
796 psbs("No current filename");
800 // see if file exists- if not, its just a new file request
801 if ((sr=stat((char*)fn, &st_buf)) < 0) {
802 // This is just a request for a new file creation.
803 // The file_insert below will fail but we get
804 // an empty buffer with a file name. Then the "write"
805 // command can do the create.
807 if ((st_buf.st_mode & (S_IFREG)) == 0) {
808 // This is not a regular file
809 psbs("\"%s\" is not a regular file", fn);
812 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
813 // dont have any read permissions
814 psbs("\"%s\" is not readable", fn);
819 // There is a read-able regular file
820 // make this the current file
821 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
822 free(cfn); // free the old name
823 cfn = q; // remember new cfn
826 // delete all the contents of text[]
827 new_text(2 * file_size(fn));
828 screenbegin = dot = end = text;
831 ch = file_insert(fn, text, file_size(fn));
834 // start empty buf with dummy line
835 (void) char_insert(text, '\n');
838 file_modified = FALSE;
839 #ifdef CONFIG_FEATURE_VI_YANKMARK
840 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
841 free(reg[Ureg]); // free orig line reg- for 'U'
844 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
845 free(reg[YDreg]); // free default yank/delete register
848 for (li = 0; li < 28; li++) {
851 #endif /* CONFIG_FEATURE_VI_YANKMARK */
852 // how many lines in text[]?
853 li = count_lines(text, end - 1);
855 #ifdef CONFIG_FEATURE_VI_READONLY
857 #endif /* CONFIG_FEATURE_VI_READONLY */
859 (sr < 0 ? " [New file]" : ""),
860 #ifdef CONFIG_FEATURE_VI_READONLY
861 ((vi_readonly || readonly) ? " [Read only]" : ""),
862 #endif /* CONFIG_FEATURE_VI_READONLY */
864 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
865 if (b != -1 || e != -1) {
866 ni((Byte *) "No address allowed on this command");
869 if (strlen((char *) args) > 0) {
870 // user wants a new filename
872 cfn = (Byte *) bb_xstrdup((char *) args);
874 // user wants file status info
877 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
878 // print out values of all features
879 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
880 clear_to_eol(); // clear the line
885 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
886 if (b < 0) { // no addr given- use defaults
887 q = begin_line(dot); // assume .,. for the range
890 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
891 clear_to_eol(); // clear the line
893 for (; q <= r; q++) {
897 c_is_no_print = c > 127 && !Isprint(c);
904 } else if (c < ' ' || c == 127) {
915 #ifdef CONFIG_FEATURE_VI_SET
917 #endif /* CONFIG_FEATURE_VI_SET */
919 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
920 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
922 // force end of argv list
929 // don't exit if the file been modified
931 psbs("No write since last change (:%s! overrides)",
932 (*cmd == 'q' ? "quit" : "next"));
935 // are there other file to edit
936 if (*cmd == 'q' && optind < save_argc - 1) {
937 psbs("%d more file to edit", (save_argc - optind - 1));
940 if (*cmd == 'n' && optind >= save_argc - 1) {
941 psbs("No more files to edit");
945 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
947 if (strlen((char *) fn) <= 0) {
948 psbs("No filename given");
951 if (b < 0) { // no addr given- use defaults
952 q = begin_line(dot); // assume "dot"
954 // read after current line- unless user said ":0r foo"
957 #ifdef CONFIG_FEATURE_VI_READONLY
958 l= readonly; // remember current files' status
960 ch = file_insert(fn, q, file_size(fn));
961 #ifdef CONFIG_FEATURE_VI_READONLY
965 goto vc1; // nothing was inserted
966 // how many lines in text[]?
967 li = count_lines(q, q + ch - 1);
969 #ifdef CONFIG_FEATURE_VI_READONLY
971 #endif /* CONFIG_FEATURE_VI_READONLY */
973 #ifdef CONFIG_FEATURE_VI_READONLY
974 ((vi_readonly || readonly) ? " [Read only]" : ""),
975 #endif /* CONFIG_FEATURE_VI_READONLY */
978 // if the insert is before "dot" then we need to update
981 file_modified = TRUE;
983 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
984 if (file_modified && ! useforce) {
985 psbs("No write since last change (:rewind! overrides)");
987 // reset the filenames to edit
988 optind = fn_start - 1;
991 #ifdef CONFIG_FEATURE_VI_SET
992 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
993 i = 0; // offset into args
994 if (strlen((char *) args) == 0) {
995 // print out values of all options
996 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
997 clear_to_eol(); // clear the line
998 printf("----------------------------------------\r\n");
999 #ifdef CONFIG_FEATURE_VI_SETOPTS
1002 printf("autoindent ");
1008 printf("ignorecase ");
1011 printf("showmatch ");
1012 printf("tabstop=%d ", tabstop);
1013 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1017 if (strncasecmp((char *) args, "no", 2) == 0)
1018 i = 2; // ":set noautoindent"
1019 #ifdef CONFIG_FEATURE_VI_SETOPTS
1020 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1021 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1022 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1023 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1024 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1025 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1026 if (ch > 0 && ch < columns - 1)
1029 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1030 #endif /* CONFIG_FEATURE_VI_SET */
1031 #ifdef CONFIG_FEATURE_VI_SEARCH
1032 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1036 // F points to the "find" pattern
1037 // R points to the "replace" pattern
1038 // replace the cmd line delimiters "/" with NULLs
1039 gflag = 0; // global replace flag
1040 c = orig_buf[1]; // what is the delimiter
1041 F = orig_buf + 2; // start of "find"
1042 R = (Byte *) strchr((char *) F, c); // middle delimiter
1043 if (!R) goto colon_s_fail;
1044 *R++ = '\0'; // terminate "find"
1045 buf1 = (Byte *) strchr((char *) R, c);
1046 if (!buf1) goto colon_s_fail;
1047 *buf1++ = '\0'; // terminate "replace"
1048 if (*buf1 == 'g') { // :s/foo/bar/g
1050 gflag++; // turn on gflag
1053 if (b < 0) { // maybe :s/foo/bar/
1054 q = begin_line(dot); // start with cur line
1055 b = count_lines(text, q); // cur line number
1058 e = b; // maybe :.s/foo/bar/
1059 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1060 ls = q; // orig line start
1062 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1064 // we found the "find" pattern- delete it
1065 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1066 // inset the "replace" patern
1067 (void) string_insert(buf1, R); // insert the string
1068 // check for "global" :s/foo/bar/g
1070 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1071 q = buf1 + strlen((char *) R);
1072 goto vc4; // don't let q move past cur line
1078 #endif /* CONFIG_FEATURE_VI_SEARCH */
1079 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1080 psb("%s", vi_Version);
1081 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1082 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1083 (strncasecmp((char *) cmd, "x", i) == 0)) {
1084 // is there a file name to write to?
1085 if (strlen((char *) args) > 0) {
1088 #ifdef CONFIG_FEATURE_VI_READONLY
1089 if ((vi_readonly || readonly) && ! useforce) {
1090 psbs("\"%s\" File is read only", fn);
1093 #endif /* CONFIG_FEATURE_VI_READONLY */
1094 // how many lines in text[]?
1095 li = count_lines(q, r);
1097 // see if file exists- if not, its just a new file request
1099 // if "fn" is not write-able, chmod u+w
1100 // sprintf(syscmd, "chmod u+w %s", fn);
1104 l = file_write(fn, q, r);
1105 if (useforce && forced) {
1107 // sprintf(syscmd, "chmod u-w %s", fn);
1111 psb("\"%s\" %dL, %dC", fn, li, l);
1112 if (q == text && r == end - 1 && l == ch)
1113 file_modified = FALSE;
1114 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1117 #ifdef CONFIG_FEATURE_VI_READONLY
1119 #endif /* CONFIG_FEATURE_VI_READONLY */
1120 #ifdef CONFIG_FEATURE_VI_YANKMARK
1121 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1122 if (b < 0) { // no addr given- use defaults
1123 q = begin_line(dot); // assume .,. for the range
1126 text_yank(q, r, YDreg);
1127 li = count_lines(q, r);
1128 psb("Yank %d lines (%d chars) into [%c]",
1129 li, strlen((char *) reg[YDreg]), what_reg());
1130 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1136 dot = bound_dot(dot); // make sure "dot" is valid
1138 #ifdef CONFIG_FEATURE_VI_SEARCH
1140 psb(":s expression missing delimiters");
1144 static void Hit_Return(void)
1148 standout_start(); // start reverse video
1149 write1("[Hit return to continue]");
1150 standout_end(); // end reverse video
1151 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1153 redraw(TRUE); // force redraw all
1155 #endif /* CONFIG_FEATURE_VI_COLON */
1157 //----- Synchronize the cursor to Dot --------------------------
1158 static void sync_cursor(Byte * d, int *row, int *col)
1160 Byte *beg_cur, *end_cur; // begin and end of "d" line
1161 Byte *beg_scr, *end_scr; // begin and end of screen
1165 beg_cur = begin_line(d); // first char of cur line
1166 end_cur = end_line(d); // last char of cur line
1168 beg_scr = end_scr = screenbegin; // first char of screen
1169 end_scr = end_screen(); // last char of screen
1171 if (beg_cur < screenbegin) {
1172 // "d" is before top line on screen
1173 // how many lines do we have to move
1174 cnt = count_lines(beg_cur, screenbegin);
1176 screenbegin = beg_cur;
1177 if (cnt > (rows - 1) / 2) {
1178 // we moved too many lines. put "dot" in middle of screen
1179 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1180 screenbegin = prev_line(screenbegin);
1183 } else if (beg_cur > end_scr) {
1184 // "d" is after bottom line on screen
1185 // how many lines do we have to move
1186 cnt = count_lines(end_scr, beg_cur);
1187 if (cnt > (rows - 1) / 2)
1188 goto sc1; // too many lines
1189 for (ro = 0; ro < cnt - 1; ro++) {
1190 // move screen begin the same amount
1191 screenbegin = next_line(screenbegin);
1192 // now, move the end of screen
1193 end_scr = next_line(end_scr);
1194 end_scr = end_line(end_scr);
1197 // "d" is on screen- find out which row
1199 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1205 // find out what col "d" is on
1207 do { // drive "co" to correct column
1208 if (*tp == '\n' || *tp == '\0')
1212 co += ((tabstop - 1) - (co % tabstop));
1213 } else if (*tp < ' ' || *tp == 127) {
1214 co++; // display as ^X, use 2 columns
1216 } while (tp++ < d && ++co);
1218 // "co" is the column where "dot" is.
1219 // The screen has "columns" columns.
1220 // The currently displayed columns are 0+offset -- columns+ofset
1221 // |-------------------------------------------------------------|
1223 // offset | |------- columns ----------------|
1225 // If "co" is already in this range then we do not have to adjust offset
1226 // but, we do have to subtract the "offset" bias from "co".
1227 // If "co" is outside this range then we have to change "offset".
1228 // If the first char of a line is a tab the cursor will try to stay
1229 // in column 7, but we have to set offset to 0.
1231 if (co < 0 + offset) {
1234 if (co >= columns + offset) {
1235 offset = co - columns + 1;
1237 // if the first char of the line is a tab, and "dot" is sitting on it
1238 // force offset to 0.
1239 if (d == beg_cur && *d == '\t') {
1248 //----- Text Movement Routines ---------------------------------
1249 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1251 while (p > text && p[-1] != '\n')
1252 p--; // go to cur line B-o-l
1256 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1258 while (p < end - 1 && *p != '\n')
1259 p++; // go to cur line E-o-l
1263 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1265 while (p < end - 1 && *p != '\n')
1266 p++; // go to cur line E-o-l
1267 // Try to stay off of the Newline
1268 if (*p == '\n' && (p - begin_line(p)) > 0)
1273 static Byte *prev_line(Byte * p) // return pointer first char prev line
1275 p = begin_line(p); // goto begining of cur line
1276 if (p[-1] == '\n' && p > text)
1277 p--; // step to prev line
1278 p = begin_line(p); // goto begining of prev line
1282 static Byte *next_line(Byte * p) // return pointer first char next line
1285 if (*p == '\n' && p < end - 1)
1286 p++; // step to next line
1290 //----- Text Information Routines ------------------------------
1291 static Byte *end_screen(void)
1296 // find new bottom line
1298 for (cnt = 0; cnt < rows - 2; cnt++)
1304 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1309 if (stop < start) { // start and stop are backwards- reverse them
1315 stop = end_line(stop); // get to end of this line
1316 for (q = start; q <= stop && q <= end - 1; q++) {
1323 static Byte *find_line(int li) // find begining of line #li
1327 for (q = text; li > 1; li--) {
1333 //----- Dot Movement Routines ----------------------------------
1334 static void dot_left(void)
1336 if (dot > text && dot[-1] != '\n')
1340 static void dot_right(void)
1342 if (dot < end - 1 && *dot != '\n')
1346 static void dot_begin(void)
1348 dot = begin_line(dot); // return pointer to first char cur line
1351 static void dot_end(void)
1353 dot = end_line(dot); // return pointer to last char cur line
1356 static Byte *move_to_col(Byte * p, int l)
1363 if (*p == '\n' || *p == '\0')
1367 co += ((tabstop - 1) - (co % tabstop));
1368 } else if (*p < ' ' || *p == 127) {
1369 co++; // display as ^X, use 2 columns
1371 } while (++co <= l && p++ < end);
1375 static void dot_next(void)
1377 dot = next_line(dot);
1380 static void dot_prev(void)
1382 dot = prev_line(dot);
1385 static void dot_scroll(int cnt, int dir)
1389 for (; cnt > 0; cnt--) {
1392 // ctrl-Y scroll up one line
1393 screenbegin = prev_line(screenbegin);
1396 // ctrl-E scroll down one line
1397 screenbegin = next_line(screenbegin);
1400 // make sure "dot" stays on the screen so we dont scroll off
1401 if (dot < screenbegin)
1403 q = end_screen(); // find new bottom line
1405 dot = begin_line(q); // is dot is below bottom line?
1409 static void dot_skip_over_ws(void)
1412 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1416 static void dot_delete(void) // delete the char at 'dot'
1418 (void) text_hole_delete(dot, dot);
1421 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1423 if (p >= end && end > text) {
1425 indicate_error('1');
1429 indicate_error('2');
1434 //----- Helper Utility Routines --------------------------------
1436 //----------------------------------------------------------------
1437 //----- Char Routines --------------------------------------------
1438 /* Chars that are part of a word-
1439 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1440 * Chars that are Not part of a word (stoppers)
1441 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1442 * Chars that are WhiteSpace
1443 * TAB NEWLINE VT FF RETURN SPACE
1444 * DO NOT COUNT NEWLINE AS WHITESPACE
1447 static Byte *new_screen(int ro, int co)
1452 screensize = ro * co + 8;
1453 screen = (Byte *) xmalloc(screensize);
1454 // initialize the new screen. assume this will be a empty file.
1456 // non-existent text[] lines start with a tilde (~).
1457 for (li = 1; li < ro - 1; li++) {
1458 screen[(li * co) + 0] = '~';
1463 static Byte *new_text(int size)
1466 size = 10240; // have a minimum size for new files
1468 text = (Byte *) xmalloc(size + 8);
1469 memset(text, '\0', size); // clear new text[]
1470 //text += 4; // leave some room for "oops"
1471 textend = text + size - 1;
1472 //textend -= 4; // leave some root for "oops"
1476 #ifdef CONFIG_FEATURE_VI_SEARCH
1477 static int mycmp(Byte * s1, Byte * s2, int len)
1481 i = strncmp((char *) s1, (char *) s2, len);
1482 #ifdef CONFIG_FEATURE_VI_SETOPTS
1484 i = strncasecmp((char *) s1, (char *) s2, len);
1486 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1490 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1492 #ifndef REGEX_SEARCH
1496 len = strlen((char *) pat);
1497 if (dir == FORWARD) {
1498 stop = end - 1; // assume range is p - end-1
1499 if (range == LIMITED)
1500 stop = next_line(p); // range is to next line
1501 for (start = p; start < stop; start++) {
1502 if (mycmp(start, pat, len) == 0) {
1506 } else if (dir == BACK) {
1507 stop = text; // assume range is text - p
1508 if (range == LIMITED)
1509 stop = prev_line(p); // range is to prev line
1510 for (start = p - len; start >= stop; start--) {
1511 if (mycmp(start, pat, len) == 0) {
1516 // pattern not found
1518 #else /*REGEX_SEARCH */
1520 struct re_pattern_buffer preg;
1524 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1530 // assume a LIMITED forward search
1538 // count the number of chars to search over, forward or backward
1542 // RANGE could be negative if we are searching backwards
1545 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1547 // The pattern was not compiled
1548 psbs("bad search pattern: \"%s\": %s", pat, q);
1549 i = 0; // return p if pattern not compiled
1559 // search for the compiled pattern, preg, in p[]
1560 // range < 0- search backward
1561 // range > 0- search forward
1563 // re_search() < 0 not found or error
1564 // re_search() > 0 index of found pattern
1565 // struct pattern char int int int struct reg
1566 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1567 i = re_search(&preg, q, size, 0, range, 0);
1570 i = 0; // return NULL if pattern not found
1573 if (dir == FORWARD) {
1579 #endif /*REGEX_SEARCH */
1581 #endif /* CONFIG_FEATURE_VI_SEARCH */
1583 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1585 if (c == 22) { // Is this an ctrl-V?
1586 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1587 p--; // backup onto ^
1588 refresh(FALSE); // show the ^
1592 file_modified = TRUE; // has the file been modified
1593 } else if (c == 27) { // Is this an ESC?
1596 end_cmd_q(); // stop adding to q
1597 strcpy((char *) status_buffer, " "); // clear the status buffer
1598 if ((p[-1] != '\n') && (dot>text)) {
1601 } else if (c == erase_char) { // Is this a BS
1603 if ((p[-1] != '\n') && (dot>text)) {
1605 p = text_hole_delete(p, p); // shrink buffer 1 char
1606 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1607 // also rmove char from last_modifying_cmd
1608 if (last_modifying_cmd != 0 && strlen((char *) last_modifying_cmd) > 0) {
1611 q = last_modifying_cmd;
1612 q[strlen((char *) q) - 1] = '\0'; // erase BS
1613 q[strlen((char *) q) - 1] = '\0'; // erase prev char
1615 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1618 // insert a char into text[]
1619 Byte *sp; // "save p"
1622 c = '\n'; // translate \r to \n
1623 sp = p; // remember addr of insert
1624 p = stupid_insert(p, c); // insert the char
1625 #ifdef CONFIG_FEATURE_VI_SETOPTS
1626 if (showmatch && strchr(")]}", *sp) != NULL) {
1629 if (autoindent && c == '\n') { // auto indent the new line
1632 q = prev_line(p); // use prev line as templet
1633 for (; isblnk(*q); q++) {
1634 p = stupid_insert(p, *q); // insert the char
1637 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1642 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1644 p = text_hole_make(p, 1);
1647 file_modified = TRUE; // has the file been modified
1653 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1655 Byte *save_dot, *p, *q;
1661 if (strchr("cdy><", c)) {
1662 // these cmds operate on whole lines
1663 p = q = begin_line(p);
1664 for (cnt = 1; cnt < cmdcnt; cnt++) {
1668 } else if (strchr("^%$0bBeEft", c)) {
1669 // These cmds operate on char positions
1670 do_cmd(c); // execute movement cmd
1672 } else if (strchr("wW", c)) {
1673 do_cmd(c); // execute movement cmd
1674 // if we are at the next word's first char
1675 // step back one char
1676 // but check the possibilities when it is true
1677 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1678 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1679 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1680 dot--; // move back off of next word
1681 if (dot > text && *dot == '\n')
1682 dot--; // stay off NL
1684 } else if (strchr("H-k{", c)) {
1685 // these operate on multi-lines backwards
1686 q = end_line(dot); // find NL
1687 do_cmd(c); // execute movement cmd
1690 } else if (strchr("L+j}\r\n", c)) {
1691 // these operate on multi-lines forwards
1692 p = begin_line(dot);
1693 do_cmd(c); // execute movement cmd
1694 dot_end(); // find NL
1697 c = 27; // error- return an ESC char
1710 static int st_test(Byte * p, int type, int dir, Byte * tested)
1720 if (type == S_BEFORE_WS) {
1722 test = ((!isspace(c)) || c == '\n');
1724 if (type == S_TO_WS) {
1726 test = ((!isspace(c)) || c == '\n');
1728 if (type == S_OVER_WS) {
1730 test = ((isspace(c)));
1732 if (type == S_END_PUNCT) {
1734 test = ((ispunct(c)));
1736 if (type == S_END_ALNUM) {
1738 test = ((isalnum(c)) || c == '_');
1744 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1748 while (st_test(p, type, dir, &c)) {
1749 // make sure we limit search to correct number of lines
1750 if (c == '\n' && --linecnt < 1)
1752 if (dir >= 0 && p >= end - 1)
1754 if (dir < 0 && p <= text)
1756 p += dir; // move to next char
1761 // find matching char of pair () [] {}
1762 static Byte *find_pair(Byte * p, Byte c)
1769 dir = 1; // assume forward
1793 for (q = p + dir; text <= q && q < end; q += dir) {
1794 // look for match, count levels of pairs (( ))
1796 level++; // increase pair levels
1798 level--; // reduce pair level
1800 break; // found matching pair
1803 q = NULL; // indicate no match
1807 #ifdef CONFIG_FEATURE_VI_SETOPTS
1808 // show the matching char of a pair, () [] {}
1809 static void showmatching(Byte * p)
1813 // we found half of a pair
1814 q = find_pair(p, *p); // get loc of matching char
1816 indicate_error('3'); // no matching char
1818 // "q" now points to matching pair
1819 save_dot = dot; // remember where we are
1820 dot = q; // go to new loc
1821 refresh(FALSE); // let the user see it
1822 (void) mysleep(40); // give user some time
1823 dot = save_dot; // go back to old loc
1827 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1829 // open a hole in text[]
1830 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1839 cnt = end - src; // the rest of buffer
1840 if (memmove(dest, src, cnt) != dest) {
1841 psbs("can't create room for new characters");
1843 memset(p, ' ', size); // clear new hole
1844 end = end + size; // adjust the new END
1845 file_modified = TRUE; // has the file been modified
1850 // close a hole in text[]
1851 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1856 // move forwards, from beginning
1860 if (q < p) { // they are backward- swap them
1864 hole_size = q - p + 1;
1866 if (src < text || src > end)
1868 if (dest < text || dest >= end)
1871 goto thd_atend; // just delete the end of the buffer
1872 if (memmove(dest, src, cnt) != dest) {
1873 psbs("can't delete the character");
1876 end = end - hole_size; // adjust the new END
1878 dest = end - 1; // make sure dest in below end-1
1880 dest = end = text; // keep pointers valid
1881 file_modified = TRUE; // has the file been modified
1886 // copy text into register, then delete text.
1887 // if dist <= 0, do not include, or go past, a NewLine
1889 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1893 // make sure start <= stop
1895 // they are backwards, reverse them
1901 // we can not cross NL boundaries
1905 // dont go past a NewLine
1906 for (; p + 1 <= stop; p++) {
1908 stop = p; // "stop" just before NewLine
1914 #ifdef CONFIG_FEATURE_VI_YANKMARK
1915 text_yank(start, stop, YDreg);
1916 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1917 if (yf == YANKDEL) {
1918 p = text_hole_delete(start, stop);
1923 static void show_help(void)
1925 puts("These features are available:"
1926 #ifdef CONFIG_FEATURE_VI_SEARCH
1927 "\n\tPattern searches with / and ?"
1928 #endif /* CONFIG_FEATURE_VI_SEARCH */
1929 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1930 "\n\tLast command repeat with \'.\'"
1931 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1932 #ifdef CONFIG_FEATURE_VI_YANKMARK
1933 "\n\tLine marking with 'x"
1934 "\n\tNamed buffers with \"x"
1935 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1936 #ifdef CONFIG_FEATURE_VI_READONLY
1937 "\n\tReadonly if vi is called as \"view\""
1938 "\n\tReadonly with -R command line arg"
1939 #endif /* CONFIG_FEATURE_VI_READONLY */
1940 #ifdef CONFIG_FEATURE_VI_SET
1941 "\n\tSome colon mode commands with \':\'"
1942 #endif /* CONFIG_FEATURE_VI_SET */
1943 #ifdef CONFIG_FEATURE_VI_SETOPTS
1944 "\n\tSettable options with \":set\""
1945 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1946 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1947 "\n\tSignal catching- ^C"
1948 "\n\tJob suspend and resume with ^Z"
1949 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1950 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1951 "\n\tAdapt to window re-sizes"
1952 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1956 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1961 strcpy((char *) buf, ""); // init buf
1962 if (strlen((char *) s) <= 0)
1963 s = (Byte *) "(NULL)";
1964 for (; *s > '\0'; s++) {
1968 c_is_no_print = c > 127 && !Isprint(c);
1969 if (c_is_no_print) {
1970 strcat((char *) buf, SOn);
1973 if (c < ' ' || c == 127) {
1974 strcat((char *) buf, "^");
1981 strcat((char *) buf, (char *) b);
1983 strcat((char *) buf, SOs);
1985 strcat((char *) buf, "$");
1990 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1991 static void start_new_cmd_q(Byte c)
1994 free(last_modifying_cmd);
1995 // get buffer for new cmd
1996 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1997 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1998 // if there is a current cmd count put it in the buffer first
2000 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2001 // save char c onto queue
2002 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2007 static void end_cmd_q(void)
2009 #ifdef CONFIG_FEATURE_VI_YANKMARK
2010 YDreg = 26; // go back to default Yank/Delete reg
2011 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2015 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2017 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2018 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2022 i = strlen((char *) s);
2023 p = text_hole_make(p, i);
2024 strncpy((char *) p, (char *) s, i);
2025 for (cnt = 0; *s != '\0'; s++) {
2029 #ifdef CONFIG_FEATURE_VI_YANKMARK
2030 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2031 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2034 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2036 #ifdef CONFIG_FEATURE_VI_YANKMARK
2037 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2042 if (q < p) { // they are backwards- reverse them
2049 free(t); // if already a yank register, free it
2050 t = (Byte *) xmalloc(cnt + 1); // get a new register
2051 memset(t, '\0', cnt + 1); // clear new text[]
2052 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2057 static Byte what_reg(void)
2063 c = 'D'; // default to D-reg
2064 if (0 <= YDreg && YDreg <= 25)
2065 c = 'a' + (Byte) YDreg;
2073 static void check_context(Byte cmd)
2075 // A context is defined to be "modifying text"
2076 // Any modifying command establishes a new context.
2078 if (dot < context_start || dot > context_end) {
2079 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2080 // we are trying to modify text[]- make this the current context
2081 mark[27] = mark[26]; // move cur to prev
2082 mark[26] = dot; // move local to cur
2083 context_start = prev_line(prev_line(dot));
2084 context_end = next_line(next_line(dot));
2085 //loiter= start_loiter= now;
2091 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2095 // the current context is in mark[26]
2096 // the previous context is in mark[27]
2097 // only swap context if other context is valid
2098 if (text <= mark[27] && mark[27] <= end - 1) {
2100 mark[27] = mark[26];
2102 p = mark[26]; // where we are going- previous context
2103 context_start = prev_line(prev_line(prev_line(p)));
2104 context_end = next_line(next_line(next_line(p)));
2108 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2110 static int isblnk(Byte c) // is the char a blank or tab
2112 return (c == ' ' || c == '\t');
2115 //----- Set terminal attributes --------------------------------
2116 static void rawmode(void)
2118 tcgetattr(0, &term_orig);
2119 term_vi = term_orig;
2120 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2121 term_vi.c_iflag &= (~IXON & ~ICRNL);
2122 term_vi.c_oflag &= (~ONLCR);
2123 term_vi.c_cc[VMIN] = 1;
2124 term_vi.c_cc[VTIME] = 0;
2125 erase_char = term_vi.c_cc[VERASE];
2126 tcsetattr(0, TCSANOW, &term_vi);
2129 static void cookmode(void)
2132 tcsetattr(0, TCSANOW, &term_orig);
2135 //----- Come here when we get a window resize signal ---------
2136 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2137 static void winch_sig(int sig)
2139 signal(SIGWINCH, winch_sig);
2140 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2142 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2143 new_screen(rows, columns); // get memory for virtual screen
2144 redraw(TRUE); // re-draw the screen
2147 //----- Come here when we get a continue signal -------------------
2148 static void cont_sig(int sig)
2150 rawmode(); // terminal to "raw"
2151 *status_buffer = '\0'; // clear the status buffer
2152 redraw(TRUE); // re-draw the screen
2154 signal(SIGTSTP, suspend_sig);
2155 signal(SIGCONT, SIG_DFL);
2156 kill(my_pid, SIGCONT);
2159 //----- Come here when we get a Suspend signal -------------------
2160 static void suspend_sig(int sig)
2162 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2163 clear_to_eol(); // Erase to end of line
2164 cookmode(); // terminal to "cooked"
2166 signal(SIGCONT, cont_sig);
2167 signal(SIGTSTP, SIG_DFL);
2168 kill(my_pid, SIGTSTP);
2171 //----- Come here when we get a signal ---------------------------
2172 static void catch_sig(int sig)
2174 signal(SIGHUP, catch_sig);
2175 signal(SIGINT, catch_sig);
2176 signal(SIGTERM, catch_sig);
2177 signal(SIGALRM, catch_sig);
2179 longjmp(restart, sig);
2182 //----- Come here when we get a core dump signal -----------------
2183 static void core_sig(int sig)
2185 signal(SIGQUIT, core_sig);
2186 signal(SIGILL, core_sig);
2187 signal(SIGTRAP, core_sig);
2188 signal(SIGIOT, core_sig);
2189 signal(SIGABRT, core_sig);
2190 signal(SIGFPE, core_sig);
2191 signal(SIGBUS, core_sig);
2192 signal(SIGSEGV, core_sig);
2194 signal(SIGSYS, core_sig);
2197 if(sig) { // signaled
2198 dot = bound_dot(dot); // make sure "dot" is valid
2200 longjmp(restart, sig);
2203 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2205 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2207 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2212 tv.tv_usec = hund * 10000;
2213 select(1, &rfds, NULL, NULL, &tv);
2214 return (FD_ISSET(0, &rfds));
2217 static Byte readbuffer[BUFSIZ];
2218 static int readed_for_parse;
2220 //----- IO Routines --------------------------------------------
2221 static Byte readit(void) // read (maybe cursor) key from stdin
2230 static const struct esc_cmds esccmds[] = {
2231 {"OA", (Byte) VI_K_UP}, // cursor key Up
2232 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2233 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2234 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2235 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2236 {"OF", (Byte) VI_K_END}, // Cursor Key End
2237 {"[A", (Byte) VI_K_UP}, // cursor key Up
2238 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2239 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2240 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2241 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2242 {"[F", (Byte) VI_K_END}, // Cursor Key End
2243 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2244 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2245 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2246 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2247 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2248 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2249 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2250 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2251 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2252 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2253 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2254 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2255 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2256 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2257 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2258 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2259 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2260 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2261 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2262 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2263 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2266 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2268 (void) alarm(0); // turn alarm OFF while we wait for input
2270 n = readed_for_parse;
2271 // get input from User- are there already input chars in Q?
2274 // the Q is empty, wait for a typed char
2275 n = read(0, readbuffer, BUFSIZ - 1);
2278 goto ri0; // interrupted sys call
2281 if (errno == EFAULT)
2283 if (errno == EINVAL)
2291 if (readbuffer[0] == 27) {
2292 // This is an ESC char. Is this Esc sequence?
2293 // Could be bare Esc key. See if there are any
2294 // more chars to read after the ESC. This would
2295 // be a Function or Cursor Key sequence.
2299 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2301 // keep reading while there are input chars and room in buffer
2302 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2303 // read the rest of the ESC string
2304 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2310 readed_for_parse = n;
2313 if(c == 27 && n > 1) {
2314 // Maybe cursor or function key?
2315 const struct esc_cmds *eindex;
2317 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2318 int cnt = strlen(eindex->seq);
2322 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2324 // is a Cursor key- put derived value back into Q
2326 // for squeeze out the ESC sequence
2330 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2331 /* defined ESC sequence not found, set only one ESC */
2337 // remove key sequence from Q
2338 readed_for_parse -= n;
2339 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2340 (void) alarm(3); // we are done waiting for input, turn alarm ON
2344 //----- IO Routines --------------------------------------------
2345 static Byte get_one_char()
2349 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2350 // ! adding2q && ioq == 0 read()
2351 // ! adding2q && ioq != 0 *ioq
2352 // adding2q *last_modifying_cmd= read()
2354 // we are not adding to the q.
2355 // but, we may be reading from a q
2357 // there is no current q, read from STDIN
2358 c = readit(); // get the users input
2360 // there is a queue to get chars from first
2363 // the end of the q, read from STDIN
2365 ioq_start = ioq = 0;
2366 c = readit(); // get the users input
2370 // adding STDIN chars to q
2371 c = readit(); // get the users input
2372 if (last_modifying_cmd != 0) {
2373 int len = strlen((char *) last_modifying_cmd);
2374 if (len + 1 >= BUFSIZ) {
2375 psbs("last_modifying_cmd overrun");
2377 // add new char to q
2378 last_modifying_cmd[len] = c;
2382 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2383 c = readit(); // get the users input
2384 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2385 return (c); // return the char, where ever it came from
2388 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2393 static Byte *obufp = NULL;
2395 strcpy((char *) buf, (char *) prompt);
2396 *status_buffer = '\0'; // clear the status buffer
2397 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2398 clear_to_eol(); // clear the line
2399 write1(prompt); // write out the :, /, or ? prompt
2401 for (i = strlen((char *) buf); i < BUFSIZ;) {
2402 c = get_one_char(); // read user input
2403 if (c == '\n' || c == '\r' || c == 27)
2404 break; // is this end of input
2405 if (c == erase_char) { // user wants to erase prev char
2406 i--; // backup to prev char
2407 buf[i] = '\0'; // erase the char
2408 buf[i + 1] = '\0'; // null terminate buffer
2409 write1("\b \b"); // erase char on screen
2410 if (i <= 0) { // user backs up before b-o-l, exit
2414 buf[i] = c; // save char in buffer
2415 buf[i + 1] = '\0'; // make sure buffer is null terminated
2416 putchar(c); // echo the char back to user
2422 obufp = (Byte *) bb_xstrdup((char *) buf);
2426 static int file_size(const Byte * fn) // what is the byte size of "fn"
2431 if (fn == 0 || strlen(fn) <= 0)
2434 sr = stat((char *) fn, &st_buf); // see if file exists
2436 cnt = (int) st_buf.st_size;
2441 static int file_insert(Byte * fn, Byte * p, int size)
2446 #ifdef CONFIG_FEATURE_VI_READONLY
2448 #endif /* CONFIG_FEATURE_VI_READONLY */
2449 if (fn == 0 || strlen((char*) fn) <= 0) {
2450 psbs("No filename given");
2454 // OK- this is just a no-op
2459 psbs("Trying to insert a negative number (%d) of characters", size);
2462 if (p < text || p > end) {
2463 psbs("Trying to insert file outside of memory");
2467 // see if we can open the file
2468 #ifdef CONFIG_FEATURE_VI_READONLY
2469 if (vi_readonly) goto fi1; // do not try write-mode
2471 fd = open((char *) fn, O_RDWR); // assume read & write
2473 // could not open for writing- maybe file is read only
2474 #ifdef CONFIG_FEATURE_VI_READONLY
2477 fd = open((char *) fn, O_RDONLY); // try read-only
2479 psbs("\"%s\" %s", fn, "could not open file");
2482 #ifdef CONFIG_FEATURE_VI_READONLY
2483 // got the file- read-only
2485 #endif /* CONFIG_FEATURE_VI_READONLY */
2487 p = text_hole_make(p, size);
2488 cnt = read(fd, p, size);
2492 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2493 psbs("could not read file \"%s\"", fn);
2494 } else if (cnt < size) {
2495 // There was a partial read, shrink unused space text[]
2496 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2497 psbs("could not read all of file \"%s\"", fn);
2500 file_modified = TRUE;
2505 static int file_write(Byte * fn, Byte * first, Byte * last)
2507 int fd, cnt, charcnt;
2510 psbs("No current filename");
2514 // FIXIT- use the correct umask()
2515 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2518 cnt = last - first + 1;
2519 charcnt = write(fd, first, cnt);
2520 if (charcnt == cnt) {
2522 //file_modified= FALSE; // the file has not been modified
2530 //----- Terminal Drawing ---------------------------------------
2531 // The terminal is made up of 'rows' line of 'columns' columns.
2532 // classically this would be 24 x 80.
2533 // screen coordinates
2539 // 23,0 ... 23,79 status line
2542 //----- Move the cursor to row x col (count from 0, not 1) -------
2543 static void place_cursor(int row, int col, int opti)
2547 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2550 // char cm3[BUFSIZ];
2552 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2554 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2556 if (row < 0) row = 0;
2557 if (row >= rows) row = rows - 1;
2558 if (col < 0) col = 0;
2559 if (col >= columns) col = columns - 1;
2561 //----- 1. Try the standard terminal ESC sequence
2562 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2564 if (! opti) goto pc0;
2566 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2567 //----- find the minimum # of chars to move cursor -------------
2568 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2569 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2571 // move to the correct row
2572 while (row < Rrow) {
2573 // the cursor has to move up
2577 while (row > Rrow) {
2578 // the cursor has to move down
2579 strcat(cm2, CMdown);
2583 // now move to the correct column
2584 strcat(cm2, "\r"); // start at col 0
2585 // just send out orignal source char to get to correct place
2586 screenp = &screen[row * columns]; // start of screen line
2587 strncat(cm2, screenp, col);
2589 //----- 3. Try some other way of moving cursor
2590 //---------------------------------------------
2592 // pick the shortest cursor motion to send out
2594 if (strlen(cm2) < strlen(cm)) {
2596 } /* else if (strlen(cm3) < strlen(cm)) {
2599 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2601 write1(cm); // move the cursor
2604 //----- Erase from cursor to end of line -----------------------
2605 static void clear_to_eol()
2607 write1(Ceol); // Erase from cursor to end of line
2610 //----- Erase from cursor to end of screen -----------------------
2611 static void clear_to_eos()
2613 write1(Ceos); // Erase from cursor to end of screen
2616 //----- Start standout mode ------------------------------------
2617 static void standout_start() // send "start reverse video" sequence
2619 write1(SOs); // Start reverse video mode
2622 //----- End standout mode --------------------------------------
2623 static void standout_end() // send "end reverse video" sequence
2625 write1(SOn); // End reverse video mode
2628 //----- Flash the screen --------------------------------------
2629 static void flash(int h)
2631 standout_start(); // send "start reverse video" sequence
2634 standout_end(); // send "end reverse video" sequence
2638 static void Indicate_Error(void)
2640 #ifdef CONFIG_FEATURE_VI_CRASHME
2642 return; // generate a random command
2643 #endif /* CONFIG_FEATURE_VI_CRASHME */
2645 write1(bell); // send out a bell character
2651 //----- Screen[] Routines --------------------------------------
2652 //----- Erase the Screen[] memory ------------------------------
2653 static void screen_erase()
2655 memset(screen, ' ', screensize); // clear new screen
2658 //----- Draw the status line at bottom of the screen -------------
2659 static void show_status_line(void)
2661 static int last_cksum;
2664 cnt = strlen((char *) status_buffer);
2665 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
2666 // don't write the status line unless it changes
2667 if (cnt > 0 && last_cksum != cksum) {
2668 last_cksum= cksum; // remember if we have seen this line
2669 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2670 write1(status_buffer);
2672 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2676 //----- format the status buffer, the bottom line of screen ------
2677 // print status buffer, with STANDOUT mode
2678 static void psbs(const char *format, ...)
2682 va_start(args, format);
2683 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2684 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
2686 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2692 // print status buffer
2693 static void psb(const char *format, ...)
2697 va_start(args, format);
2698 vsprintf((char *) status_buffer, format, args);
2703 static void ni(Byte * s) // display messages
2707 print_literal(buf, s);
2708 psbs("\'%s\' is not implemented", buf);
2711 static void edit_status(void) // show file status on status line
2713 int cur, tot, percent;
2715 cur = count_lines(text, dot);
2716 tot = count_lines(text, end - 1);
2717 // current line percent
2718 // ------------- ~~ ----------
2721 percent = (100 * cur) / tot;
2727 #ifdef CONFIG_FEATURE_VI_READONLY
2729 #endif /* CONFIG_FEATURE_VI_READONLY */
2730 "%s line %d of %d --%d%%--",
2731 (cfn != 0 ? (char *) cfn : "No file"),
2732 #ifdef CONFIG_FEATURE_VI_READONLY
2733 ((vi_readonly || readonly) ? " [Read only]" : ""),
2734 #endif /* CONFIG_FEATURE_VI_READONLY */
2735 (file_modified ? " [modified]" : ""),
2739 //----- Force refresh of all Lines -----------------------------
2740 static void redraw(int full_screen)
2742 place_cursor(0, 0, FALSE); // put cursor in correct place
2743 clear_to_eos(); // tel terminal to erase display
2744 screen_erase(); // erase the internal screen buffer
2745 refresh(full_screen); // this will redraw the entire display
2748 //----- Format a text[] line into a buffer ---------------------
2749 static void format_line(Byte *dest, Byte *src, int li)
2754 for (co= 0; co < MAX_SCR_COLS; co++) {
2755 c= ' '; // assume blank
2756 if (li > 0 && co == 0) {
2757 c = '~'; // not first line, assume Tilde
2759 // are there chars in text[] and have we gone past the end
2760 if (text < end && src < end) {
2765 if (c > 127 && !Isprint(c)) {
2768 if (c < ' ' || c == 127) {
2772 for (; (co % tabstop) != (tabstop - 1); co++) {
2780 c += '@'; // make it visible
2783 // the co++ is done here so that the column will
2784 // not be overwritten when we blank-out the rest of line
2791 //----- Refresh the changed screen lines -----------------------
2792 // Copy the source line from text[] into the buffer and note
2793 // if the current screenline is different from the new buffer.
2794 // If they differ then that line needs redrawing on the terminal.
2796 static void refresh(int full_screen)
2798 static int old_offset;
2800 Byte buf[MAX_SCR_COLS];
2801 Byte *tp, *sp; // pointer into text[] and screen[]
2802 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2803 int last_li= -2; // last line that changed- for optimizing cursor movement
2804 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2806 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2808 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2809 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2810 tp = screenbegin; // index into text[] of top line
2812 // compare text[] to screen[] and mark screen[] lines that need updating
2813 for (li = 0; li < rows - 1; li++) {
2814 int cs, ce; // column start & end
2815 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2816 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2817 // format current text line into buf
2818 format_line(buf, tp, li);
2820 // skip to the end of the current text[] line
2821 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2823 // see if there are any changes between vitual screen and buf
2824 changed = FALSE; // assume no change
2827 sp = &screen[li * columns]; // start of screen line
2829 // force re-draw of every single column from 0 - columns-1
2832 // compare newly formatted buffer with virtual screen
2833 // look forward for first difference between buf and screen
2834 for ( ; cs <= ce; cs++) {
2835 if (buf[cs + offset] != sp[cs]) {
2836 changed = TRUE; // mark for redraw
2841 // look backward for last difference between buf and screen
2842 for ( ; ce >= cs; ce--) {
2843 if (buf[ce + offset] != sp[ce]) {
2844 changed = TRUE; // mark for redraw
2848 // now, cs is index of first diff, and ce is index of last diff
2850 // if horz offset has changed, force a redraw
2851 if (offset != old_offset) {
2856 // make a sanity check of columns indexes
2858 if (ce > columns-1) ce= columns-1;
2859 if (cs > ce) { cs= 0; ce= columns-1; }
2860 // is there a change between vitual screen and buf
2862 // copy changed part of buffer to virtual screen
2863 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2865 // move cursor to column of first change
2866 if (offset != old_offset) {
2867 // opti_cur_move is still too stupid
2868 // to handle offsets correctly
2869 place_cursor(li, cs, FALSE);
2871 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2872 // if this just the next line
2873 // try to optimize cursor movement
2874 // otherwise, use standard ESC sequence
2875 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2877 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2878 place_cursor(li, cs, FALSE); // use standard ESC sequence
2879 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2882 // write line out to terminal
2892 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2894 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2898 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2899 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2902 place_cursor(crow, ccol, FALSE);
2903 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2905 if (offset != old_offset)
2906 old_offset = offset;
2909 //---------------------------------------------------------------------
2910 //----- the Ascii Chart -----------------------------------------------
2912 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2913 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2914 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2915 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2916 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2917 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2918 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2919 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2920 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2921 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2922 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2923 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2924 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2925 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2926 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2927 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2928 //---------------------------------------------------------------------
2930 //----- Execute a Vi Command -----------------------------------
2931 static void do_cmd(Byte c)
2933 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2934 int cnt, i, j, dir, yf;
2936 c1 = c; // quiet the compiler
2937 cnt = yf = dir = 0; // quiet the compiler
2938 p = q = save_dot = msg = buf; // quiet the compiler
2939 memset(buf, '\0', 9); // clear buf
2941 /* if this is a cursor key, skip these checks */
2954 if (cmd_mode == 2) {
2955 // flip-flop Insert/Replace mode
2956 if (c == VI_K_INSERT) goto dc_i;
2957 // we are 'R'eplacing the current *dot with new char
2959 // don't Replace past E-o-l
2960 cmd_mode = 1; // convert to insert
2962 if (1 <= c || Isprint(c)) {
2964 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2965 dot = char_insert(dot, c); // insert new char
2970 if (cmd_mode == 1) {
2971 // hitting "Insert" twice means "R" replace mode
2972 if (c == VI_K_INSERT) goto dc5;
2973 // insert the char c at "dot"
2974 if (1 <= c || Isprint(c)) {
2975 dot = char_insert(dot, c);
2990 #ifdef CONFIG_FEATURE_VI_CRASHME
2991 case 0x14: // dc4 ctrl-T
2992 crashme = (crashme == 0) ? 1 : 0;
2994 #endif /* CONFIG_FEATURE_VI_CRASHME */
3023 //case 'u': // u- FIXME- there is no undo
3025 default: // unrecognised command
3034 end_cmd_q(); // stop adding to q
3035 case 0x00: // nul- ignore
3037 case 2: // ctrl-B scroll up full screen
3038 case VI_K_PAGEUP: // Cursor Key Page Up
3039 dot_scroll(rows - 2, -1);
3041 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3042 case 0x03: // ctrl-C interrupt
3043 longjmp(restart, 1);
3045 case 26: // ctrl-Z suspend
3046 suspend_sig(SIGTSTP);
3048 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3049 case 4: // ctrl-D scroll down half screen
3050 dot_scroll((rows - 2) / 2, 1);
3052 case 5: // ctrl-E scroll down one line
3055 case 6: // ctrl-F scroll down full screen
3056 case VI_K_PAGEDOWN: // Cursor Key Page Down
3057 dot_scroll(rows - 2, 1);
3059 case 7: // ctrl-G show current status
3062 case 'h': // h- move left
3063 case VI_K_LEFT: // cursor key Left
3064 case 8: // ctrl-H- move left (This may be ERASE char)
3065 case 127: // DEL- move left (This may be ERASE char)
3071 case 10: // Newline ^J
3072 case 'j': // j- goto next line, same col
3073 case VI_K_DOWN: // cursor key Down
3077 dot_next(); // go to next B-o-l
3078 dot = move_to_col(dot, ccol + offset); // try stay in same col
3080 case 12: // ctrl-L force redraw whole screen
3081 case 18: // ctrl-R force redraw
3082 place_cursor(0, 0, FALSE); // put cursor in correct place
3083 clear_to_eos(); // tel terminal to erase display
3085 screen_erase(); // erase the internal screen buffer
3086 refresh(TRUE); // this will redraw the entire display
3088 case 13: // Carriage Return ^M
3089 case '+': // +- goto next line
3096 case 21: // ctrl-U scroll up half screen
3097 dot_scroll((rows - 2) / 2, -1);
3099 case 25: // ctrl-Y scroll up one line
3105 cmd_mode = 0; // stop insrting
3107 *status_buffer = '\0'; // clear status buffer
3109 case ' ': // move right
3110 case 'l': // move right
3111 case VI_K_RIGHT: // Cursor Key Right
3117 #ifdef CONFIG_FEATURE_VI_YANKMARK
3118 case '"': // "- name a register to use for Delete/Yank
3119 c1 = get_one_char();
3127 case '\'': // '- goto a specific mark
3128 c1 = get_one_char();
3134 if (text <= q && q < end) {
3136 dot_begin(); // go to B-o-l
3139 } else if (c1 == '\'') { // goto previous context
3140 dot = swap_context(dot); // swap current and previous context
3141 dot_begin(); // go to B-o-l
3147 case 'm': // m- Mark a line
3148 // this is really stupid. If there are any inserts or deletes
3149 // between text[0] and dot then this mark will not point to the
3150 // correct location! It could be off by many lines!
3151 // Well..., at least its quick and dirty.
3152 c1 = get_one_char();
3156 // remember the line
3157 mark[(int) c1] = dot;
3162 case 'P': // P- Put register before
3163 case 'p': // p- put register after
3166 psbs("Nothing in register %c", what_reg());
3169 // are we putting whole lines or strings
3170 if (strchr((char *) p, '\n') != NULL) {
3172 dot_begin(); // putting lines- Put above
3175 // are we putting after very last line?
3176 if (end_line(dot) == (end - 1)) {
3177 dot = end; // force dot to end of text[]
3179 dot_next(); // next line, then put before
3184 dot_right(); // move to right, can move to NL
3186 dot = string_insert(dot, p); // insert the string
3187 end_cmd_q(); // stop adding to q
3189 case 'U': // U- Undo; replace current line with original version
3190 if (reg[Ureg] != 0) {
3191 p = begin_line(dot);
3193 p = text_hole_delete(p, q); // delete cur line
3194 p = string_insert(p, reg[Ureg]); // insert orig line
3199 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3200 case '$': // $- goto end of line
3201 case VI_K_END: // Cursor Key End
3205 dot = end_line(dot);
3207 case '%': // %- find matching char of pair () [] {}
3208 for (q = dot; q < end && *q != '\n'; q++) {
3209 if (strchr("()[]{}", *q) != NULL) {
3210 // we found half of a pair
3211 p = find_pair(q, *q);
3223 case 'f': // f- forward to a user specified char
3224 last_forward_char = get_one_char(); // get the search char
3226 // dont separate these two commands. 'f' depends on ';'
3228 //**** fall thru to ... 'i'
3229 case ';': // ;- look at rest of line for last forward char
3233 if (last_forward_char == 0) break;
3235 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3238 if (*q == last_forward_char)
3241 case '-': // -- goto prev line
3248 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3249 case '.': // .- repeat the last modifying command
3250 // Stuff the last_modifying_cmd back into stdin
3251 // and let it be re-executed.
3252 if (last_modifying_cmd != 0) {
3253 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3256 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3257 #ifdef CONFIG_FEATURE_VI_SEARCH
3258 case '?': // /- search for a pattern
3259 case '/': // /- search for a pattern
3262 q = get_input_line(buf); // get input line- use "status line"
3263 if (strlen((char *) q) == 1)
3264 goto dc3; // if no pat re-use old pat
3265 if (strlen((char *) q) > 1) { // new pat- save it and find
3266 // there is a new pat
3267 free(last_search_pattern);
3268 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3269 goto dc3; // now find the pattern
3271 // user changed mind and erased the "/"- do nothing
3273 case 'N': // N- backward search for last pattern
3277 dir = BACK; // assume BACKWARD search
3279 if (last_search_pattern[0] == '?') {
3283 goto dc4; // now search for pattern
3285 case 'n': // n- repeat search for last pattern
3286 // search rest of text[] starting at next char
3287 // if search fails return orignal "p" not the "p+1" address
3292 if (last_search_pattern == 0) {
3293 msg = (Byte *) "No previous regular expression";
3296 if (last_search_pattern[0] == '/') {
3297 dir = FORWARD; // assume FORWARD search
3300 if (last_search_pattern[0] == '?') {
3305 q = char_search(p, last_search_pattern + 1, dir, FULL);
3307 dot = q; // good search, update "dot"
3311 // no pattern found between "dot" and "end"- continue at top
3316 q = char_search(p, last_search_pattern + 1, dir, FULL);
3317 if (q != NULL) { // found something
3318 dot = q; // found new pattern- goto it
3319 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3321 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3324 msg = (Byte *) "Pattern not found";
3329 case '{': // {- move backward paragraph
3330 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3331 if (q != NULL) { // found blank line
3332 dot = next_line(q); // move to next blank line
3335 case '}': // }- move forward paragraph
3336 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3337 if (q != NULL) { // found blank line
3338 dot = next_line(q); // move to next blank line
3341 #endif /* CONFIG_FEATURE_VI_SEARCH */
3342 case '0': // 0- goto begining of line
3352 if (c == '0' && cmdcnt < 1) {
3353 dot_begin(); // this was a standalone zero
3355 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3358 case ':': // :- the colon mode commands
3359 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3360 #ifdef CONFIG_FEATURE_VI_COLON
3361 colon(p); // execute the command
3362 #else /* CONFIG_FEATURE_VI_COLON */
3364 p++; // move past the ':'
3365 cnt = strlen((char *) p);
3368 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3369 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3370 if (file_modified && p[1] != '!') {
3371 psbs("No write since last change (:quit! overrides)");
3375 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3376 strncasecmp((char *) p, "wq", cnt) == 0 ||
3377 strncasecmp((char *) p, "x", cnt) == 0) {
3378 cnt = file_write(cfn, text, end - 1);
3379 file_modified = FALSE;
3380 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3381 if (p[0] == 'x' || p[1] == 'q') {
3384 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3385 edit_status(); // show current file status
3386 } else if (sscanf((char *) p, "%d", &j) > 0) {
3387 dot = find_line(j); // go to line # j
3389 } else { // unrecognised cmd
3392 #endif /* CONFIG_FEATURE_VI_COLON */
3394 case '<': // <- Left shift something
3395 case '>': // >- Right shift something
3396 cnt = count_lines(text, dot); // remember what line we are on
3397 c1 = get_one_char(); // get the type of thing to delete
3398 find_range(&p, &q, c1);
3399 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3402 i = count_lines(p, q); // # of lines we are shifting
3403 for ( ; i > 0; i--, p = next_line(p)) {
3405 // shift left- remove tab or 8 spaces
3407 // shrink buffer 1 char
3408 (void) text_hole_delete(p, p);
3409 } else if (*p == ' ') {
3410 // we should be calculating columns, not just SPACE
3411 for (j = 0; *p == ' ' && j < tabstop; j++) {
3412 (void) text_hole_delete(p, p);
3415 } else if (c == '>') {
3416 // shift right -- add tab or 8 spaces
3417 (void) char_insert(p, '\t');
3420 dot = find_line(cnt); // what line were we on
3422 end_cmd_q(); // stop adding to q
3424 case 'A': // A- append at e-o-l
3425 dot_end(); // go to e-o-l
3426 //**** fall thru to ... 'a'
3427 case 'a': // a- append after current char
3432 case 'B': // B- back a blank-delimited Word
3433 case 'E': // E- end of a blank-delimited word
3434 case 'W': // W- forward a blank-delimited word
3441 if (c == 'W' || isspace(dot[dir])) {
3442 dot = skip_thing(dot, 1, dir, S_TO_WS);
3443 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3446 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3448 case 'C': // C- Change to e-o-l
3449 case 'D': // D- delete to e-o-l
3451 dot = dollar_line(dot); // move to before NL
3452 // copy text into a register and delete
3453 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3455 goto dc_i; // start inserting
3456 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3458 end_cmd_q(); // stop adding to q
3459 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3461 case 'G': // G- goto to a line number (default= E-O-F)
3462 dot = end - 1; // assume E-O-F
3464 dot = find_line(cmdcnt); // what line is #cmdcnt
3468 case 'H': // H- goto top line on screen
3470 if (cmdcnt > (rows - 1)) {
3471 cmdcnt = (rows - 1);
3478 case 'I': // I- insert before first non-blank
3481 //**** fall thru to ... 'i'
3482 case 'i': // i- insert before current char
3483 case VI_K_INSERT: // Cursor Key Insert
3485 cmd_mode = 1; // start insrting
3486 psb("-- Insert --");
3488 case 'J': // J- join current and next lines together
3492 dot_end(); // move to NL
3493 if (dot < end - 1) { // make sure not last char in text[]
3494 *dot++ = ' '; // replace NL with space
3495 while (isblnk(*dot)) { // delete leading WS
3499 end_cmd_q(); // stop adding to q
3501 case 'L': // L- goto bottom line on screen
3503 if (cmdcnt > (rows - 1)) {
3504 cmdcnt = (rows - 1);
3512 case 'M': // M- goto middle line on screen
3514 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3515 dot = next_line(dot);
3517 case 'O': // O- open a empty line above
3519 p = begin_line(dot);
3520 if (p[-1] == '\n') {
3522 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3524 dot = char_insert(dot, '\n');
3527 dot = char_insert(dot, '\n'); // i\n ESC
3532 case 'R': // R- continuous Replace char
3535 psb("-- Replace --");
3537 case 'X': // X- delete char before dot
3538 case 'x': // x- delete the current char
3539 case 's': // s- substitute the current char
3546 if (dot[dir] != '\n') {
3548 dot--; // delete prev char
3549 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3552 goto dc_i; // start insrting
3553 end_cmd_q(); // stop adding to q
3555 case 'Z': // Z- if modified, {write}; exit
3556 // ZZ means to save file (if necessary), then exit
3557 c1 = get_one_char();
3563 #ifdef CONFIG_FEATURE_VI_READONLY
3566 #endif /* CONFIG_FEATURE_VI_READONLY */
3568 cnt = file_write(cfn, text, end - 1);
3569 if (cnt == (end - 1 - text + 1)) {
3576 case '^': // ^- move to first non-blank on line
3580 case 'b': // b- back a word
3581 case 'e': // e- end of word
3588 if ((dot + dir) < text || (dot + dir) > end - 1)
3591 if (isspace(*dot)) {
3592 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3594 if (isalnum(*dot) || *dot == '_') {
3595 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3596 } else if (ispunct(*dot)) {
3597 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3600 case 'c': // c- change something
3601 case 'd': // d- delete something
3602 #ifdef CONFIG_FEATURE_VI_YANKMARK
3603 case 'y': // y- yank something
3604 case 'Y': // Y- Yank a line
3605 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3606 yf = YANKDEL; // assume either "c" or "d"
3607 #ifdef CONFIG_FEATURE_VI_YANKMARK
3608 if (c == 'y' || c == 'Y')
3610 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3613 c1 = get_one_char(); // get the type of thing to delete
3614 find_range(&p, &q, c1);
3615 if (c1 == 27) { // ESC- user changed mind and wants out
3616 c = c1 = 27; // Escape- do nothing
3617 } else if (strchr("wW", c1)) {
3619 // don't include trailing WS as part of word
3620 while (isblnk(*q)) {
3621 if (q <= text || q[-1] == '\n')
3626 dot = yank_delete(p, q, 0, yf); // delete word
3627 } else if (strchr("^0bBeEft$", c1)) {
3628 // single line copy text into a register and delete
3629 dot = yank_delete(p, q, 0, yf); // delete word
3630 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3631 // multiple line copy text into a register and delete
3632 dot = yank_delete(p, q, 1, yf); // delete lines
3634 dot = char_insert(dot, '\n');
3635 // on the last line of file don't move to prev line
3636 if (dot != (end-1)) {
3639 } else if (c == 'd') {
3644 // could not recognize object
3645 c = c1 = 27; // error-
3649 // if CHANGING, not deleting, start inserting after the delete
3651 strcpy((char *) buf, "Change");
3652 goto dc_i; // start inserting
3655 strcpy((char *) buf, "Delete");
3657 #ifdef CONFIG_FEATURE_VI_YANKMARK
3658 if (c == 'y' || c == 'Y') {
3659 strcpy((char *) buf, "Yank");
3662 q = p + strlen((char *) p);
3663 for (cnt = 0; p <= q; p++) {
3667 psb("%s %d lines (%d chars) using [%c]",
3668 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3669 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3670 end_cmd_q(); // stop adding to q
3673 case 'k': // k- goto prev line, same col
3674 case VI_K_UP: // cursor key Up
3679 dot = move_to_col(dot, ccol + offset); // try stay in same col
3681 case 'r': // r- replace the current char with user input
3682 c1 = get_one_char(); // get the replacement char
3685 file_modified = TRUE; // has the file been modified
3687 end_cmd_q(); // stop adding to q
3689 case 't': // t- move to char prior to next x
3690 last_forward_char = get_one_char();
3692 if (*dot == last_forward_char)
3694 last_forward_char= 0;
3696 case 'w': // w- forward a word
3700 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3701 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3702 } else if (ispunct(*dot)) { // we are on PUNCT
3703 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3706 dot++; // move over word
3707 if (isspace(*dot)) {
3708 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3712 c1 = get_one_char(); // get the replacement char
3715 cnt = (rows - 2) / 2; // put dot at center
3717 cnt = rows - 2; // put dot at bottom
3718 screenbegin = begin_line(dot); // start dot at top
3719 dot_scroll(cnt, -1);
3721 case '|': // |- move to column "cmdcnt"
3722 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3724 case '~': // ~- flip the case of letters a-z -> A-Z
3728 if (islower(*dot)) {
3729 *dot = toupper(*dot);
3730 file_modified = TRUE; // has the file been modified
3731 } else if (isupper(*dot)) {
3732 *dot = tolower(*dot);
3733 file_modified = TRUE; // has the file been modified
3736 end_cmd_q(); // stop adding to q
3738 //----- The Cursor and Function Keys -----------------------------
3739 case VI_K_HOME: // Cursor Key Home
3742 // The Fn keys could point to do_macro which could translate them
3743 case VI_K_FUN1: // Function Key F1
3744 case VI_K_FUN2: // Function Key F2
3745 case VI_K_FUN3: // Function Key F3
3746 case VI_K_FUN4: // Function Key F4
3747 case VI_K_FUN5: // Function Key F5
3748 case VI_K_FUN6: // Function Key F6
3749 case VI_K_FUN7: // Function Key F7
3750 case VI_K_FUN8: // Function Key F8
3751 case VI_K_FUN9: // Function Key F9
3752 case VI_K_FUN10: // Function Key F10
3753 case VI_K_FUN11: // Function Key F11
3754 case VI_K_FUN12: // Function Key F12
3759 // if text[] just became empty, add back an empty line
3761 (void) char_insert(text, '\n'); // start empty buf with dummy line
3764 // it is OK for dot to exactly equal to end, otherwise check dot validity
3766 dot = bound_dot(dot); // make sure "dot" is valid
3768 #ifdef CONFIG_FEATURE_VI_YANKMARK
3769 check_context(c); // update the current context
3770 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3773 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3774 cnt = dot - begin_line(dot);
3775 // Try to stay off of the Newline
3776 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3780 #ifdef CONFIG_FEATURE_VI_CRASHME
3781 static int totalcmds = 0;
3782 static int Mp = 85; // Movement command Probability
3783 static int Np = 90; // Non-movement command Probability
3784 static int Dp = 96; // Delete command Probability
3785 static int Ip = 97; // Insert command Probability
3786 static int Yp = 98; // Yank command Probability
3787 static int Pp = 99; // Put command Probability
3788 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3789 char chars[20] = "\t012345 abcdABCD-=.$";
3790 char *words[20] = { "this", "is", "a", "test",
3791 "broadcast", "the", "emergency", "of",
3792 "system", "quick", "brown", "fox",
3793 "jumped", "over", "lazy", "dogs",
3794 "back", "January", "Febuary", "March"
3797 "You should have received a copy of the GNU General Public License\n",
3798 "char c, cm, *cmd, *cmd1;\n",
3799 "generate a command by percentages\n",
3800 "Numbers may be typed as a prefix to some commands.\n",
3801 "Quit, discarding changes!\n",
3802 "Forced write, if permission originally not valid.\n",
3803 "In general, any ex or ed command (such as substitute or delete).\n",
3804 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3805 "Please get w/ me and I will go over it with you.\n",
3806 "The following is a list of scheduled, committed changes.\n",
3807 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3808 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3809 "Any question about transactions please contact Sterling Huxley.\n",
3810 "I will try to get back to you by Friday, December 31.\n",
3811 "This Change will be implemented on Friday.\n",
3812 "Let me know if you have problems accessing this;\n",
3813 "Sterling Huxley recently added you to the access list.\n",
3814 "Would you like to go to lunch?\n",
3815 "The last command will be automatically run.\n",
3816 "This is too much english for a computer geek.\n",
3818 char *multilines[20] = {
3819 "You should have received a copy of the GNU General Public License\n",
3820 "char c, cm, *cmd, *cmd1;\n",
3821 "generate a command by percentages\n",
3822 "Numbers may be typed as a prefix to some commands.\n",
3823 "Quit, discarding changes!\n",
3824 "Forced write, if permission originally not valid.\n",
3825 "In general, any ex or ed command (such as substitute or delete).\n",
3826 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3827 "Please get w/ me and I will go over it with you.\n",
3828 "The following is a list of scheduled, committed changes.\n",
3829 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3830 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3831 "Any question about transactions please contact Sterling Huxley.\n",
3832 "I will try to get back to you by Friday, December 31.\n",
3833 "This Change will be implemented on Friday.\n",
3834 "Let me know if you have problems accessing this;\n",
3835 "Sterling Huxley recently added you to the access list.\n",
3836 "Would you like to go to lunch?\n",
3837 "The last command will be automatically run.\n",
3838 "This is too much english for a computer geek.\n",
3841 // create a random command to execute
3842 static void crash_dummy()
3844 static int sleeptime; // how long to pause between commands
3845 char c, cm, *cmd, *cmd1;
3846 int i, cnt, thing, rbi, startrbi, percent;
3848 // "dot" movement commands
3849 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3851 // is there already a command running?
3852 if (readed_for_parse > 0)
3856 sleeptime = 0; // how long to pause between commands
3857 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3858 // generate a command by percentages
3859 percent = (int) lrand48() % 100; // get a number from 0-99
3860 if (percent < Mp) { // Movement commands
3861 // available commands
3864 } else if (percent < Np) { // non-movement commands
3865 cmd = "mz<>\'\""; // available commands
3867 } else if (percent < Dp) { // Delete commands
3868 cmd = "dx"; // available commands
3870 } else if (percent < Ip) { // Inset commands
3871 cmd = "iIaAsrJ"; // available commands
3873 } else if (percent < Yp) { // Yank commands
3874 cmd = "yY"; // available commands
3876 } else if (percent < Pp) { // Put commands
3877 cmd = "pP"; // available commands
3880 // We do not know how to handle this command, try again
3884 // randomly pick one of the available cmds from "cmd[]"
3885 i = (int) lrand48() % strlen(cmd);
3887 if (strchr(":\024", cm))
3888 goto cd0; // dont allow colon or ctrl-T commands
3889 readbuffer[rbi++] = cm; // put cmd into input buffer
3891 // now we have the command-
3892 // there are 1, 2, and multi char commands
3893 // find out which and generate the rest of command as necessary
3894 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3895 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3896 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3897 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3899 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3901 readbuffer[rbi++] = c; // add movement to input buffer
3903 if (strchr("iIaAsc", cm)) { // multi-char commands
3905 // change some thing
3906 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3908 readbuffer[rbi++] = c; // add movement to input buffer
3910 thing = (int) lrand48() % 4; // what thing to insert
3911 cnt = (int) lrand48() % 10; // how many to insert
3912 for (i = 0; i < cnt; i++) {
3913 if (thing == 0) { // insert chars
3914 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3915 } else if (thing == 1) { // insert words
3916 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3917 strcat((char *) readbuffer, " ");
3918 sleeptime = 0; // how fast to type
3919 } else if (thing == 2) { // insert lines
3920 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3921 sleeptime = 0; // how fast to type
3922 } else { // insert multi-lines
3923 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3924 sleeptime = 0; // how fast to type
3927 strcat((char *) readbuffer, "\033");
3929 readed_for_parse = strlen(readbuffer);
3933 (void) mysleep(sleeptime); // sleep 1/100 sec
3936 // test to see if there are any errors
3937 static void crash_test()
3939 static time_t oldtim;
3941 char d[2], msg[BUFSIZ];
3945 strcat((char *) msg, "end<text ");
3947 if (end > textend) {
3948 strcat((char *) msg, "end>textend ");
3951 strcat((char *) msg, "dot<text ");
3954 strcat((char *) msg, "dot>end ");
3956 if (screenbegin < text) {
3957 strcat((char *) msg, "screenbegin<text ");
3959 if (screenbegin > end - 1) {
3960 strcat((char *) msg, "screenbegin>end-1 ");
3963 if (strlen(msg) > 0) {
3965 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3966 totalcmds, last_input_char, msg, SOs, SOn);
3968 while (read(0, d, 1) > 0) {
3969 if (d[0] == '\n' || d[0] == '\r')
3974 tim = (time_t) time((time_t *) 0);
3975 if (tim >= (oldtim + 3)) {
3976 sprintf((char *) status_buffer,
3977 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3978 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3983 #endif /* CONFIG_FEATURE_VI_CRASHME */