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.28 2003/03/19 09:11:45 mjn3 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 immplement
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_WIN_RESIZE
201 static struct winsize winsize; // remember the window size
202 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
203 #ifdef CONFIG_FEATURE_VI_DOT_CMD
204 static int adding2q; // are we currently adding user input to q
205 static Byte *last_modifying_cmd; // last modifying cmd for "."
206 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
207 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
208 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
209 static Byte *modifying_cmds; // cmds that modify text[]
210 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
211 #ifdef CONFIG_FEATURE_VI_READONLY
212 static int vi_readonly, readonly;
213 #endif /* CONFIG_FEATURE_VI_READONLY */
214 #ifdef CONFIG_FEATURE_VI_YANKMARK
215 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
216 static int YDreg, Ureg; // default delete register and orig line for "U"
217 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
218 static Byte *context_start, *context_end;
219 #endif /* CONFIG_FEATURE_VI_YANKMARK */
220 #ifdef CONFIG_FEATURE_VI_SEARCH
221 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
222 #endif /* CONFIG_FEATURE_VI_SEARCH */
225 static void edit_file(Byte *); // edit one file
226 static void do_cmd(Byte); // execute a command
227 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
228 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
229 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
230 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
231 static Byte *next_line(Byte *); // return pointer to next line B-o-l
232 static Byte *end_screen(void); // get pointer to last char on screen
233 static int count_lines(Byte *, Byte *); // count line from start to stop
234 static Byte *find_line(int); // find begining of line #li
235 static Byte *move_to_col(Byte *, int); // move "p" to column l
236 static int isblnk(Byte); // is the char a blank or tab
237 static void dot_left(void); // move dot left- dont leave line
238 static void dot_right(void); // move dot right- dont leave line
239 static void dot_begin(void); // move dot to B-o-l
240 static void dot_end(void); // move dot to E-o-l
241 static void dot_next(void); // move dot to next line B-o-l
242 static void dot_prev(void); // move dot to prev line B-o-l
243 static void dot_scroll(int, int); // move the screen up or down
244 static void dot_skip_over_ws(void); // move dot pat WS
245 static void dot_delete(void); // delete the char at 'dot'
246 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
247 static Byte *new_screen(int, int); // malloc virtual screen memory
248 static Byte *new_text(int); // malloc memory for text[] buffer
249 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
250 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
251 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
252 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
253 static Byte *skip_thing(Byte *, int, int, int); // skip some object
254 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
255 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
256 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
257 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
258 static void show_help(void); // display some help info
259 static void rawmode(void); // set "raw" mode on tty
260 static void cookmode(void); // return to "cooked" mode on tty
261 static int mysleep(int); // sleep for 'h' 1/100 seconds
262 static Byte readit(void); // read (maybe cursor) key from stdin
263 static Byte get_one_char(void); // read 1 char from stdin
264 static int file_size(const Byte *); // what is the byte size of "fn"
265 static int file_insert(Byte *, Byte *, int);
266 static int file_write(Byte *, Byte *, Byte *);
267 static void place_cursor(int, int, int);
268 static void screen_erase(void);
269 static void clear_to_eol(void);
270 static void clear_to_eos(void);
271 static void standout_start(void); // send "start reverse video" sequence
272 static void standout_end(void); // send "end reverse video" sequence
273 static void flash(int); // flash the terminal screen
274 static void show_status_line(void); // put a message on the bottom line
275 static void psb(const char *, ...); // Print Status Buf
276 static void psbs(const char *, ...); // Print Status Buf in standout mode
277 static void ni(Byte *); // display messages
278 static void edit_status(void); // show file status on status line
279 static void redraw(int); // force a full screen refresh
280 static void format_line(Byte*, Byte*, int);
281 static void refresh(int); // update the terminal from screen[]
283 static void Indicate_Error(void); // use flash or beep to indicate error
284 #define indicate_error(c) Indicate_Error()
286 #ifdef CONFIG_FEATURE_VI_SEARCH
287 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
288 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
289 #endif /* CONFIG_FEATURE_VI_SEARCH */
290 #ifdef CONFIG_FEATURE_VI_COLON
291 static void Hit_Return(void);
292 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
293 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
294 static void colon(Byte *); // execute the "colon" mode cmds
295 #endif /* CONFIG_FEATURE_VI_COLON */
296 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
297 static void winch_sig(int); // catch window size changes
298 static void suspend_sig(int); // catch ctrl-Z
299 static void catch_sig(int); // catch ctrl-C and alarm time-outs
300 static void core_sig(int); // catch a core dump signal
301 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
302 #ifdef CONFIG_FEATURE_VI_DOT_CMD
303 static void start_new_cmd_q(Byte); // new queue for command
304 static void end_cmd_q(void); // stop saving input chars
305 #else /* CONFIG_FEATURE_VI_DOT_CMD */
307 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
308 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
309 static void window_size_get(int); // find out what size the window is
310 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
311 #ifdef CONFIG_FEATURE_VI_SETOPTS
312 static void showmatching(Byte *); // show the matching pair () [] {}
313 #endif /* CONFIG_FEATURE_VI_SETOPTS */
314 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
315 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
316 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
317 #ifdef CONFIG_FEATURE_VI_YANKMARK
318 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
319 static Byte what_reg(void); // what is letter of current YDreg
320 static void check_context(Byte); // remember context for '' command
321 #endif /* CONFIG_FEATURE_VI_YANKMARK */
322 #ifdef CONFIG_FEATURE_VI_CRASHME
323 static void crash_dummy();
324 static void crash_test();
325 static int crashme = 0;
326 #endif /* CONFIG_FEATURE_VI_CRASHME */
329 static void write1(const char *out)
334 extern int vi_main(int argc, char **argv)
337 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, 200);
339 #ifdef CONFIG_FEATURE_VI_YANKMARK
341 #endif /* CONFIG_FEATURE_VI_YANKMARK */
342 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
345 #ifdef CONFIG_FEATURE_VI_CRASHME
346 (void) srand((long) my_pid);
347 #endif /* CONFIG_FEATURE_VI_CRASHME */
349 status_buffer = STATUS_BUFFER;
351 #ifdef CONFIG_FEATURE_VI_READONLY
352 vi_readonly = readonly = FALSE;
353 if (strncmp(argv[0], "view", 4) == 0) {
357 #endif /* CONFIG_FEATURE_VI_READONLY */
358 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
359 #ifdef CONFIG_FEATURE_VI_YANKMARK
360 for (i = 0; i < 28; i++) {
362 } // init the yank regs
363 #endif /* CONFIG_FEATURE_VI_YANKMARK */
364 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
365 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
366 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
368 // 1- process $HOME/.exrc file
369 // 2- process EXINIT variable from environment
370 // 3- process command line args
371 while ((c = getopt(argc, argv, "hCR")) != -1) {
373 #ifdef CONFIG_FEATURE_VI_CRASHME
377 #endif /* CONFIG_FEATURE_VI_CRASHME */
378 #ifdef CONFIG_FEATURE_VI_READONLY
379 case 'R': // Read-only flag
382 #endif /* CONFIG_FEATURE_VI_READONLY */
383 //case 'r': // recover flag- ignore- we don't use tmp file
384 //case 'x': // encryption flag- ignore
385 //case 'c': // execute command first
386 //case 'h': // help -- just use default
393 // The argv array can be used by the ":next" and ":rewind" commands
395 fn_start = optind; // remember first file name for :next and :rew
398 //----- This is the main file handling loop --------------
399 if (optind >= argc) {
400 editing = 1; // 0= exit, 1= one file, 2= multiple files
403 for (; optind < argc; optind++) {
404 editing = 1; // 0=exit, 1=one file, 2+ =many files
406 cfn = (Byte *) bb_xstrdup(argv[optind]);
410 //-----------------------------------------------------------
415 static void edit_file(Byte * fn)
420 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
422 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
423 #ifdef CONFIG_FEATURE_VI_YANKMARK
424 static Byte *cur_line;
425 #endif /* CONFIG_FEATURE_VI_YANKMARK */
431 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
433 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
434 new_screen(rows, columns); // get memory for virtual screen
436 cnt = file_size(fn); // file size
437 size = 2 * cnt; // 200% of file size
438 new_text(size); // get a text[] buffer
439 screenbegin = dot = end = text;
441 ch= file_insert(fn, text, cnt);
444 (void) char_insert(text, '\n'); // start empty buf with dummy line
446 file_modified = FALSE;
447 #ifdef CONFIG_FEATURE_VI_YANKMARK
448 YDreg = 26; // default Yank/Delete reg
449 Ureg = 27; // hold orig line for "U" cmd
450 for (cnt = 0; cnt < 28; cnt++) {
453 mark[26] = mark[27] = text; // init "previous context"
454 #endif /* CONFIG_FEATURE_VI_YANKMARK */
456 last_forward_char = last_input_char = '\0';
461 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
464 signal(SIGWINCH, winch_sig);
465 signal(SIGTSTP, suspend_sig);
466 sig = setjmp(restart);
468 const char *msg = "";
471 msg = "(window resize)";
481 msg = "(I tried to touch invalid memory)";
485 psbs("-- caught signal %d %s--", sig, msg);
486 screenbegin = dot = text;
488 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
491 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
494 offset = 0; // no horizontal offset
496 #ifdef CONFIG_FEATURE_VI_DOT_CMD
497 free(last_modifying_cmd);
499 ioq = ioq_start = last_modifying_cmd = 0;
501 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
502 redraw(FALSE); // dont force every col re-draw
506 //------This is the main Vi cmd handling loop -----------------------
507 while (editing > 0) {
508 #ifdef CONFIG_FEATURE_VI_CRASHME
510 if ((end - text) > 1) {
511 crash_dummy(); // generate a random command
515 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
519 #endif /* CONFIG_FEATURE_VI_CRASHME */
520 last_input_char = c = get_one_char(); // get a cmd from user
521 #ifdef CONFIG_FEATURE_VI_YANKMARK
522 // save a copy of the current line- for the 'U" command
523 if (begin_line(dot) != cur_line) {
524 cur_line = begin_line(dot);
525 text_yank(begin_line(dot), end_line(dot), Ureg);
527 #endif /* CONFIG_FEATURE_VI_YANKMARK */
528 #ifdef CONFIG_FEATURE_VI_DOT_CMD
529 // These are commands that change text[].
530 // Remember the input for the "." command
531 if (!adding2q && ioq_start == 0
532 && strchr((char *) modifying_cmds, c) != NULL) {
535 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
536 do_cmd(c); // execute the user command
538 // poll to see if there is input already waiting. if we are
539 // not able to display output fast enough to keep up, skip
540 // the display update until we catch up with input.
541 if (mysleep(0) == 0) {
542 // no input pending- so update output
546 #ifdef CONFIG_FEATURE_VI_CRASHME
548 crash_test(); // test editor variables
549 #endif /* CONFIG_FEATURE_VI_CRASHME */
551 //-------------------------------------------------------------------
553 place_cursor(rows, 0, FALSE); // go to bottom of screen
554 clear_to_eol(); // Erase to end of line
558 //----- The Colon commands -------------------------------------
559 #ifdef CONFIG_FEATURE_VI_COLON
560 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
565 #ifdef CONFIG_FEATURE_VI_YANKMARK
567 #endif /* CONFIG_FEATURE_VI_YANKMARK */
568 #ifdef CONFIG_FEATURE_VI_SEARCH
569 Byte *pat, buf[BUFSIZ];
570 #endif /* CONFIG_FEATURE_VI_SEARCH */
572 *addr = -1; // assume no addr
573 if (*p == '.') { // the current line
576 *addr = count_lines(text, q);
577 #ifdef CONFIG_FEATURE_VI_YANKMARK
578 } else if (*p == '\'') { // is this a mark addr
582 if (c >= 'a' && c <= 'z') {
586 if (q != NULL) { // is mark valid
587 *addr = count_lines(text, q); // count lines
590 #endif /* CONFIG_FEATURE_VI_YANKMARK */
591 #ifdef CONFIG_FEATURE_VI_SEARCH
592 } else if (*p == '/') { // a search pattern
600 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
603 q = char_search(dot, pat, FORWARD, FULL);
605 *addr = count_lines(text, q);
608 #endif /* CONFIG_FEATURE_VI_SEARCH */
609 } else if (*p == '$') { // the last line in file
611 q = begin_line(end - 1);
612 *addr = count_lines(text, q);
613 } else if (isdigit(*p)) { // specific line number
614 sscanf((char *) p, "%d%n", addr, &st);
616 } else { // I don't reconise this
617 // unrecognised address- assume -1
623 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
625 //----- get the address' i.e., 1,3 'a,'b -----
626 // get FIRST addr, if present
628 p++; // skip over leading spaces
629 if (*p == '%') { // alias for 1,$
632 *e = count_lines(text, end-1);
635 p = get_one_address(p, b);
638 if (*p == ',') { // is there a address seperator
642 // get SECOND addr, if present
643 p = get_one_address(p, e);
647 p++; // skip over trailing spaces
651 #ifdef CONFIG_FEATURE_VI_SETOPTS
652 static void setops(const Byte *args, const char *opname, int flg_no,
653 const char *short_opname, int opt)
655 const char *a = (char *) args + flg_no;
656 int l = strlen(opname) - 1; /* opname have + ' ' */
658 if (strncasecmp(a, opname, l) == 0 ||
659 strncasecmp(a, short_opname, 2) == 0) {
668 static void colon(Byte * buf)
670 Byte c, *orig_buf, *buf1, *q, *r;
671 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
672 int i, l, li, ch, st, b, e;
673 int useforce, forced;
676 // :3154 // if (-e line 3154) goto it else stay put
677 // :4,33w! foo // write a portion of buffer to file "foo"
678 // :w // write all of buffer to current file
680 // :q! // quit- dont care about modified file
681 // :'a,'z!sort -u // filter block through sort
682 // :'f // goto mark "f"
683 // :'fl // list literal the mark "f" line
684 // :.r bar // read file "bar" into buffer before dot
685 // :/123/,/abc/d // delete lines from "123" line to "abc" line
686 // :/xyz/ // goto the "xyz" line
687 // :s/find/replace/ // substitute pattern "find" with "replace"
688 // :!<cmd> // run <cmd> then return
690 if (strlen((char *) buf) <= 0)
693 buf++; // move past the ':'
695 forced = useforce = FALSE;
696 li = st = ch = i = 0;
698 q = text; // assume 1,$ for the range
700 li = count_lines(text, end - 1);
701 fn = cfn; // default to current file
702 memset(cmd, '\0', BUFSIZ); // clear cmd[]
703 memset(args, '\0', BUFSIZ); // clear args[]
705 // look for optional address(es) :. :1 :1,9 :'q,'a :%
706 buf = get_address(buf, &b, &e);
708 // remember orig command line
711 // get the COMMAND into cmd[]
713 while (*buf != '\0') {
721 strcpy((char *) args, (char *) buf);
722 buf1 = last_char_is((char *)cmd, '!');
725 *buf1 = '\0'; // get rid of !
728 // if there is only one addr, then the addr
729 // is the line number of the single line the
730 // user wants. So, reset the end
731 // pointer to point at end of the "b" line
732 q = find_line(b); // what line is #b
737 // we were given two addrs. change the
738 // end pointer to the addr given by user.
739 r = find_line(e); // what line is #e
743 // ------------ now look for the command ------------
744 i = strlen((char *) cmd);
745 if (i == 0) { // :123CR goto line #123
747 dot = find_line(b); // what line is #b
750 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
751 // :!ls run the <cmd>
752 (void) alarm(0); // wait for input- no alarms
753 place_cursor(rows - 1, 0, FALSE); // go to Status line
754 clear_to_eol(); // clear the line
756 system(orig_buf+1); // run the cmd
758 Hit_Return(); // let user see results
759 (void) alarm(3); // done waiting for input
760 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
761 if (b < 0) { // no addr given- use defaults
762 b = e = count_lines(text, dot);
765 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
766 if (b < 0) { // no addr given- use defaults
767 q = begin_line(dot); // assume .,. for the range
770 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
772 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
775 // don't edit, if the current file has been modified
776 if (file_modified && ! useforce) {
777 psbs("No write since last change (:edit! overrides)");
780 if (strlen(args) > 0) {
781 // the user supplied a file name
783 } else if (cfn != 0 && strlen(cfn) > 0) {
784 // no user supplied name- use the current filename
788 // no user file name, no current name- punt
789 psbs("No current filename");
793 // see if file exists- if not, its just a new file request
794 if ((sr=stat((char*)fn, &st_buf)) < 0) {
795 // This is just a request for a new file creation.
796 // The file_insert below will fail but we get
797 // an empty buffer with a file name. Then the "write"
798 // command can do the create.
800 if ((st_buf.st_mode & (S_IFREG)) == 0) {
801 // This is not a regular file
802 psbs("\"%s\" is not a regular file", fn);
805 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
806 // dont have any read permissions
807 psbs("\"%s\" is not readable", fn);
812 // There is a read-able regular file
813 // make this the current file
814 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
815 free(cfn); // free the old name
816 cfn = q; // remember new cfn
819 // delete all the contents of text[]
820 new_text(2 * file_size(fn));
821 screenbegin = dot = end = text;
824 ch = file_insert(fn, text, file_size(fn));
827 // start empty buf with dummy line
828 (void) char_insert(text, '\n');
831 file_modified = FALSE;
832 #ifdef CONFIG_FEATURE_VI_YANKMARK
833 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
834 free(reg[Ureg]); // free orig line reg- for 'U'
837 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
838 free(reg[YDreg]); // free default yank/delete register
841 for (li = 0; li < 28; li++) {
844 #endif /* CONFIG_FEATURE_VI_YANKMARK */
845 // how many lines in text[]?
846 li = count_lines(text, end - 1);
848 #ifdef CONFIG_FEATURE_VI_READONLY
850 #endif /* CONFIG_FEATURE_VI_READONLY */
852 (sr < 0 ? " [New file]" : ""),
853 #ifdef CONFIG_FEATURE_VI_READONLY
854 ((vi_readonly || readonly) ? " [Read only]" : ""),
855 #endif /* CONFIG_FEATURE_VI_READONLY */
857 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
858 if (b != -1 || e != -1) {
859 ni((Byte *) "No address allowed on this command");
862 if (strlen((char *) args) > 0) {
863 // user wants a new filename
865 cfn = (Byte *) bb_xstrdup((char *) args);
867 // user wants file status info
870 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
871 // print out values of all features
872 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
873 clear_to_eol(); // clear the line
878 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
879 if (b < 0) { // no addr given- use defaults
880 q = begin_line(dot); // assume .,. for the range
883 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
884 clear_to_eol(); // clear the line
886 for (; q <= r; q++) {
890 c_is_no_print = c > 127 && !Isprint(c);
897 } else if (c < ' ' || c == 127) {
908 #ifdef CONFIG_FEATURE_VI_SET
910 #endif /* CONFIG_FEATURE_VI_SET */
912 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
913 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
915 // force end of argv list
922 // don't exit if the file been modified
924 psbs("No write since last change (:%s! overrides)",
925 (*cmd == 'q' ? "quit" : "next"));
928 // are there other file to edit
929 if (*cmd == 'q' && optind < save_argc - 1) {
930 psbs("%d more file to edit", (save_argc - optind - 1));
933 if (*cmd == 'n' && optind >= save_argc - 1) {
934 psbs("No more files to edit");
938 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
940 if (strlen((char *) fn) <= 0) {
941 psbs("No filename given");
944 if (b < 0) { // no addr given- use defaults
945 q = begin_line(dot); // assume "dot"
947 // read after current line- unless user said ":0r foo"
950 #ifdef CONFIG_FEATURE_VI_READONLY
951 l= readonly; // remember current files' status
953 ch = file_insert(fn, q, file_size(fn));
954 #ifdef CONFIG_FEATURE_VI_READONLY
958 goto vc1; // nothing was inserted
959 // how many lines in text[]?
960 li = count_lines(q, q + ch - 1);
962 #ifdef CONFIG_FEATURE_VI_READONLY
964 #endif /* CONFIG_FEATURE_VI_READONLY */
966 #ifdef CONFIG_FEATURE_VI_READONLY
967 ((vi_readonly || readonly) ? " [Read only]" : ""),
968 #endif /* CONFIG_FEATURE_VI_READONLY */
971 // if the insert is before "dot" then we need to update
974 file_modified = TRUE;
976 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
977 if (file_modified && ! useforce) {
978 psbs("No write since last change (:rewind! overrides)");
980 // reset the filenames to edit
981 optind = fn_start - 1;
984 #ifdef CONFIG_FEATURE_VI_SET
985 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
986 i = 0; // offset into args
987 if (strlen((char *) args) == 0) {
988 // print out values of all options
989 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
990 clear_to_eol(); // clear the line
991 printf("----------------------------------------\r\n");
992 #ifdef CONFIG_FEATURE_VI_SETOPTS
995 printf("autoindent ");
1001 printf("ignorecase ");
1004 printf("showmatch ");
1005 printf("tabstop=%d ", tabstop);
1006 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1010 if (strncasecmp((char *) args, "no", 2) == 0)
1011 i = 2; // ":set noautoindent"
1012 #ifdef CONFIG_FEATURE_VI_SETOPTS
1013 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1014 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1015 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1016 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1017 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1018 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1019 if (ch > 0 && ch < columns - 1)
1022 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1023 #endif /* CONFIG_FEATURE_VI_SET */
1024 #ifdef CONFIG_FEATURE_VI_SEARCH
1025 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1029 // F points to the "find" pattern
1030 // R points to the "replace" pattern
1031 // replace the cmd line delimiters "/" with NULLs
1032 gflag = 0; // global replace flag
1033 c = orig_buf[1]; // what is the delimiter
1034 F = orig_buf + 2; // start of "find"
1035 R = (Byte *) strchr((char *) F, c); // middle delimiter
1036 if (!R) goto colon_s_fail;
1037 *R++ = '\0'; // terminate "find"
1038 buf1 = (Byte *) strchr((char *) R, c);
1039 if (!buf1) goto colon_s_fail;
1040 *buf1++ = '\0'; // terminate "replace"
1041 if (*buf1 == 'g') { // :s/foo/bar/g
1043 gflag++; // turn on gflag
1046 if (b < 0) { // maybe :s/foo/bar/
1047 q = begin_line(dot); // start with cur line
1048 b = count_lines(text, q); // cur line number
1051 e = b; // maybe :.s/foo/bar/
1052 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1053 ls = q; // orig line start
1055 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1057 // we found the "find" pattern- delete it
1058 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1059 // inset the "replace" patern
1060 (void) string_insert(buf1, R); // insert the string
1061 // check for "global" :s/foo/bar/g
1063 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1064 q = buf1 + strlen((char *) R);
1065 goto vc4; // don't let q move past cur line
1071 #endif /* CONFIG_FEATURE_VI_SEARCH */
1072 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1073 psb("%s", vi_Version);
1074 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1075 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1076 (strncasecmp((char *) cmd, "x", i) == 0)) {
1077 // is there a file name to write to?
1078 if (strlen((char *) args) > 0) {
1081 #ifdef CONFIG_FEATURE_VI_READONLY
1082 if ((vi_readonly || readonly) && ! useforce) {
1083 psbs("\"%s\" File is read only", fn);
1086 #endif /* CONFIG_FEATURE_VI_READONLY */
1087 // how many lines in text[]?
1088 li = count_lines(q, r);
1090 // see if file exists- if not, its just a new file request
1092 // if "fn" is not write-able, chmod u+w
1093 // sprintf(syscmd, "chmod u+w %s", fn);
1097 l = file_write(fn, q, r);
1098 if (useforce && forced) {
1100 // sprintf(syscmd, "chmod u-w %s", fn);
1104 psb("\"%s\" %dL, %dC", fn, li, l);
1105 if (q == text && r == end - 1 && l == ch)
1106 file_modified = FALSE;
1107 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1110 #ifdef CONFIG_FEATURE_VI_READONLY
1112 #endif /* CONFIG_FEATURE_VI_READONLY */
1113 #ifdef CONFIG_FEATURE_VI_YANKMARK
1114 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1115 if (b < 0) { // no addr given- use defaults
1116 q = begin_line(dot); // assume .,. for the range
1119 text_yank(q, r, YDreg);
1120 li = count_lines(q, r);
1121 psb("Yank %d lines (%d chars) into [%c]",
1122 li, strlen((char *) reg[YDreg]), what_reg());
1123 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1129 dot = bound_dot(dot); // make sure "dot" is valid
1131 #ifdef CONFIG_FEATURE_VI_SEARCH
1133 psb(":s expression missing delimiters");
1137 static void Hit_Return(void)
1141 standout_start(); // start reverse video
1142 write1("[Hit return to continue]");
1143 standout_end(); // end reverse video
1144 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1146 redraw(TRUE); // force redraw all
1148 #endif /* CONFIG_FEATURE_VI_COLON */
1150 //----- Synchronize the cursor to Dot --------------------------
1151 static void sync_cursor(Byte * d, int *row, int *col)
1153 Byte *beg_cur, *end_cur; // begin and end of "d" line
1154 Byte *beg_scr, *end_scr; // begin and end of screen
1158 beg_cur = begin_line(d); // first char of cur line
1159 end_cur = end_line(d); // last char of cur line
1161 beg_scr = end_scr = screenbegin; // first char of screen
1162 end_scr = end_screen(); // last char of screen
1164 if (beg_cur < screenbegin) {
1165 // "d" is before top line on screen
1166 // how many lines do we have to move
1167 cnt = count_lines(beg_cur, screenbegin);
1169 screenbegin = beg_cur;
1170 if (cnt > (rows - 1) / 2) {
1171 // we moved too many lines. put "dot" in middle of screen
1172 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1173 screenbegin = prev_line(screenbegin);
1176 } else if (beg_cur > end_scr) {
1177 // "d" is after bottom line on screen
1178 // how many lines do we have to move
1179 cnt = count_lines(end_scr, beg_cur);
1180 if (cnt > (rows - 1) / 2)
1181 goto sc1; // too many lines
1182 for (ro = 0; ro < cnt - 1; ro++) {
1183 // move screen begin the same amount
1184 screenbegin = next_line(screenbegin);
1185 // now, move the end of screen
1186 end_scr = next_line(end_scr);
1187 end_scr = end_line(end_scr);
1190 // "d" is on screen- find out which row
1192 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1198 // find out what col "d" is on
1200 do { // drive "co" to correct column
1201 if (*tp == '\n' || *tp == '\0')
1205 co += ((tabstop - 1) - (co % tabstop));
1206 } else if (*tp < ' ' || *tp == 127) {
1207 co++; // display as ^X, use 2 columns
1209 } while (tp++ < d && ++co);
1211 // "co" is the column where "dot" is.
1212 // The screen has "columns" columns.
1213 // The currently displayed columns are 0+offset -- columns+ofset
1214 // |-------------------------------------------------------------|
1216 // offset | |------- columns ----------------|
1218 // If "co" is already in this range then we do not have to adjust offset
1219 // but, we do have to subtract the "offset" bias from "co".
1220 // If "co" is outside this range then we have to change "offset".
1221 // If the first char of a line is a tab the cursor will try to stay
1222 // in column 7, but we have to set offset to 0.
1224 if (co < 0 + offset) {
1227 if (co >= columns + offset) {
1228 offset = co - columns + 1;
1230 // if the first char of the line is a tab, and "dot" is sitting on it
1231 // force offset to 0.
1232 if (d == beg_cur && *d == '\t') {
1241 //----- Text Movement Routines ---------------------------------
1242 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1244 while (p > text && p[-1] != '\n')
1245 p--; // go to cur line B-o-l
1249 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1251 while (p < end - 1 && *p != '\n')
1252 p++; // go to cur line E-o-l
1256 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1258 while (p < end - 1 && *p != '\n')
1259 p++; // go to cur line E-o-l
1260 // Try to stay off of the Newline
1261 if (*p == '\n' && (p - begin_line(p)) > 0)
1266 static Byte *prev_line(Byte * p) // return pointer first char prev line
1268 p = begin_line(p); // goto begining of cur line
1269 if (p[-1] == '\n' && p > text)
1270 p--; // step to prev line
1271 p = begin_line(p); // goto begining of prev line
1275 static Byte *next_line(Byte * p) // return pointer first char next line
1278 if (*p == '\n' && p < end - 1)
1279 p++; // step to next line
1283 //----- Text Information Routines ------------------------------
1284 static Byte *end_screen(void)
1289 // find new bottom line
1291 for (cnt = 0; cnt < rows - 2; cnt++)
1297 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1302 if (stop < start) { // start and stop are backwards- reverse them
1308 stop = end_line(stop); // get to end of this line
1309 for (q = start; q <= stop && q <= end - 1; q++) {
1316 static Byte *find_line(int li) // find begining of line #li
1320 for (q = text; li > 1; li--) {
1326 //----- Dot Movement Routines ----------------------------------
1327 static void dot_left(void)
1329 if (dot > text && dot[-1] != '\n')
1333 static void dot_right(void)
1335 if (dot < end - 1 && *dot != '\n')
1339 static void dot_begin(void)
1341 dot = begin_line(dot); // return pointer to first char cur line
1344 static void dot_end(void)
1346 dot = end_line(dot); // return pointer to last char cur line
1349 static Byte *move_to_col(Byte * p, int l)
1356 if (*p == '\n' || *p == '\0')
1360 co += ((tabstop - 1) - (co % tabstop));
1361 } else if (*p < ' ' || *p == 127) {
1362 co++; // display as ^X, use 2 columns
1364 } while (++co <= l && p++ < end);
1368 static void dot_next(void)
1370 dot = next_line(dot);
1373 static void dot_prev(void)
1375 dot = prev_line(dot);
1378 static void dot_scroll(int cnt, int dir)
1382 for (; cnt > 0; cnt--) {
1385 // ctrl-Y scroll up one line
1386 screenbegin = prev_line(screenbegin);
1389 // ctrl-E scroll down one line
1390 screenbegin = next_line(screenbegin);
1393 // make sure "dot" stays on the screen so we dont scroll off
1394 if (dot < screenbegin)
1396 q = end_screen(); // find new bottom line
1398 dot = begin_line(q); // is dot is below bottom line?
1402 static void dot_skip_over_ws(void)
1405 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1409 static void dot_delete(void) // delete the char at 'dot'
1411 (void) text_hole_delete(dot, dot);
1414 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1416 if (p >= end && end > text) {
1418 indicate_error('1');
1422 indicate_error('2');
1427 //----- Helper Utility Routines --------------------------------
1429 //----------------------------------------------------------------
1430 //----- Char Routines --------------------------------------------
1431 /* Chars that are part of a word-
1432 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1433 * Chars that are Not part of a word (stoppers)
1434 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1435 * Chars that are WhiteSpace
1436 * TAB NEWLINE VT FF RETURN SPACE
1437 * DO NOT COUNT NEWLINE AS WHITESPACE
1440 static Byte *new_screen(int ro, int co)
1445 screensize = ro * co + 8;
1446 screen = (Byte *) xmalloc(screensize);
1447 // initialize the new screen. assume this will be a empty file.
1449 // non-existant text[] lines start with a tilde (~).
1450 for (li = 1; li < ro - 1; li++) {
1451 screen[(li * co) + 0] = '~';
1456 static Byte *new_text(int size)
1459 size = 10240; // have a minimum size for new files
1461 text = (Byte *) xmalloc(size + 8);
1462 memset(text, '\0', size); // clear new text[]
1463 //text += 4; // leave some room for "oops"
1464 textend = text + size - 1;
1465 //textend -= 4; // leave some root for "oops"
1469 #ifdef CONFIG_FEATURE_VI_SEARCH
1470 static int mycmp(Byte * s1, Byte * s2, int len)
1474 i = strncmp((char *) s1, (char *) s2, len);
1475 #ifdef CONFIG_FEATURE_VI_SETOPTS
1477 i = strncasecmp((char *) s1, (char *) s2, len);
1479 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1483 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1485 #ifndef REGEX_SEARCH
1489 len = strlen((char *) pat);
1490 if (dir == FORWARD) {
1491 stop = end - 1; // assume range is p - end-1
1492 if (range == LIMITED)
1493 stop = next_line(p); // range is to next line
1494 for (start = p; start < stop; start++) {
1495 if (mycmp(start, pat, len) == 0) {
1499 } else if (dir == BACK) {
1500 stop = text; // assume range is text - p
1501 if (range == LIMITED)
1502 stop = prev_line(p); // range is to prev line
1503 for (start = p - len; start >= stop; start--) {
1504 if (mycmp(start, pat, len) == 0) {
1509 // pattern not found
1511 #else /*REGEX_SEARCH */
1513 struct re_pattern_buffer preg;
1517 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1523 // assume a LIMITED forward search
1531 // count the number of chars to search over, forward or backward
1535 // RANGE could be negative if we are searching backwards
1538 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1540 // The pattern was not compiled
1541 psbs("bad search pattern: \"%s\": %s", pat, q);
1542 i = 0; // return p if pattern not compiled
1552 // search for the compiled pattern, preg, in p[]
1553 // range < 0- search backward
1554 // range > 0- search forward
1556 // re_search() < 0 not found or error
1557 // re_search() > 0 index of found pattern
1558 // struct pattern char int int int struct reg
1559 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1560 i = re_search(&preg, q, size, 0, range, 0);
1563 i = 0; // return NULL if pattern not found
1566 if (dir == FORWARD) {
1572 #endif /*REGEX_SEARCH */
1574 #endif /* CONFIG_FEATURE_VI_SEARCH */
1576 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1578 if (c == 22) { // Is this an ctrl-V?
1579 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1580 p--; // backup onto ^
1581 refresh(FALSE); // show the ^
1585 file_modified = TRUE; // has the file been modified
1586 } else if (c == 27) { // Is this an ESC?
1589 end_cmd_q(); // stop adding to q
1590 strcpy((char *) status_buffer, " "); // clear the status buffer
1591 if ((p[-1] != '\n') && (dot>text)) {
1594 } else if (c == erase_char) { // Is this a BS
1596 if ((p[-1] != '\n') && (dot>text)) {
1598 p = text_hole_delete(p, p); // shrink buffer 1 char
1599 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1600 // also rmove char from last_modifying_cmd
1601 if (last_modifying_cmd != 0 && strlen((char *) last_modifying_cmd) > 0) {
1604 q = last_modifying_cmd;
1605 q[strlen((char *) q) - 1] = '\0'; // erase BS
1606 q[strlen((char *) q) - 1] = '\0'; // erase prev char
1608 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1611 // insert a char into text[]
1612 Byte *sp; // "save p"
1615 c = '\n'; // translate \r to \n
1616 sp = p; // remember addr of insert
1617 p = stupid_insert(p, c); // insert the char
1618 #ifdef CONFIG_FEATURE_VI_SETOPTS
1619 if (showmatch && strchr(")]}", *sp) != NULL) {
1622 if (autoindent && c == '\n') { // auto indent the new line
1625 q = prev_line(p); // use prev line as templet
1626 for (; isblnk(*q); q++) {
1627 p = stupid_insert(p, *q); // insert the char
1630 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1635 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1637 p = text_hole_make(p, 1);
1640 file_modified = TRUE; // has the file been modified
1646 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1648 Byte *save_dot, *p, *q;
1654 if (strchr("cdy><", c)) {
1655 // these cmds operate on whole lines
1656 p = q = begin_line(p);
1657 for (cnt = 1; cnt < cmdcnt; cnt++) {
1661 } else if (strchr("^%$0bBeEft", c)) {
1662 // These cmds operate on char positions
1663 do_cmd(c); // execute movement cmd
1665 } else if (strchr("wW", c)) {
1666 do_cmd(c); // execute movement cmd
1668 dot--; // move back off of next word
1669 if (dot > text && *dot == '\n')
1670 dot--; // stay off NL
1672 } else if (strchr("H-k{", c)) {
1673 // these operate on multi-lines backwards
1674 q = end_line(dot); // find NL
1675 do_cmd(c); // execute movement cmd
1678 } else if (strchr("L+j}\r\n", c)) {
1679 // these operate on multi-lines forwards
1680 p = begin_line(dot);
1681 do_cmd(c); // execute movement cmd
1682 dot_end(); // find NL
1685 c = 27; // error- return an ESC char
1698 static int st_test(Byte * p, int type, int dir, Byte * tested)
1708 if (type == S_BEFORE_WS) {
1710 test = ((!isspace(c)) || c == '\n');
1712 if (type == S_TO_WS) {
1714 test = ((!isspace(c)) || c == '\n');
1716 if (type == S_OVER_WS) {
1718 test = ((isspace(c)));
1720 if (type == S_END_PUNCT) {
1722 test = ((ispunct(c)));
1724 if (type == S_END_ALNUM) {
1726 test = ((isalnum(c)) || c == '_');
1732 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1736 while (st_test(p, type, dir, &c)) {
1737 // make sure we limit search to correct number of lines
1738 if (c == '\n' && --linecnt < 1)
1740 if (dir >= 0 && p >= end - 1)
1742 if (dir < 0 && p <= text)
1744 p += dir; // move to next char
1749 // find matching char of pair () [] {}
1750 static Byte *find_pair(Byte * p, Byte c)
1757 dir = 1; // assume forward
1781 for (q = p + dir; text <= q && q < end; q += dir) {
1782 // look for match, count levels of pairs (( ))
1784 level++; // increase pair levels
1786 level--; // reduce pair level
1788 break; // found matching pair
1791 q = NULL; // indicate no match
1795 #ifdef CONFIG_FEATURE_VI_SETOPTS
1796 // show the matching char of a pair, () [] {}
1797 static void showmatching(Byte * p)
1801 // we found half of a pair
1802 q = find_pair(p, *p); // get loc of matching char
1804 indicate_error('3'); // no matching char
1806 // "q" now points to matching pair
1807 save_dot = dot; // remember where we are
1808 dot = q; // go to new loc
1809 refresh(FALSE); // let the user see it
1810 (void) mysleep(40); // give user some time
1811 dot = save_dot; // go back to old loc
1815 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1817 // open a hole in text[]
1818 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1827 cnt = end - src; // the rest of buffer
1828 if (memmove(dest, src, cnt) != dest) {
1829 psbs("can't create room for new characters");
1831 memset(p, ' ', size); // clear new hole
1832 end = end + size; // adjust the new END
1833 file_modified = TRUE; // has the file been modified
1838 // close a hole in text[]
1839 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1844 // move forwards, from beginning
1848 if (q < p) { // they are backward- swap them
1852 hole_size = q - p + 1;
1854 if (src < text || src > end)
1856 if (dest < text || dest >= end)
1859 goto thd_atend; // just delete the end of the buffer
1860 if (memmove(dest, src, cnt) != dest) {
1861 psbs("can't delete the character");
1864 end = end - hole_size; // adjust the new END
1866 dest = end - 1; // make sure dest in below end-1
1868 dest = end = text; // keep pointers valid
1869 file_modified = TRUE; // has the file been modified
1874 // copy text into register, then delete text.
1875 // if dist <= 0, do not include, or go past, a NewLine
1877 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1881 // make sure start <= stop
1883 // they are backwards, reverse them
1889 // we can not cross NL boundaries
1893 // dont go past a NewLine
1894 for (; p + 1 <= stop; p++) {
1896 stop = p; // "stop" just before NewLine
1902 #ifdef CONFIG_FEATURE_VI_YANKMARK
1903 text_yank(start, stop, YDreg);
1904 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1905 if (yf == YANKDEL) {
1906 p = text_hole_delete(start, stop);
1911 static void show_help(void)
1913 puts("These features are available:"
1914 #ifdef CONFIG_FEATURE_VI_SEARCH
1915 "\n\tPattern searches with / and ?"
1916 #endif /* CONFIG_FEATURE_VI_SEARCH */
1917 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1918 "\n\tLast command repeat with \'.\'"
1919 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1920 #ifdef CONFIG_FEATURE_VI_YANKMARK
1921 "\n\tLine marking with 'x"
1922 "\n\tNamed buffers with \"x"
1923 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1924 #ifdef CONFIG_FEATURE_VI_READONLY
1925 "\n\tReadonly if vi is called as \"view\""
1926 "\n\tReadonly with -R command line arg"
1927 #endif /* CONFIG_FEATURE_VI_READONLY */
1928 #ifdef CONFIG_FEATURE_VI_SET
1929 "\n\tSome colon mode commands with \':\'"
1930 #endif /* CONFIG_FEATURE_VI_SET */
1931 #ifdef CONFIG_FEATURE_VI_SETOPTS
1932 "\n\tSettable options with \":set\""
1933 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1934 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1935 "\n\tSignal catching- ^C"
1936 "\n\tJob suspend and resume with ^Z"
1937 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1938 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1939 "\n\tAdapt to window re-sizes"
1940 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1944 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1949 strcpy((char *) buf, ""); // init buf
1950 if (strlen((char *) s) <= 0)
1951 s = (Byte *) "(NULL)";
1952 for (; *s > '\0'; s++) {
1956 c_is_no_print = c > 127 && !Isprint(c);
1957 if (c_is_no_print) {
1958 strcat((char *) buf, SOn);
1961 if (c < ' ' || c == 127) {
1962 strcat((char *) buf, "^");
1969 strcat((char *) buf, (char *) b);
1971 strcat((char *) buf, SOs);
1973 strcat((char *) buf, "$");
1978 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1979 static void start_new_cmd_q(Byte c)
1982 free(last_modifying_cmd);
1983 // get buffer for new cmd
1984 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1985 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1986 // if there is a current cmd count put it in the buffer first
1988 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
1989 // save char c onto queue
1990 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
1995 static void end_cmd_q(void)
1997 #ifdef CONFIG_FEATURE_VI_YANKMARK
1998 YDreg = 26; // go back to default Yank/Delete reg
1999 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2003 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2005 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2006 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2010 i = strlen((char *) s);
2011 p = text_hole_make(p, i);
2012 strncpy((char *) p, (char *) s, i);
2013 for (cnt = 0; *s != '\0'; s++) {
2017 #ifdef CONFIG_FEATURE_VI_YANKMARK
2018 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2019 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2022 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2024 #ifdef CONFIG_FEATURE_VI_YANKMARK
2025 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2030 if (q < p) { // they are backwards- reverse them
2037 free(t); // if already a yank register, free it
2038 t = (Byte *) xmalloc(cnt + 1); // get a new register
2039 memset(t, '\0', cnt + 1); // clear new text[]
2040 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2045 static Byte what_reg(void)
2051 c = 'D'; // default to D-reg
2052 if (0 <= YDreg && YDreg <= 25)
2053 c = 'a' + (Byte) YDreg;
2061 static void check_context(Byte cmd)
2063 // A context is defined to be "modifying text"
2064 // Any modifying command establishes a new context.
2066 if (dot < context_start || dot > context_end) {
2067 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2068 // we are trying to modify text[]- make this the current context
2069 mark[27] = mark[26]; // move cur to prev
2070 mark[26] = dot; // move local to cur
2071 context_start = prev_line(prev_line(dot));
2072 context_end = next_line(next_line(dot));
2073 //loiter= start_loiter= now;
2079 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2083 // the current context is in mark[26]
2084 // the previous context is in mark[27]
2085 // only swap context if other context is valid
2086 if (text <= mark[27] && mark[27] <= end - 1) {
2088 mark[27] = mark[26];
2090 p = mark[26]; // where we are going- previous context
2091 context_start = prev_line(prev_line(prev_line(p)));
2092 context_end = next_line(next_line(next_line(p)));
2096 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2098 static int isblnk(Byte c) // is the char a blank or tab
2100 return (c == ' ' || c == '\t');
2103 //----- Set terminal attributes --------------------------------
2104 static void rawmode(void)
2106 tcgetattr(0, &term_orig);
2107 term_vi = term_orig;
2108 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2109 term_vi.c_iflag &= (~IXON & ~ICRNL);
2110 term_vi.c_oflag &= (~ONLCR);
2112 term_vi.c_cc[VMIN] = 1;
2113 term_vi.c_cc[VTIME] = 0;
2115 erase_char = term_vi.c_cc[VERASE];
2116 tcsetattr(0, TCSANOW, &term_vi);
2119 static void cookmode(void)
2122 tcsetattr(0, TCSANOW, &term_orig);
2125 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2126 //----- See what the window size currently is --------------------
2127 static void window_size_get(int sig)
2131 i = ioctl(0, TIOCGWINSZ, &winsize);
2134 winsize.ws_row = 24;
2135 winsize.ws_col = 80;
2137 if (winsize.ws_row <= 1) {
2138 winsize.ws_row = 24;
2140 if (winsize.ws_col <= 1) {
2141 winsize.ws_col = 80;
2143 rows = (int) winsize.ws_row;
2144 columns = (int) winsize.ws_col;
2146 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2148 //----- Come here when we get a window resize signal ---------
2149 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2150 static void winch_sig(int sig)
2152 signal(SIGWINCH, winch_sig);
2153 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2155 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2156 new_screen(rows, columns); // get memory for virtual screen
2157 redraw(TRUE); // re-draw the screen
2160 //----- Come here when we get a continue signal -------------------
2161 static void cont_sig(int sig)
2163 rawmode(); // terminal to "raw"
2164 *status_buffer = '\0'; // clear the status buffer
2165 redraw(TRUE); // re-draw the screen
2167 signal(SIGTSTP, suspend_sig);
2168 signal(SIGCONT, SIG_DFL);
2169 kill(my_pid, SIGCONT);
2172 //----- Come here when we get a Suspend signal -------------------
2173 static void suspend_sig(int sig)
2175 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2176 clear_to_eol(); // Erase to end of line
2177 cookmode(); // terminal to "cooked"
2179 signal(SIGCONT, cont_sig);
2180 signal(SIGTSTP, SIG_DFL);
2181 kill(my_pid, SIGTSTP);
2184 //----- Come here when we get a signal ---------------------------
2185 static void catch_sig(int sig)
2187 signal(SIGHUP, catch_sig);
2188 signal(SIGINT, catch_sig);
2189 signal(SIGTERM, catch_sig);
2190 signal(SIGALRM, catch_sig);
2192 longjmp(restart, sig);
2195 //----- Come here when we get a core dump signal -----------------
2196 static void core_sig(int sig)
2198 signal(SIGQUIT, core_sig);
2199 signal(SIGILL, core_sig);
2200 signal(SIGTRAP, core_sig);
2201 signal(SIGIOT, core_sig);
2202 signal(SIGABRT, core_sig);
2203 signal(SIGFPE, core_sig);
2204 signal(SIGBUS, core_sig);
2205 signal(SIGSEGV, core_sig);
2207 signal(SIGSYS, core_sig);
2210 if(sig) { // signaled
2211 dot = bound_dot(dot); // make sure "dot" is valid
2213 longjmp(restart, sig);
2216 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2218 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2220 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2225 tv.tv_usec = hund * 10000;
2226 select(1, &rfds, NULL, NULL, &tv);
2227 return (FD_ISSET(0, &rfds));
2230 static Byte readbuffer[BUFSIZ];
2231 static int readed_for_parse;
2233 //----- IO Routines --------------------------------------------
2234 static Byte readit(void) // read (maybe cursor) key from stdin
2243 static const struct esc_cmds esccmds[] = {
2244 {"OA", (Byte) VI_K_UP}, // cursor key Up
2245 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2246 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2247 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2248 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2249 {"OF", (Byte) VI_K_END}, // Cursor Key End
2250 {"[A", (Byte) VI_K_UP}, // cursor key Up
2251 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2252 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2253 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2254 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2255 {"[F", (Byte) VI_K_END}, // Cursor Key End
2256 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2257 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2258 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2259 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2260 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2261 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2262 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2263 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2264 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2265 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2266 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2267 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2268 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2269 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2270 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2271 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2272 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2273 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2274 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2275 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2276 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2279 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2281 (void) alarm(0); // turn alarm OFF while we wait for input
2283 n = readed_for_parse;
2284 // get input from User- are there already input chars in Q?
2287 // the Q is empty, wait for a typed char
2288 n = read(0, readbuffer, BUFSIZ - 1);
2291 goto ri0; // interrupted sys call
2294 if (errno == EFAULT)
2296 if (errno == EINVAL)
2304 if (readbuffer[0] == 27) {
2305 // This is an ESC char. Is this Esc sequence?
2306 // Could be bare Esc key. See if there are any
2307 // more chars to read after the ESC. This would
2308 // be a Function or Cursor Key sequence.
2312 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2314 // keep reading while there are input chars and room in buffer
2315 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2316 // read the rest of the ESC string
2317 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2323 readed_for_parse = n;
2326 if(c == 27 && n > 1) {
2327 // Maybe cursor or function key?
2328 const struct esc_cmds *eindex;
2330 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2331 int cnt = strlen(eindex->seq);
2335 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2337 // is a Cursor key- put derived value back into Q
2339 // for squeeze out the ESC sequence
2343 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2344 /* defined ESC sequence not found, set only one ESC */
2350 // remove key sequence from Q
2351 readed_for_parse -= n;
2352 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2353 (void) alarm(3); // we are done waiting for input, turn alarm ON
2357 //----- IO Routines --------------------------------------------
2358 static Byte get_one_char()
2362 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2363 // ! adding2q && ioq == 0 read()
2364 // ! adding2q && ioq != 0 *ioq
2365 // adding2q *last_modifying_cmd= read()
2367 // we are not adding to the q.
2368 // but, we may be reading from a q
2370 // there is no current q, read from STDIN
2371 c = readit(); // get the users input
2373 // there is a queue to get chars from first
2376 // the end of the q, read from STDIN
2378 ioq_start = ioq = 0;
2379 c = readit(); // get the users input
2383 // adding STDIN chars to q
2384 c = readit(); // get the users input
2385 if (last_modifying_cmd != 0) {
2386 int len = strlen((char *) last_modifying_cmd);
2387 if (len + 1 >= BUFSIZ) {
2388 psbs("last_modifying_cmd overrun");
2390 // add new char to q
2391 last_modifying_cmd[len] = c;
2395 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2396 c = readit(); // get the users input
2397 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2398 return (c); // return the char, where ever it came from
2401 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2406 static Byte *obufp = NULL;
2408 strcpy((char *) buf, (char *) prompt);
2409 *status_buffer = '\0'; // clear the status buffer
2410 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2411 clear_to_eol(); // clear the line
2412 write1(prompt); // write out the :, /, or ? prompt
2414 for (i = strlen((char *) buf); i < BUFSIZ;) {
2415 c = get_one_char(); // read user input
2416 if (c == '\n' || c == '\r' || c == 27)
2417 break; // is this end of input
2418 if (c == erase_char) { // user wants to erase prev char
2419 i--; // backup to prev char
2420 buf[i] = '\0'; // erase the char
2421 buf[i + 1] = '\0'; // null terminate buffer
2422 write1("\b \b"); // erase char on screen
2423 if (i <= 0) { // user backs up before b-o-l, exit
2427 buf[i] = c; // save char in buffer
2428 buf[i + 1] = '\0'; // make sure buffer is null terminated
2429 putchar(c); // echo the char back to user
2435 obufp = (Byte *) bb_xstrdup((char *) buf);
2439 static int file_size(const Byte * fn) // what is the byte size of "fn"
2444 if (fn == 0 || strlen(fn) <= 0)
2447 sr = stat((char *) fn, &st_buf); // see if file exists
2449 cnt = (int) st_buf.st_size;
2454 static int file_insert(Byte * fn, Byte * p, int size)
2459 #ifdef CONFIG_FEATURE_VI_READONLY
2461 #endif /* CONFIG_FEATURE_VI_READONLY */
2462 if (fn == 0 || strlen((char*) fn) <= 0) {
2463 psbs("No filename given");
2467 // OK- this is just a no-op
2472 psbs("Trying to insert a negative number (%d) of characters", size);
2475 if (p < text || p > end) {
2476 psbs("Trying to insert file outside of memory");
2480 // see if we can open the file
2481 #ifdef CONFIG_FEATURE_VI_READONLY
2482 if (vi_readonly) goto fi1; // do not try write-mode
2484 fd = open((char *) fn, O_RDWR); // assume read & write
2486 // could not open for writing- maybe file is read only
2487 #ifdef CONFIG_FEATURE_VI_READONLY
2490 fd = open((char *) fn, O_RDONLY); // try read-only
2492 psbs("\"%s\" %s", fn, "could not open file");
2495 #ifdef CONFIG_FEATURE_VI_READONLY
2496 // got the file- read-only
2498 #endif /* CONFIG_FEATURE_VI_READONLY */
2500 p = text_hole_make(p, size);
2501 cnt = read(fd, p, size);
2505 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2506 psbs("could not read file \"%s\"", fn);
2507 } else if (cnt < size) {
2508 // There was a partial read, shrink unused space text[]
2509 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2510 psbs("could not read all of file \"%s\"", fn);
2513 file_modified = TRUE;
2518 static int file_write(Byte * fn, Byte * first, Byte * last)
2520 int fd, cnt, charcnt;
2523 psbs("No current filename");
2527 // FIXIT- use the correct umask()
2528 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2531 cnt = last - first + 1;
2532 charcnt = write(fd, first, cnt);
2533 if (charcnt == cnt) {
2535 //file_modified= FALSE; // the file has not been modified
2543 //----- Terminal Drawing ---------------------------------------
2544 // The terminal is made up of 'rows' line of 'columns' columns.
2545 // classicly this would be 24 x 80.
2546 // screen coordinates
2552 // 23,0 ... 23,79 status line
2555 //----- Move the cursor to row x col (count from 0, not 1) -------
2556 static void place_cursor(int row, int col, int opti)
2560 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2563 // char cm3[BUFSIZ];
2565 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2567 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2569 if (row < 0) row = 0;
2570 if (row >= rows) row = rows - 1;
2571 if (col < 0) col = 0;
2572 if (col >= columns) col = columns - 1;
2574 //----- 1. Try the standard terminal ESC sequence
2575 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2577 if (! opti) goto pc0;
2579 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2580 //----- find the minimum # of chars to move cursor -------------
2581 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2582 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2584 // move to the correct row
2585 while (row < Rrow) {
2586 // the cursor has to move up
2590 while (row > Rrow) {
2591 // the cursor has to move down
2592 strcat(cm2, CMdown);
2596 // now move to the correct column
2597 strcat(cm2, "\r"); // start at col 0
2598 // just send out orignal source char to get to correct place
2599 screenp = &screen[row * columns]; // start of screen line
2600 strncat(cm2, screenp, col);
2602 //----- 3. Try some other way of moving cursor
2603 //---------------------------------------------
2605 // pick the shortest cursor motion to send out
2607 if (strlen(cm2) < strlen(cm)) {
2609 } /* else if (strlen(cm3) < strlen(cm)) {
2612 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2614 write1(cm); // move the cursor
2617 //----- Erase from cursor to end of line -----------------------
2618 static void clear_to_eol()
2620 write1(Ceol); // Erase from cursor to end of line
2623 //----- Erase from cursor to end of screen -----------------------
2624 static void clear_to_eos()
2626 write1(Ceos); // Erase from cursor to end of screen
2629 //----- Start standout mode ------------------------------------
2630 static void standout_start() // send "start reverse video" sequence
2632 write1(SOs); // Start reverse video mode
2635 //----- End standout mode --------------------------------------
2636 static void standout_end() // send "end reverse video" sequence
2638 write1(SOn); // End reverse video mode
2641 //----- Flash the screen --------------------------------------
2642 static void flash(int h)
2644 standout_start(); // send "start reverse video" sequence
2647 standout_end(); // send "end reverse video" sequence
2651 static void Indicate_Error(void)
2653 #ifdef CONFIG_FEATURE_VI_CRASHME
2655 return; // generate a random command
2656 #endif /* CONFIG_FEATURE_VI_CRASHME */
2658 write1(bell); // send out a bell character
2664 //----- Screen[] Routines --------------------------------------
2665 //----- Erase the Screen[] memory ------------------------------
2666 static void screen_erase()
2668 memset(screen, ' ', screensize); // clear new screen
2671 //----- Draw the status line at bottom of the screen -------------
2672 static void show_status_line(void)
2674 static int last_cksum;
2677 cnt = strlen((char *) status_buffer);
2678 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
2679 // don't write the status line unless it changes
2680 if (cnt > 0 && last_cksum != cksum) {
2681 last_cksum= cksum; // remember if we have seen this line
2682 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2683 write1(status_buffer);
2685 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2689 //----- format the status buffer, the bottom line of screen ------
2690 // print status buffer, with STANDOUT mode
2691 static void psbs(const char *format, ...)
2695 va_start(args, format);
2696 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2697 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
2699 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2705 // print status buffer
2706 static void psb(const char *format, ...)
2710 va_start(args, format);
2711 vsprintf((char *) status_buffer, format, args);
2716 static void ni(Byte * s) // display messages
2720 print_literal(buf, s);
2721 psbs("\'%s\' is not implemented", buf);
2724 static void edit_status(void) // show file status on status line
2726 int cur, tot, percent;
2728 cur = count_lines(text, dot);
2729 tot = count_lines(text, end - 1);
2730 // current line percent
2731 // ------------- ~~ ----------
2734 percent = (100 * cur) / tot;
2740 #ifdef CONFIG_FEATURE_VI_READONLY
2742 #endif /* CONFIG_FEATURE_VI_READONLY */
2743 "%s line %d of %d --%d%%--",
2744 (cfn != 0 ? (char *) cfn : "No file"),
2745 #ifdef CONFIG_FEATURE_VI_READONLY
2746 ((vi_readonly || readonly) ? " [Read only]" : ""),
2747 #endif /* CONFIG_FEATURE_VI_READONLY */
2748 (file_modified ? " [modified]" : ""),
2752 //----- Force refresh of all Lines -----------------------------
2753 static void redraw(int full_screen)
2755 place_cursor(0, 0, FALSE); // put cursor in correct place
2756 clear_to_eos(); // tel terminal to erase display
2757 screen_erase(); // erase the internal screen buffer
2758 refresh(full_screen); // this will redraw the entire display
2761 //----- Format a text[] line into a buffer ---------------------
2762 static void format_line(Byte *dest, Byte *src, int li)
2767 for (co= 0; co < MAX_SCR_COLS; co++) {
2768 c= ' '; // assume blank
2769 if (li > 0 && co == 0) {
2770 c = '~'; // not first line, assume Tilde
2772 // are there chars in text[] and have we gone past the end
2773 if (text < end && src < end) {
2778 if (c > 127 && !Isprint(c)) {
2781 if (c < ' ' || c == 127) {
2785 for (; (co % tabstop) != (tabstop - 1); co++) {
2793 c += '@'; // make it visible
2796 // the co++ is done here so that the column will
2797 // not be overwritten when we blank-out the rest of line
2804 //----- Refresh the changed screen lines -----------------------
2805 // Copy the source line from text[] into the buffer and note
2806 // if the current screenline is different from the new buffer.
2807 // If they differ then that line needs redrawing on the terminal.
2809 static void refresh(int full_screen)
2811 static int old_offset;
2813 Byte buf[MAX_SCR_COLS];
2814 Byte *tp, *sp; // pointer into text[] and screen[]
2815 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2816 int last_li= -2; // last line that changed- for optimizing cursor movement
2817 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2819 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2821 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2822 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2823 tp = screenbegin; // index into text[] of top line
2825 // compare text[] to screen[] and mark screen[] lines that need updating
2826 for (li = 0; li < rows - 1; li++) {
2827 int cs, ce; // column start & end
2828 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2829 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2830 // format current text line into buf
2831 format_line(buf, tp, li);
2833 // skip to the end of the current text[] line
2834 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2836 // see if there are any changes between vitual screen and buf
2837 changed = FALSE; // assume no change
2840 sp = &screen[li * columns]; // start of screen line
2842 // force re-draw of every single column from 0 - columns-1
2845 // compare newly formatted buffer with virtual screen
2846 // look forward for first difference between buf and screen
2847 for ( ; cs <= ce; cs++) {
2848 if (buf[cs + offset] != sp[cs]) {
2849 changed = TRUE; // mark for redraw
2854 // look backward for last difference between buf and screen
2855 for ( ; ce >= cs; ce--) {
2856 if (buf[ce + offset] != sp[ce]) {
2857 changed = TRUE; // mark for redraw
2861 // now, cs is index of first diff, and ce is index of last diff
2863 // if horz offset has changed, force a redraw
2864 if (offset != old_offset) {
2869 // make a sanity check of columns indexes
2871 if (ce > columns-1) ce= columns-1;
2872 if (cs > ce) { cs= 0; ce= columns-1; }
2873 // is there a change between vitual screen and buf
2875 // copy changed part of buffer to virtual screen
2876 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2878 // move cursor to column of first change
2879 if (offset != old_offset) {
2880 // opti_cur_move is still too stupid
2881 // to handle offsets correctly
2882 place_cursor(li, cs, FALSE);
2884 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2885 // if this just the next line
2886 // try to optimize cursor movement
2887 // otherwise, use standard ESC sequence
2888 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2890 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2891 place_cursor(li, cs, FALSE); // use standard ESC sequence
2892 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2895 // write line out to terminal
2905 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2907 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2911 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2912 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2915 place_cursor(crow, ccol, FALSE);
2916 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2918 if (offset != old_offset)
2919 old_offset = offset;
2922 //---------------------------------------------------------------------
2923 //----- the Ascii Chart -----------------------------------------------
2925 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2926 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2927 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2928 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2929 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2930 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2931 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2932 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2933 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2934 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2935 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2936 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2937 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2938 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2939 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2940 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2941 //---------------------------------------------------------------------
2943 //----- Execute a Vi Command -----------------------------------
2944 static void do_cmd(Byte c)
2946 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2947 int cnt, i, j, dir, yf;
2949 c1 = c; // quiet the compiler
2950 cnt = yf = dir = 0; // quiet the compiler
2951 p = q = save_dot = msg = buf; // quiet the compiler
2952 memset(buf, '\0', 9); // clear buf
2954 /* if this is a cursor key, skip these checks */
2967 if (cmd_mode == 2) {
2968 // flip-flop Insert/Replace mode
2969 if (c == VI_K_INSERT) goto dc_i;
2970 // we are 'R'eplacing the current *dot with new char
2972 // don't Replace past E-o-l
2973 cmd_mode = 1; // convert to insert
2975 if (1 <= c || Isprint(c)) {
2977 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2978 dot = char_insert(dot, c); // insert new char
2983 if (cmd_mode == 1) {
2984 // hitting "Insert" twice means "R" replace mode
2985 if (c == VI_K_INSERT) goto dc5;
2986 // insert the char c at "dot"
2987 if (1 <= c || Isprint(c)) {
2988 dot = char_insert(dot, c);
3003 #ifdef CONFIG_FEATURE_VI_CRASHME
3004 case 0x14: // dc4 ctrl-T
3005 crashme = (crashme == 0) ? 1 : 0;
3007 #endif /* CONFIG_FEATURE_VI_CRASHME */
3036 //case 'u': // u- FIXME- there is no undo
3038 default: // unrecognised command
3047 end_cmd_q(); // stop adding to q
3048 case 0x00: // nul- ignore
3050 case 2: // ctrl-B scroll up full screen
3051 case VI_K_PAGEUP: // Cursor Key Page Up
3052 dot_scroll(rows - 2, -1);
3054 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3055 case 0x03: // ctrl-C interrupt
3056 longjmp(restart, 1);
3058 case 26: // ctrl-Z suspend
3059 suspend_sig(SIGTSTP);
3061 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3062 case 4: // ctrl-D scroll down half screen
3063 dot_scroll((rows - 2) / 2, 1);
3065 case 5: // ctrl-E scroll down one line
3068 case 6: // ctrl-F scroll down full screen
3069 case VI_K_PAGEDOWN: // Cursor Key Page Down
3070 dot_scroll(rows - 2, 1);
3072 case 7: // ctrl-G show current status
3075 case 'h': // h- move left
3076 case VI_K_LEFT: // cursor key Left
3077 case 8: // ctrl-H- move left (This may be ERASE char)
3078 case 127: // DEL- move left (This may be ERASE char)
3084 case 10: // Newline ^J
3085 case 'j': // j- goto next line, same col
3086 case VI_K_DOWN: // cursor key Down
3090 dot_next(); // go to next B-o-l
3091 dot = move_to_col(dot, ccol + offset); // try stay in same col
3093 case 12: // ctrl-L force redraw whole screen
3094 case 18: // ctrl-R force redraw
3095 place_cursor(0, 0, FALSE); // put cursor in correct place
3096 clear_to_eos(); // tel terminal to erase display
3098 screen_erase(); // erase the internal screen buffer
3099 refresh(TRUE); // this will redraw the entire display
3101 case 13: // Carriage Return ^M
3102 case '+': // +- goto next line
3109 case 21: // ctrl-U scroll up half screen
3110 dot_scroll((rows - 2) / 2, -1);
3112 case 25: // ctrl-Y scroll up one line
3118 cmd_mode = 0; // stop insrting
3120 *status_buffer = '\0'; // clear status buffer
3122 case ' ': // move right
3123 case 'l': // move right
3124 case VI_K_RIGHT: // Cursor Key Right
3130 #ifdef CONFIG_FEATURE_VI_YANKMARK
3131 case '"': // "- name a register to use for Delete/Yank
3132 c1 = get_one_char();
3140 case '\'': // '- goto a specific mark
3141 c1 = get_one_char();
3147 if (text <= q && q < end) {
3149 dot_begin(); // go to B-o-l
3152 } else if (c1 == '\'') { // goto previous context
3153 dot = swap_context(dot); // swap current and previous context
3154 dot_begin(); // go to B-o-l
3160 case 'm': // m- Mark a line
3161 // this is really stupid. If there are any inserts or deletes
3162 // between text[0] and dot then this mark will not point to the
3163 // correct location! It could be off by many lines!
3164 // Well..., at least its quick and dirty.
3165 c1 = get_one_char();
3169 // remember the line
3170 mark[(int) c1] = dot;
3175 case 'P': // P- Put register before
3176 case 'p': // p- put register after
3179 psbs("Nothing in register %c", what_reg());
3182 // are we putting whole lines or strings
3183 if (strchr((char *) p, '\n') != NULL) {
3185 dot_begin(); // putting lines- Put above
3188 // are we putting after very last line?
3189 if (end_line(dot) == (end - 1)) {
3190 dot = end; // force dot to end of text[]
3192 dot_next(); // next line, then put before
3197 dot_right(); // move to right, can move to NL
3199 dot = string_insert(dot, p); // insert the string
3200 end_cmd_q(); // stop adding to q
3202 case 'U': // U- Undo; replace current line with original version
3203 if (reg[Ureg] != 0) {
3204 p = begin_line(dot);
3206 p = text_hole_delete(p, q); // delete cur line
3207 p = string_insert(p, reg[Ureg]); // insert orig line
3212 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3213 case '$': // $- goto end of line
3214 case VI_K_END: // Cursor Key End
3218 dot = end_line(dot + 1);
3220 case '%': // %- find matching char of pair () [] {}
3221 for (q = dot; q < end && *q != '\n'; q++) {
3222 if (strchr("()[]{}", *q) != NULL) {
3223 // we found half of a pair
3224 p = find_pair(q, *q);
3236 case 'f': // f- forward to a user specified char
3237 last_forward_char = get_one_char(); // get the search char
3239 // dont seperate these two commands. 'f' depends on ';'
3241 //**** fall thru to ... 'i'
3242 case ';': // ;- look at rest of line for last forward char
3246 if (last_forward_char == 0) break;
3248 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3251 if (*q == last_forward_char)
3254 case '-': // -- goto prev line
3261 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3262 case '.': // .- repeat the last modifying command
3263 // Stuff the last_modifying_cmd back into stdin
3264 // and let it be re-executed.
3265 if (last_modifying_cmd != 0) {
3266 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3269 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3270 #ifdef CONFIG_FEATURE_VI_SEARCH
3271 case '?': // /- search for a pattern
3272 case '/': // /- search for a pattern
3275 q = get_input_line(buf); // get input line- use "status line"
3276 if (strlen((char *) q) == 1)
3277 goto dc3; // if no pat re-use old pat
3278 if (strlen((char *) q) > 1) { // new pat- save it and find
3279 // there is a new pat
3280 free(last_search_pattern);
3281 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3282 goto dc3; // now find the pattern
3284 // user changed mind and erased the "/"- do nothing
3286 case 'N': // N- backward search for last pattern
3290 dir = BACK; // assume BACKWARD search
3292 if (last_search_pattern[0] == '?') {
3296 goto dc4; // now search for pattern
3298 case 'n': // n- repeat search for last pattern
3299 // search rest of text[] starting at next char
3300 // if search fails return orignal "p" not the "p+1" address
3305 if (last_search_pattern == 0) {
3306 msg = (Byte *) "No previous regular expression";
3309 if (last_search_pattern[0] == '/') {
3310 dir = FORWARD; // assume FORWARD search
3313 if (last_search_pattern[0] == '?') {
3318 q = char_search(p, last_search_pattern + 1, dir, FULL);
3320 dot = q; // good search, update "dot"
3324 // no pattern found between "dot" and "end"- continue at top
3329 q = char_search(p, last_search_pattern + 1, dir, FULL);
3330 if (q != NULL) { // found something
3331 dot = q; // found new pattern- goto it
3332 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3334 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3337 msg = (Byte *) "Pattern not found";
3342 case '{': // {- move backward paragraph
3343 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3344 if (q != NULL) { // found blank line
3345 dot = next_line(q); // move to next blank line
3348 case '}': // }- move forward paragraph
3349 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3350 if (q != NULL) { // found blank line
3351 dot = next_line(q); // move to next blank line
3354 #endif /* CONFIG_FEATURE_VI_SEARCH */
3355 case '0': // 0- goto begining of line
3365 if (c == '0' && cmdcnt < 1) {
3366 dot_begin(); // this was a standalone zero
3368 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3371 case ':': // :- the colon mode commands
3372 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3373 #ifdef CONFIG_FEATURE_VI_COLON
3374 colon(p); // execute the command
3375 #else /* CONFIG_FEATURE_VI_COLON */
3377 p++; // move past the ':'
3378 cnt = strlen((char *) p);
3381 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3382 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3383 if (file_modified && p[1] != '!') {
3384 psbs("No write since last change (:quit! overrides)");
3388 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3389 strncasecmp((char *) p, "wq", cnt) == 0 ||
3390 strncasecmp((char *) p, "x", cnt) == 0) {
3391 cnt = file_write(cfn, text, end - 1);
3392 file_modified = FALSE;
3393 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3394 if (p[0] == 'x' || p[1] == 'q') {
3397 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3398 edit_status(); // show current file status
3399 } else if (sscanf((char *) p, "%d", &j) > 0) {
3400 dot = find_line(j); // go to line # j
3402 } else { // unrecognised cmd
3405 #endif /* CONFIG_FEATURE_VI_COLON */
3407 case '<': // <- Left shift something
3408 case '>': // >- Right shift something
3409 cnt = count_lines(text, dot); // remember what line we are on
3410 c1 = get_one_char(); // get the type of thing to delete
3411 find_range(&p, &q, c1);
3412 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3415 i = count_lines(p, q); // # of lines we are shifting
3416 for ( ; i > 0; i--, p = next_line(p)) {
3418 // shift left- remove tab or 8 spaces
3420 // shrink buffer 1 char
3421 (void) text_hole_delete(p, p);
3422 } else if (*p == ' ') {
3423 // we should be calculating columns, not just SPACE
3424 for (j = 0; *p == ' ' && j < tabstop; j++) {
3425 (void) text_hole_delete(p, p);
3428 } else if (c == '>') {
3429 // shift right -- add tab or 8 spaces
3430 (void) char_insert(p, '\t');
3433 dot = find_line(cnt); // what line were we on
3435 end_cmd_q(); // stop adding to q
3437 case 'A': // A- append at e-o-l
3438 dot_end(); // go to e-o-l
3439 //**** fall thru to ... 'a'
3440 case 'a': // a- append after current char
3445 case 'B': // B- back a blank-delimited Word
3446 case 'E': // E- end of a blank-delimited word
3447 case 'W': // W- forward a blank-delimited word
3454 if (c == 'W' || isspace(dot[dir])) {
3455 dot = skip_thing(dot, 1, dir, S_TO_WS);
3456 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3459 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3461 case 'C': // C- Change to e-o-l
3462 case 'D': // D- delete to e-o-l
3464 dot = dollar_line(dot); // move to before NL
3465 // copy text into a register and delete
3466 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3468 goto dc_i; // start inserting
3469 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3471 end_cmd_q(); // stop adding to q
3472 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3474 case 'G': // G- goto to a line number (default= E-O-F)
3475 dot = end - 1; // assume E-O-F
3477 dot = find_line(cmdcnt); // what line is #cmdcnt
3481 case 'H': // H- goto top line on screen
3483 if (cmdcnt > (rows - 1)) {
3484 cmdcnt = (rows - 1);
3491 case 'I': // I- insert before first non-blank
3494 //**** fall thru to ... 'i'
3495 case 'i': // i- insert before current char
3496 case VI_K_INSERT: // Cursor Key Insert
3498 cmd_mode = 1; // start insrting
3499 psb("-- Insert --");
3501 case 'J': // J- join current and next lines together
3505 dot_end(); // move to NL
3506 if (dot < end - 1) { // make sure not last char in text[]
3507 *dot++ = ' '; // replace NL with space
3508 while (isblnk(*dot)) { // delete leading WS
3512 end_cmd_q(); // stop adding to q
3514 case 'L': // L- goto bottom line on screen
3516 if (cmdcnt > (rows - 1)) {
3517 cmdcnt = (rows - 1);
3525 case 'M': // M- goto middle line on screen
3527 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3528 dot = next_line(dot);
3530 case 'O': // O- open a empty line above
3532 p = begin_line(dot);
3533 if (p[-1] == '\n') {
3535 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3537 dot = char_insert(dot, '\n');
3540 dot = char_insert(dot, '\n'); // i\n ESC
3545 case 'R': // R- continuous Replace char
3548 psb("-- Replace --");
3550 case 'X': // X- delete char before dot
3551 case 'x': // x- delete the current char
3552 case 's': // s- substitute the current char
3559 if (dot[dir] != '\n') {
3561 dot--; // delete prev char
3562 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3565 goto dc_i; // start insrting
3566 end_cmd_q(); // stop adding to q
3568 case 'Z': // Z- if modified, {write}; exit
3569 // ZZ means to save file (if necessary), then exit
3570 c1 = get_one_char();
3576 #ifdef CONFIG_FEATURE_VI_READONLY
3579 #endif /* CONFIG_FEATURE_VI_READONLY */
3581 cnt = file_write(cfn, text, end - 1);
3582 if (cnt == (end - 1 - text + 1)) {
3589 case '^': // ^- move to first non-blank on line
3593 case 'b': // b- back a word
3594 case 'e': // e- end of word
3601 if ((dot + dir) < text || (dot + dir) > end - 1)
3604 if (isspace(*dot)) {
3605 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3607 if (isalnum(*dot) || *dot == '_') {
3608 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3609 } else if (ispunct(*dot)) {
3610 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3613 case 'c': // c- change something
3614 case 'd': // d- delete something
3615 #ifdef CONFIG_FEATURE_VI_YANKMARK
3616 case 'y': // y- yank something
3617 case 'Y': // Y- Yank a line
3618 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3619 yf = YANKDEL; // assume either "c" or "d"
3620 #ifdef CONFIG_FEATURE_VI_YANKMARK
3621 if (c == 'y' || c == 'Y')
3623 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3626 c1 = get_one_char(); // get the type of thing to delete
3627 find_range(&p, &q, c1);
3628 if (c1 == 27) { // ESC- user changed mind and wants out
3629 c = c1 = 27; // Escape- do nothing
3630 } else if (strchr("wW", c1)) {
3632 // don't include trailing WS as part of word
3633 while (isblnk(*q)) {
3634 if (q <= text || q[-1] == '\n')
3639 dot = yank_delete(p, q, 0, yf); // delete word
3640 } else if (strchr("^0bBeEft$", c1)) {
3641 // single line copy text into a register and delete
3642 dot = yank_delete(p, q, 0, yf); // delete word
3643 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3644 // multiple line copy text into a register and delete
3645 dot = yank_delete(p, q, 1, yf); // delete lines
3647 dot = char_insert(dot, '\n');
3648 // on the last line of file don't move to prev line
3649 if (dot != (end-1)) {
3652 } else if (c == 'd') {
3657 // could not recognize object
3658 c = c1 = 27; // error-
3662 // if CHANGING, not deleting, start inserting after the delete
3664 strcpy((char *) buf, "Change");
3665 goto dc_i; // start inserting
3668 strcpy((char *) buf, "Delete");
3670 #ifdef CONFIG_FEATURE_VI_YANKMARK
3671 if (c == 'y' || c == 'Y') {
3672 strcpy((char *) buf, "Yank");
3675 q = p + strlen((char *) p);
3676 for (cnt = 0; p <= q; p++) {
3680 psb("%s %d lines (%d chars) using [%c]",
3681 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3682 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3683 end_cmd_q(); // stop adding to q
3686 case 'k': // k- goto prev line, same col
3687 case VI_K_UP: // cursor key Up
3692 dot = move_to_col(dot, ccol + offset); // try stay in same col
3694 case 'r': // r- replace the current char with user input
3695 c1 = get_one_char(); // get the replacement char
3698 file_modified = TRUE; // has the file been modified
3700 end_cmd_q(); // stop adding to q
3702 case 't': // t- move to char prior to next x
3703 last_forward_char = get_one_char();
3705 if (*dot == last_forward_char)
3707 last_forward_char= 0;
3709 case 'w': // w- forward a word
3713 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3714 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3715 } else if (ispunct(*dot)) { // we are on PUNCT
3716 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3719 dot++; // move over word
3720 if (isspace(*dot)) {
3721 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3725 c1 = get_one_char(); // get the replacement char
3728 cnt = (rows - 2) / 2; // put dot at center
3730 cnt = rows - 2; // put dot at bottom
3731 screenbegin = begin_line(dot); // start dot at top
3732 dot_scroll(cnt, -1);
3734 case '|': // |- move to column "cmdcnt"
3735 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3737 case '~': // ~- flip the case of letters a-z -> A-Z
3741 if (islower(*dot)) {
3742 *dot = toupper(*dot);
3743 file_modified = TRUE; // has the file been modified
3744 } else if (isupper(*dot)) {
3745 *dot = tolower(*dot);
3746 file_modified = TRUE; // has the file been modified
3749 end_cmd_q(); // stop adding to q
3751 //----- The Cursor and Function Keys -----------------------------
3752 case VI_K_HOME: // Cursor Key Home
3755 // The Fn keys could point to do_macro which could translate them
3756 case VI_K_FUN1: // Function Key F1
3757 case VI_K_FUN2: // Function Key F2
3758 case VI_K_FUN3: // Function Key F3
3759 case VI_K_FUN4: // Function Key F4
3760 case VI_K_FUN5: // Function Key F5
3761 case VI_K_FUN6: // Function Key F6
3762 case VI_K_FUN7: // Function Key F7
3763 case VI_K_FUN8: // Function Key F8
3764 case VI_K_FUN9: // Function Key F9
3765 case VI_K_FUN10: // Function Key F10
3766 case VI_K_FUN11: // Function Key F11
3767 case VI_K_FUN12: // Function Key F12
3772 // if text[] just became empty, add back an empty line
3774 (void) char_insert(text, '\n'); // start empty buf with dummy line
3777 // it is OK for dot to exactly equal to end, otherwise check dot validity
3779 dot = bound_dot(dot); // make sure "dot" is valid
3781 #ifdef CONFIG_FEATURE_VI_YANKMARK
3782 check_context(c); // update the current context
3783 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3786 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3787 cnt = dot - begin_line(dot);
3788 // Try to stay off of the Newline
3789 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3793 #ifdef CONFIG_FEATURE_VI_CRASHME
3794 static int totalcmds = 0;
3795 static int Mp = 85; // Movement command Probability
3796 static int Np = 90; // Non-movement command Probability
3797 static int Dp = 96; // Delete command Probability
3798 static int Ip = 97; // Insert command Probability
3799 static int Yp = 98; // Yank command Probability
3800 static int Pp = 99; // Put command Probability
3801 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3802 char chars[20] = "\t012345 abcdABCD-=.$";
3803 char *words[20] = { "this", "is", "a", "test",
3804 "broadcast", "the", "emergency", "of",
3805 "system", "quick", "brown", "fox",
3806 "jumped", "over", "lazy", "dogs",
3807 "back", "January", "Febuary", "March"
3810 "You should have received a copy of the GNU General Public License\n",
3811 "char c, cm, *cmd, *cmd1;\n",
3812 "generate a command by percentages\n",
3813 "Numbers may be typed as a prefix to some commands.\n",
3814 "Quit, discarding changes!\n",
3815 "Forced write, if permission originally not valid.\n",
3816 "In general, any ex or ed command (such as substitute or delete).\n",
3817 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3818 "Please get w/ me and I will go over it with you.\n",
3819 "The following is a list of scheduled, committed changes.\n",
3820 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3821 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3822 "Any question about transactions please contact Sterling Huxley.\n",
3823 "I will try to get back to you by Friday, December 31.\n",
3824 "This Change will be implemented on Friday.\n",
3825 "Let me know if you have problems accessing this;\n",
3826 "Sterling Huxley recently added you to the access list.\n",
3827 "Would you like to go to lunch?\n",
3828 "The last command will be automatically run.\n",
3829 "This is too much english for a computer geek.\n",
3831 char *multilines[20] = {
3832 "You should have received a copy of the GNU General Public License\n",
3833 "char c, cm, *cmd, *cmd1;\n",
3834 "generate a command by percentages\n",
3835 "Numbers may be typed as a prefix to some commands.\n",
3836 "Quit, discarding changes!\n",
3837 "Forced write, if permission originally not valid.\n",
3838 "In general, any ex or ed command (such as substitute or delete).\n",
3839 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3840 "Please get w/ me and I will go over it with you.\n",
3841 "The following is a list of scheduled, committed changes.\n",
3842 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3843 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3844 "Any question about transactions please contact Sterling Huxley.\n",
3845 "I will try to get back to you by Friday, December 31.\n",
3846 "This Change will be implemented on Friday.\n",
3847 "Let me know if you have problems accessing this;\n",
3848 "Sterling Huxley recently added you to the access list.\n",
3849 "Would you like to go to lunch?\n",
3850 "The last command will be automatically run.\n",
3851 "This is too much english for a computer geek.\n",
3854 // create a random command to execute
3855 static void crash_dummy()
3857 static int sleeptime; // how long to pause between commands
3858 char c, cm, *cmd, *cmd1;
3859 int i, cnt, thing, rbi, startrbi, percent;
3861 // "dot" movement commands
3862 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3864 // is there already a command running?
3865 if (readed_for_parse > 0)
3869 sleeptime = 0; // how long to pause between commands
3870 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3871 // generate a command by percentages
3872 percent = (int) lrand48() % 100; // get a number from 0-99
3873 if (percent < Mp) { // Movement commands
3874 // available commands
3877 } else if (percent < Np) { // non-movement commands
3878 cmd = "mz<>\'\""; // available commands
3880 } else if (percent < Dp) { // Delete commands
3881 cmd = "dx"; // available commands
3883 } else if (percent < Ip) { // Inset commands
3884 cmd = "iIaAsrJ"; // available commands
3886 } else if (percent < Yp) { // Yank commands
3887 cmd = "yY"; // available commands
3889 } else if (percent < Pp) { // Put commands
3890 cmd = "pP"; // available commands
3893 // We do not know how to handle this command, try again
3897 // randomly pick one of the available cmds from "cmd[]"
3898 i = (int) lrand48() % strlen(cmd);
3900 if (strchr(":\024", cm))
3901 goto cd0; // dont allow colon or ctrl-T commands
3902 readbuffer[rbi++] = cm; // put cmd into input buffer
3904 // now we have the command-
3905 // there are 1, 2, and multi char commands
3906 // find out which and generate the rest of command as necessary
3907 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3908 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3909 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3910 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3912 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3914 readbuffer[rbi++] = c; // add movement to input buffer
3916 if (strchr("iIaAsc", cm)) { // multi-char commands
3918 // change some thing
3919 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3921 readbuffer[rbi++] = c; // add movement to input buffer
3923 thing = (int) lrand48() % 4; // what thing to insert
3924 cnt = (int) lrand48() % 10; // how many to insert
3925 for (i = 0; i < cnt; i++) {
3926 if (thing == 0) { // insert chars
3927 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3928 } else if (thing == 1) { // insert words
3929 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3930 strcat((char *) readbuffer, " ");
3931 sleeptime = 0; // how fast to type
3932 } else if (thing == 2) { // insert lines
3933 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3934 sleeptime = 0; // how fast to type
3935 } else { // insert multi-lines
3936 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3937 sleeptime = 0; // how fast to type
3940 strcat((char *) readbuffer, "\033");
3942 readed_for_parse = strlen(readbuffer);
3946 (void) mysleep(sleeptime); // sleep 1/100 sec
3949 // test to see if there are any errors
3950 static void crash_test()
3952 static time_t oldtim;
3954 char d[2], msg[BUFSIZ];
3958 strcat((char *) msg, "end<text ");
3960 if (end > textend) {
3961 strcat((char *) msg, "end>textend ");
3964 strcat((char *) msg, "dot<text ");
3967 strcat((char *) msg, "dot>end ");
3969 if (screenbegin < text) {
3970 strcat((char *) msg, "screenbegin<text ");
3972 if (screenbegin > end - 1) {
3973 strcat((char *) msg, "screenbegin>end-1 ");
3976 if (strlen(msg) > 0) {
3978 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3979 totalcmds, last_input_char, msg, SOs, SOn);
3981 while (read(0, d, 1) > 0) {
3982 if (d[0] == '\n' || d[0] == '\r')
3987 tim = (time_t) time((time_t *) 0);
3988 if (tim >= (oldtim + 3)) {
3989 sprintf((char *) status_buffer,
3990 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3991 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3996 #endif /* CONFIG_FEATURE_VI_CRASHME */