1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
27 #ifdef CONFIG_LOCALE_SUPPORT
28 #define Isprint(c) isprint((c))
30 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
33 #define MAX_SCR_COLS BUFSIZ
35 // Misc. non-Ascii keys that report an escape sequence
36 #define VI_K_UP 128 // cursor key Up
37 #define VI_K_DOWN 129 // cursor key Down
38 #define VI_K_RIGHT 130 // Cursor Key Right
39 #define VI_K_LEFT 131 // cursor key Left
40 #define VI_K_HOME 132 // Cursor Key Home
41 #define VI_K_END 133 // Cursor Key End
42 #define VI_K_INSERT 134 // Cursor Key Insert
43 #define VI_K_PAGEUP 135 // Cursor Key Page Up
44 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
45 #define VI_K_FUN1 137 // Function Key F1
46 #define VI_K_FUN2 138 // Function Key F2
47 #define VI_K_FUN3 139 // Function Key F3
48 #define VI_K_FUN4 140 // Function Key F4
49 #define VI_K_FUN5 141 // Function Key F5
50 #define VI_K_FUN6 142 // Function Key F6
51 #define VI_K_FUN7 143 // Function Key F7
52 #define VI_K_FUN8 144 // Function Key F8
53 #define VI_K_FUN9 145 // Function Key F9
54 #define VI_K_FUN10 146 // Function Key F10
55 #define VI_K_FUN11 147 // Function Key F11
56 #define VI_K_FUN12 148 // Function Key F12
58 /* vt102 typical ESC sequence */
59 /* terminal standout start/normal ESC sequence */
60 static const char SOs[] = "\033[7m";
61 static const char SOn[] = "\033[0m";
62 /* terminal bell sequence */
63 static const char bell[] = "\007";
64 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
65 static const char Ceol[] = "\033[0K";
66 static const char Ceos [] = "\033[0J";
67 /* Cursor motion arbitrary destination ESC sequence */
68 static const char CMrc[] = "\033[%d;%dH";
69 /* Cursor motion up and down ESC sequence */
70 static const char CMup[] = "\033[A";
71 static const char CMdown[] = "\n";
77 FORWARD = 1, // code depends on "1" for array index
78 BACK = -1, // code depends on "-1" for array index
79 LIMITED = 0, // how much of text[] in char_search
80 FULL = 1, // how much of text[] in char_search
82 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
83 S_TO_WS = 2, // used in skip_thing() for moving "dot"
84 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
85 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
86 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
89 typedef unsigned char Byte;
92 #define VI_AUTOINDENT 1
93 #define VI_SHOWMATCH 2
94 #define VI_IGNORECASE 4
95 #define VI_ERR_METHOD 8
96 #define autoindent (vi_setops & VI_AUTOINDENT)
97 #define showmatch (vi_setops & VI_SHOWMATCH )
98 #define ignorecase (vi_setops & VI_IGNORECASE)
99 /* indicate error with beep or flash */
100 #define err_method (vi_setops & VI_ERR_METHOD)
103 static int editing; // >0 while we are editing a file
104 static int cmd_mode; // 0=command 1=insert 2=replace
105 static int file_modified; // buffer contents changed
106 static int last_file_modified = -1;
107 static int fn_start; // index of first cmd line file name
108 static int save_argc; // how many file names on cmd line
109 static int cmdcnt; // repetition count
110 static fd_set rfds; // use select() for small sleeps
111 static struct timeval tv; // use select() for small sleeps
112 static int rows, columns; // the terminal screen is this size
113 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
114 static Byte *status_buffer; // mesages to the user
115 #define STATUS_BUFFER_LEN 200
116 static int have_status_msg; // is default edit status needed?
117 static int last_status_cksum; // hash of current status line
118 static Byte *cfn; // previous, current, and next file name
119 static Byte *text, *end; // pointers to the user data in memory
120 static Byte *screen; // pointer to the virtual screen buffer
121 static int screensize; // and its size
122 static Byte *screenbegin; // index into text[], of top line on the screen
123 static Byte *dot; // where all the action takes place
125 static struct termios term_orig, term_vi; // remember what the cooked mode was
126 static Byte erase_char; // the users erase character
127 static Byte last_input_char; // last char read from user
128 static Byte last_forward_char; // last char searched for with 'f'
130 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
131 static int last_row; // where the cursor was last moved to
132 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
133 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
134 static jmp_buf restart; // catch_sig()
135 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
136 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
139 #ifdef CONFIG_FEATURE_VI_DOT_CMD
140 static int adding2q; // are we currently adding user input to q
141 static Byte *last_modifying_cmd; // last modifying cmd for "."
142 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
143 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
144 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
145 static Byte *modifying_cmds; // cmds that modify text[]
146 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
147 #ifdef CONFIG_FEATURE_VI_READONLY
148 static int vi_readonly, readonly;
149 #endif /* CONFIG_FEATURE_VI_READONLY */
150 #ifdef CONFIG_FEATURE_VI_YANKMARK
151 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
152 static int YDreg, Ureg; // default delete register and orig line for "U"
153 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
154 static Byte *context_start, *context_end;
155 #endif /* CONFIG_FEATURE_VI_YANKMARK */
156 #ifdef CONFIG_FEATURE_VI_SEARCH
157 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
158 #endif /* CONFIG_FEATURE_VI_SEARCH */
161 static void edit_file(Byte *); // edit one file
162 static void do_cmd(Byte); // execute a command
163 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
164 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
165 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
166 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
167 static Byte *next_line(Byte *); // return pointer to next line B-o-l
168 static Byte *end_screen(void); // get pointer to last char on screen
169 static int count_lines(Byte *, Byte *); // count line from start to stop
170 static Byte *find_line(int); // find begining of line #li
171 static Byte *move_to_col(Byte *, int); // move "p" to column l
172 static int isblnk(Byte); // is the char a blank or tab
173 static void dot_left(void); // move dot left- dont leave line
174 static void dot_right(void); // move dot right- dont leave line
175 static void dot_begin(void); // move dot to B-o-l
176 static void dot_end(void); // move dot to E-o-l
177 static void dot_next(void); // move dot to next line B-o-l
178 static void dot_prev(void); // move dot to prev line B-o-l
179 static void dot_scroll(int, int); // move the screen up or down
180 static void dot_skip_over_ws(void); // move dot pat WS
181 static void dot_delete(void); // delete the char at 'dot'
182 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
183 static Byte *new_screen(int, int); // malloc virtual screen memory
184 static Byte *new_text(int); // malloc memory for text[] buffer
185 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
186 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
187 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
188 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
189 static Byte *skip_thing(Byte *, int, int, int); // skip some object
190 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
191 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
192 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
193 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
194 static void show_help(void); // display some help info
195 static void rawmode(void); // set "raw" mode on tty
196 static void cookmode(void); // return to "cooked" mode on tty
197 static int mysleep(int); // sleep for 'h' 1/100 seconds
198 static Byte readit(void); // read (maybe cursor) key from stdin
199 static Byte get_one_char(void); // read 1 char from stdin
200 static int file_size(const Byte *); // what is the byte size of "fn"
201 static int file_insert(Byte *, Byte *, int);
202 static int file_write(Byte *, Byte *, Byte *);
203 static void place_cursor(int, int, int);
204 static void screen_erase(void);
205 static void clear_to_eol(void);
206 static void clear_to_eos(void);
207 static void standout_start(void); // send "start reverse video" sequence
208 static void standout_end(void); // send "end reverse video" sequence
209 static void flash(int); // flash the terminal screen
210 static void show_status_line(void); // put a message on the bottom line
211 static void psb(const char *, ...); // Print Status Buf
212 static void psbs(const char *, ...); // Print Status Buf in standout mode
213 static void ni(Byte *); // display messages
214 static int format_edit_status(void); // format file status on status line
215 static void redraw(int); // force a full screen refresh
216 static void format_line(Byte*, Byte*, int);
217 static void refresh(int); // update the terminal from screen[]
219 static void Indicate_Error(void); // use flash or beep to indicate error
220 #define indicate_error(c) Indicate_Error()
221 static void Hit_Return(void);
223 #ifdef CONFIG_FEATURE_VI_SEARCH
224 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
225 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
226 #endif /* CONFIG_FEATURE_VI_SEARCH */
227 #ifdef CONFIG_FEATURE_VI_COLON
228 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
229 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
230 static void colon(Byte *); // execute the "colon" mode cmds
231 #endif /* CONFIG_FEATURE_VI_COLON */
232 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
233 static void winch_sig(int); // catch window size changes
234 static void suspend_sig(int); // catch ctrl-Z
235 static void catch_sig(int); // catch ctrl-C and alarm time-outs
236 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
237 #ifdef CONFIG_FEATURE_VI_DOT_CMD
238 static void start_new_cmd_q(Byte); // new queue for command
239 static void end_cmd_q(void); // stop saving input chars
240 #else /* CONFIG_FEATURE_VI_DOT_CMD */
242 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
243 #ifdef CONFIG_FEATURE_VI_SETOPTS
244 static void showmatching(Byte *); // show the matching pair () [] {}
245 #endif /* CONFIG_FEATURE_VI_SETOPTS */
246 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
247 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
248 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
249 #ifdef CONFIG_FEATURE_VI_YANKMARK
250 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
251 static Byte what_reg(void); // what is letter of current YDreg
252 static void check_context(Byte); // remember context for '' command
253 #endif /* CONFIG_FEATURE_VI_YANKMARK */
254 #ifdef CONFIG_FEATURE_VI_CRASHME
255 static void crash_dummy();
256 static void crash_test();
257 static int crashme = 0;
258 #endif /* CONFIG_FEATURE_VI_CRASHME */
261 static void write1(const char *out)
266 int vi_main(int argc, char **argv)
269 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
271 #ifdef CONFIG_FEATURE_VI_YANKMARK
273 #endif /* CONFIG_FEATURE_VI_YANKMARK */
274 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
277 #ifdef CONFIG_FEATURE_VI_CRASHME
278 (void) srand((long) my_pid);
279 #endif /* CONFIG_FEATURE_VI_CRASHME */
281 status_buffer = (Byte *)STATUS_BUFFER;
282 last_status_cksum = 0;
284 #ifdef CONFIG_FEATURE_VI_READONLY
285 vi_readonly = readonly = FALSE;
286 if (strncmp(argv[0], "view", 4) == 0) {
290 #endif /* CONFIG_FEATURE_VI_READONLY */
291 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
292 #ifdef CONFIG_FEATURE_VI_YANKMARK
293 for (i = 0; i < 28; i++) {
295 } // init the yank regs
296 #endif /* CONFIG_FEATURE_VI_YANKMARK */
297 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
298 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
299 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
301 // 1- process $HOME/.exrc file
302 // 2- process EXINIT variable from environment
303 // 3- process command line args
304 while ((c = getopt(argc, argv, "hCR")) != -1) {
306 #ifdef CONFIG_FEATURE_VI_CRASHME
310 #endif /* CONFIG_FEATURE_VI_CRASHME */
311 #ifdef CONFIG_FEATURE_VI_READONLY
312 case 'R': // Read-only flag
316 #endif /* CONFIG_FEATURE_VI_READONLY */
317 //case 'r': // recover flag- ignore- we don't use tmp file
318 //case 'x': // encryption flag- ignore
319 //case 'c': // execute command first
320 //case 'h': // help -- just use default
327 // The argv array can be used by the ":next" and ":rewind" commands
329 fn_start = optind; // remember first file name for :next and :rew
332 //----- This is the main file handling loop --------------
333 if (optind >= argc) {
334 editing = 1; // 0= exit, 1= one file, 2= multiple files
337 for (; optind < argc; optind++) {
338 editing = 1; // 0=exit, 1=one file, 2+ =many files
340 cfn = (Byte *) xstrdup(argv[optind]);
344 //-----------------------------------------------------------
349 static void edit_file(Byte * fn)
354 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
356 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
357 #ifdef CONFIG_FEATURE_VI_YANKMARK
358 static Byte *cur_line;
359 #endif /* CONFIG_FEATURE_VI_YANKMARK */
365 if (ENABLE_FEATURE_VI_WIN_RESIZE)
366 get_terminal_width_height(0, &columns, &rows);
367 new_screen(rows, columns); // get memory for virtual screen
369 cnt = file_size(fn); // file size
370 size = 2 * cnt; // 200% of file size
371 new_text(size); // get a text[] buffer
372 screenbegin = dot = end = text;
374 ch= file_insert(fn, text, cnt);
377 (void) char_insert(text, '\n'); // start empty buf with dummy line
380 last_file_modified = -1;
381 #ifdef CONFIG_FEATURE_VI_YANKMARK
382 YDreg = 26; // default Yank/Delete reg
383 Ureg = 27; // hold orig line for "U" cmd
384 for (cnt = 0; cnt < 28; cnt++) {
387 mark[26] = mark[27] = text; // init "previous context"
388 #endif /* CONFIG_FEATURE_VI_YANKMARK */
390 last_forward_char = last_input_char = '\0';
394 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
396 signal(SIGWINCH, winch_sig);
397 signal(SIGTSTP, suspend_sig);
398 sig = setjmp(restart);
400 screenbegin = dot = text;
402 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
405 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
408 offset = 0; // no horizontal offset
410 #ifdef CONFIG_FEATURE_VI_DOT_CMD
411 free(last_modifying_cmd);
413 ioq = ioq_start = last_modifying_cmd = 0;
415 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
416 redraw(FALSE); // dont force every col re-draw
419 //------This is the main Vi cmd handling loop -----------------------
420 while (editing > 0) {
421 #ifdef CONFIG_FEATURE_VI_CRASHME
423 if ((end - text) > 1) {
424 crash_dummy(); // generate a random command
428 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
432 #endif /* CONFIG_FEATURE_VI_CRASHME */
433 last_input_char = c = get_one_char(); // get a cmd from user
434 #ifdef CONFIG_FEATURE_VI_YANKMARK
435 // save a copy of the current line- for the 'U" command
436 if (begin_line(dot) != cur_line) {
437 cur_line = begin_line(dot);
438 text_yank(begin_line(dot), end_line(dot), Ureg);
440 #endif /* CONFIG_FEATURE_VI_YANKMARK */
441 #ifdef CONFIG_FEATURE_VI_DOT_CMD
442 // These are commands that change text[].
443 // Remember the input for the "." command
444 if (!adding2q && ioq_start == 0
445 && strchr((char *) modifying_cmds, c) != NULL) {
448 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
449 do_cmd(c); // execute the user command
451 // poll to see if there is input already waiting. if we are
452 // not able to display output fast enough to keep up, skip
453 // the display update until we catch up with input.
454 if (mysleep(0) == 0) {
455 // no input pending- so update output
459 #ifdef CONFIG_FEATURE_VI_CRASHME
461 crash_test(); // test editor variables
462 #endif /* CONFIG_FEATURE_VI_CRASHME */
464 //-------------------------------------------------------------------
466 place_cursor(rows, 0, FALSE); // go to bottom of screen
467 clear_to_eol(); // Erase to end of line
471 //----- The Colon commands -------------------------------------
472 #ifdef CONFIG_FEATURE_VI_COLON
473 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
478 #ifdef CONFIG_FEATURE_VI_YANKMARK
480 #endif /* CONFIG_FEATURE_VI_YANKMARK */
481 #ifdef CONFIG_FEATURE_VI_SEARCH
482 Byte *pat, buf[BUFSIZ];
483 #endif /* CONFIG_FEATURE_VI_SEARCH */
485 *addr = -1; // assume no addr
486 if (*p == '.') { // the current line
489 *addr = count_lines(text, q);
490 #ifdef CONFIG_FEATURE_VI_YANKMARK
491 } else if (*p == '\'') { // is this a mark addr
495 if (c >= 'a' && c <= 'z') {
499 if (q != NULL) { // is mark valid
500 *addr = count_lines(text, q); // count lines
503 #endif /* CONFIG_FEATURE_VI_YANKMARK */
504 #ifdef CONFIG_FEATURE_VI_SEARCH
505 } else if (*p == '/') { // a search pattern
513 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
516 q = char_search(dot, pat, FORWARD, FULL);
518 *addr = count_lines(text, q);
521 #endif /* CONFIG_FEATURE_VI_SEARCH */
522 } else if (*p == '$') { // the last line in file
524 q = begin_line(end - 1);
525 *addr = count_lines(text, q);
526 } else if (isdigit(*p)) { // specific line number
527 sscanf((char *) p, "%d%n", addr, &st);
529 } else { // I don't reconise this
530 // unrecognised address- assume -1
536 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
538 //----- get the address' i.e., 1,3 'a,'b -----
539 // get FIRST addr, if present
541 p++; // skip over leading spaces
542 if (*p == '%') { // alias for 1,$
545 *e = count_lines(text, end-1);
548 p = get_one_address(p, b);
551 if (*p == ',') { // is there a address separator
555 // get SECOND addr, if present
556 p = get_one_address(p, e);
560 p++; // skip over trailing spaces
564 #ifdef CONFIG_FEATURE_VI_SETOPTS
565 static void setops(const Byte *args, const char *opname, int flg_no,
566 const char *short_opname, int opt)
568 const char *a = (char *) args + flg_no;
569 int l = strlen(opname) - 1; /* opname have + ' ' */
571 if (strncasecmp(a, opname, l) == 0 ||
572 strncasecmp(a, short_opname, 2) == 0) {
581 static void colon(Byte * buf)
583 Byte c, *orig_buf, *buf1, *q, *r;
584 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
585 int i, l, li, ch, b, e;
586 int useforce = FALSE, forced = FALSE;
589 // :3154 // if (-e line 3154) goto it else stay put
590 // :4,33w! foo // write a portion of buffer to file "foo"
591 // :w // write all of buffer to current file
593 // :q! // quit- dont care about modified file
594 // :'a,'z!sort -u // filter block through sort
595 // :'f // goto mark "f"
596 // :'fl // list literal the mark "f" line
597 // :.r bar // read file "bar" into buffer before dot
598 // :/123/,/abc/d // delete lines from "123" line to "abc" line
599 // :/xyz/ // goto the "xyz" line
600 // :s/find/replace/ // substitute pattern "find" with "replace"
601 // :!<cmd> // run <cmd> then return
604 if (strlen((char *) buf) <= 0)
607 buf++; // move past the ':'
611 q = text; // assume 1,$ for the range
613 li = count_lines(text, end - 1);
614 fn = cfn; // default to current file
615 memset(cmd, '\0', BUFSIZ); // clear cmd[]
616 memset(args, '\0', BUFSIZ); // clear args[]
618 // look for optional address(es) :. :1 :1,9 :'q,'a :%
619 buf = get_address(buf, &b, &e);
621 // remember orig command line
624 // get the COMMAND into cmd[]
626 while (*buf != '\0') {
634 strcpy((char *) args, (char *) buf);
635 buf1 = (Byte*)last_char_is((char *)cmd, '!');
638 *buf1 = '\0'; // get rid of !
641 // if there is only one addr, then the addr
642 // is the line number of the single line the
643 // user wants. So, reset the end
644 // pointer to point at end of the "b" line
645 q = find_line(b); // what line is #b
650 // we were given two addrs. change the
651 // end pointer to the addr given by user.
652 r = find_line(e); // what line is #e
656 // ------------ now look for the command ------------
657 i = strlen((char *) cmd);
658 if (i == 0) { // :123CR goto line #123
660 dot = find_line(b); // what line is #b
664 #if ENABLE_FEATURE_ALLOW_EXEC
665 else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
666 // :!ls run the <cmd>
667 (void) alarm(0); // wait for input- no alarms
668 place_cursor(rows - 1, 0, FALSE); // go to Status line
669 clear_to_eol(); // clear the line
671 system((char*)(orig_buf+1)); // run the cmd
673 Hit_Return(); // let user see results
674 (void) alarm(3); // done waiting for input
677 else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
678 if (b < 0) { // no addr given- use defaults
679 b = e = count_lines(text, dot);
682 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
683 if (b < 0) { // no addr given- use defaults
684 q = begin_line(dot); // assume .,. for the range
687 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
689 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
692 // don't edit, if the current file has been modified
693 if (file_modified && ! useforce) {
694 psbs("No write since last change (:edit! overrides)");
697 if (strlen((char*)args) > 0) {
698 // the user supplied a file name
700 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
701 // no user supplied name- use the current filename
705 // no user file name, no current name- punt
706 psbs("No current filename");
710 // see if file exists- if not, its just a new file request
711 if ((sr=stat((char*)fn, &st_buf)) < 0) {
712 // This is just a request for a new file creation.
713 // The file_insert below will fail but we get
714 // an empty buffer with a file name. Then the "write"
715 // command can do the create.
717 if ((st_buf.st_mode & (S_IFREG)) == 0) {
718 // This is not a regular file
719 psbs("\"%s\" is not a regular file", fn);
722 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
723 // dont have any read permissions
724 psbs("\"%s\" is not readable", fn);
729 // There is a read-able regular file
730 // make this the current file
731 q = (Byte *) xstrdup((char *) fn); // save the cfn
732 free(cfn); // free the old name
733 cfn = q; // remember new cfn
736 // delete all the contents of text[]
737 new_text(2 * file_size(fn));
738 screenbegin = dot = end = text;
741 ch = file_insert(fn, text, file_size(fn));
744 // start empty buf with dummy line
745 (void) char_insert(text, '\n');
749 last_file_modified = -1;
750 #ifdef CONFIG_FEATURE_VI_YANKMARK
751 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
752 free(reg[Ureg]); // free orig line reg- for 'U'
755 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
756 free(reg[YDreg]); // free default yank/delete register
759 for (li = 0; li < 28; li++) {
762 #endif /* CONFIG_FEATURE_VI_YANKMARK */
763 // how many lines in text[]?
764 li = count_lines(text, end - 1);
766 #ifdef CONFIG_FEATURE_VI_READONLY
768 #endif /* CONFIG_FEATURE_VI_READONLY */
770 (sr < 0 ? " [New file]" : ""),
771 #ifdef CONFIG_FEATURE_VI_READONLY
772 ((vi_readonly || readonly) ? " [Read only]" : ""),
773 #endif /* CONFIG_FEATURE_VI_READONLY */
775 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
776 if (b != -1 || e != -1) {
777 ni((Byte *) "No address allowed on this command");
780 if (strlen((char *) args) > 0) {
781 // user wants a new filename
783 cfn = (Byte *) xstrdup((char *) args);
785 // user wants file status info
786 last_status_cksum = 0; // force status update
788 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
789 // print out values of all features
790 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
791 clear_to_eol(); // clear the line
796 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
797 if (b < 0) { // no addr given- use defaults
798 q = begin_line(dot); // assume .,. for the range
801 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
802 clear_to_eol(); // clear the line
804 for (; q <= r; q++) {
808 c_is_no_print = c > 127 && !Isprint(c);
815 } else if (c < ' ' || c == 127) {
826 #ifdef CONFIG_FEATURE_VI_SET
828 #endif /* CONFIG_FEATURE_VI_SET */
830 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
831 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
833 // force end of argv list
840 // don't exit if the file been modified
842 psbs("No write since last change (:%s! overrides)",
843 (*cmd == 'q' ? "quit" : "next"));
846 // are there other file to edit
847 if (*cmd == 'q' && optind < save_argc - 1) {
848 psbs("%d more file to edit", (save_argc - optind - 1));
851 if (*cmd == 'n' && optind >= save_argc - 1) {
852 psbs("No more files to edit");
856 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
858 if (strlen((char *) fn) <= 0) {
859 psbs("No filename given");
862 if (b < 0) { // no addr given- use defaults
863 q = begin_line(dot); // assume "dot"
865 // read after current line- unless user said ":0r foo"
868 #ifdef CONFIG_FEATURE_VI_READONLY
869 l= readonly; // remember current files' status
871 ch = file_insert(fn, q, file_size(fn));
872 #ifdef CONFIG_FEATURE_VI_READONLY
876 goto vc1; // nothing was inserted
877 // how many lines in text[]?
878 li = count_lines(q, q + ch - 1);
880 #ifdef CONFIG_FEATURE_VI_READONLY
882 #endif /* CONFIG_FEATURE_VI_READONLY */
884 #ifdef CONFIG_FEATURE_VI_READONLY
885 ((vi_readonly || readonly) ? " [Read only]" : ""),
886 #endif /* CONFIG_FEATURE_VI_READONLY */
889 // if the insert is before "dot" then we need to update
894 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
895 if (file_modified && ! useforce) {
896 psbs("No write since last change (:rewind! overrides)");
898 // reset the filenames to edit
899 optind = fn_start - 1;
902 #ifdef CONFIG_FEATURE_VI_SET
903 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
904 i = 0; // offset into args
905 if (strlen((char *) args) == 0) {
906 // print out values of all options
907 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
908 clear_to_eol(); // clear the line
909 printf("----------------------------------------\r\n");
910 #ifdef CONFIG_FEATURE_VI_SETOPTS
913 printf("autoindent ");
919 printf("ignorecase ");
922 printf("showmatch ");
923 printf("tabstop=%d ", tabstop);
924 #endif /* CONFIG_FEATURE_VI_SETOPTS */
928 if (strncasecmp((char *) args, "no", 2) == 0)
929 i = 2; // ":set noautoindent"
930 #ifdef CONFIG_FEATURE_VI_SETOPTS
931 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
932 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
933 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
934 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
935 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
936 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
937 if (ch > 0 && ch < columns - 1)
940 #endif /* CONFIG_FEATURE_VI_SETOPTS */
941 #endif /* CONFIG_FEATURE_VI_SET */
942 #ifdef CONFIG_FEATURE_VI_SEARCH
943 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
947 // F points to the "find" pattern
948 // R points to the "replace" pattern
949 // replace the cmd line delimiters "/" with NULLs
950 gflag = 0; // global replace flag
951 c = orig_buf[1]; // what is the delimiter
952 F = orig_buf + 2; // start of "find"
953 R = (Byte *) strchr((char *) F, c); // middle delimiter
954 if (!R) goto colon_s_fail;
955 *R++ = '\0'; // terminate "find"
956 buf1 = (Byte *) strchr((char *) R, c);
957 if (!buf1) goto colon_s_fail;
958 *buf1++ = '\0'; // terminate "replace"
959 if (*buf1 == 'g') { // :s/foo/bar/g
961 gflag++; // turn on gflag
964 if (b < 0) { // maybe :s/foo/bar/
965 q = begin_line(dot); // start with cur line
966 b = count_lines(text, q); // cur line number
969 e = b; // maybe :.s/foo/bar/
970 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
971 ls = q; // orig line start
973 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
975 // we found the "find" pattern- delete it
976 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
977 // inset the "replace" patern
978 (void) string_insert(buf1, R); // insert the string
979 // check for "global" :s/foo/bar/g
981 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
982 q = buf1 + strlen((char *) R);
983 goto vc4; // don't let q move past cur line
989 #endif /* CONFIG_FEATURE_VI_SEARCH */
990 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
991 psb("%s", BB_VER " " BB_BT);
992 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
993 || strncasecmp((char *) cmd, "wq", i) == 0
994 || strncasecmp((char *) cmd, "wn", i) == 0
995 || strncasecmp((char *) cmd, "x", i) == 0) {
996 // is there a file name to write to?
997 if (strlen((char *) args) > 0) {
1000 #ifdef CONFIG_FEATURE_VI_READONLY
1001 if ((vi_readonly || readonly) && ! useforce) {
1002 psbs("\"%s\" File is read only", fn);
1005 #endif /* CONFIG_FEATURE_VI_READONLY */
1006 // how many lines in text[]?
1007 li = count_lines(q, r);
1009 // see if file exists- if not, its just a new file request
1011 // if "fn" is not write-able, chmod u+w
1012 // sprintf(syscmd, "chmod u+w %s", fn);
1016 l = file_write(fn, q, r);
1017 if (useforce && forced) {
1019 // sprintf(syscmd, "chmod u-w %s", fn);
1025 psbs("Write error: %s", strerror(errno));
1027 psb("\"%s\" %dL, %dC", fn, li, l);
1028 if (q == text && r == end - 1 && l == ch) {
1030 last_file_modified = -1;
1032 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1033 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1038 #ifdef CONFIG_FEATURE_VI_READONLY
1040 #endif /* CONFIG_FEATURE_VI_READONLY */
1041 #ifdef CONFIG_FEATURE_VI_YANKMARK
1042 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1043 if (b < 0) { // no addr given- use defaults
1044 q = begin_line(dot); // assume .,. for the range
1047 text_yank(q, r, YDreg);
1048 li = count_lines(q, r);
1049 psb("Yank %d lines (%d chars) into [%c]",
1050 li, strlen((char *) reg[YDreg]), what_reg());
1051 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1057 dot = bound_dot(dot); // make sure "dot" is valid
1059 #ifdef CONFIG_FEATURE_VI_SEARCH
1061 psb(":s expression missing delimiters");
1065 #endif /* CONFIG_FEATURE_VI_COLON */
1067 static void Hit_Return(void)
1071 standout_start(); // start reverse video
1072 write1("[Hit return to continue]");
1073 standout_end(); // end reverse video
1074 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1076 redraw(TRUE); // force redraw all
1079 //----- Synchronize the cursor to Dot --------------------------
1080 static void sync_cursor(Byte * d, int *row, int *col)
1082 Byte *beg_cur; // begin and end of "d" line
1083 Byte *end_scr; // begin and end of screen
1087 beg_cur = begin_line(d); // first char of cur line
1089 end_scr = end_screen(); // last char of screen
1091 if (beg_cur < screenbegin) {
1092 // "d" is before top line on screen
1093 // how many lines do we have to move
1094 cnt = count_lines(beg_cur, screenbegin);
1096 screenbegin = beg_cur;
1097 if (cnt > (rows - 1) / 2) {
1098 // we moved too many lines. put "dot" in middle of screen
1099 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1100 screenbegin = prev_line(screenbegin);
1103 } else if (beg_cur > end_scr) {
1104 // "d" is after bottom line on screen
1105 // how many lines do we have to move
1106 cnt = count_lines(end_scr, beg_cur);
1107 if (cnt > (rows - 1) / 2)
1108 goto sc1; // too many lines
1109 for (ro = 0; ro < cnt - 1; ro++) {
1110 // move screen begin the same amount
1111 screenbegin = next_line(screenbegin);
1112 // now, move the end of screen
1113 end_scr = next_line(end_scr);
1114 end_scr = end_line(end_scr);
1117 // "d" is on screen- find out which row
1119 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1125 // find out what col "d" is on
1127 do { // drive "co" to correct column
1128 if (*tp == '\n' || *tp == '\0')
1132 co += ((tabstop - 1) - (co % tabstop));
1133 } else if (*tp < ' ' || *tp == 127) {
1134 co++; // display as ^X, use 2 columns
1136 } while (tp++ < d && ++co);
1138 // "co" is the column where "dot" is.
1139 // The screen has "columns" columns.
1140 // The currently displayed columns are 0+offset -- columns+ofset
1141 // |-------------------------------------------------------------|
1143 // offset | |------- columns ----------------|
1145 // If "co" is already in this range then we do not have to adjust offset
1146 // but, we do have to subtract the "offset" bias from "co".
1147 // If "co" is outside this range then we have to change "offset".
1148 // If the first char of a line is a tab the cursor will try to stay
1149 // in column 7, but we have to set offset to 0.
1151 if (co < 0 + offset) {
1154 if (co >= columns + offset) {
1155 offset = co - columns + 1;
1157 // if the first char of the line is a tab, and "dot" is sitting on it
1158 // force offset to 0.
1159 if (d == beg_cur && *d == '\t') {
1168 //----- Text Movement Routines ---------------------------------
1169 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1171 while (p > text && p[-1] != '\n')
1172 p--; // go to cur line B-o-l
1176 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1178 while (p < end - 1 && *p != '\n')
1179 p++; // go to cur line E-o-l
1183 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1185 while (p < end - 1 && *p != '\n')
1186 p++; // go to cur line E-o-l
1187 // Try to stay off of the Newline
1188 if (*p == '\n' && (p - begin_line(p)) > 0)
1193 static Byte *prev_line(Byte * p) // return pointer first char prev line
1195 p = begin_line(p); // goto begining of cur line
1196 if (p[-1] == '\n' && p > text)
1197 p--; // step to prev line
1198 p = begin_line(p); // goto begining of prev line
1202 static Byte *next_line(Byte * p) // return pointer first char next line
1205 if (*p == '\n' && p < end - 1)
1206 p++; // step to next line
1210 //----- Text Information Routines ------------------------------
1211 static Byte *end_screen(void)
1216 // find new bottom line
1218 for (cnt = 0; cnt < rows - 2; cnt++)
1224 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1229 if (stop < start) { // start and stop are backwards- reverse them
1235 stop = end_line(stop); // get to end of this line
1236 for (q = start; q <= stop && q <= end - 1; q++) {
1243 static Byte *find_line(int li) // find begining of line #li
1247 for (q = text; li > 1; li--) {
1253 //----- Dot Movement Routines ----------------------------------
1254 static void dot_left(void)
1256 if (dot > text && dot[-1] != '\n')
1260 static void dot_right(void)
1262 if (dot < end - 1 && *dot != '\n')
1266 static void dot_begin(void)
1268 dot = begin_line(dot); // return pointer to first char cur line
1271 static void dot_end(void)
1273 dot = end_line(dot); // return pointer to last char cur line
1276 static Byte *move_to_col(Byte * p, int l)
1283 if (*p == '\n' || *p == '\0')
1287 co += ((tabstop - 1) - (co % tabstop));
1288 } else if (*p < ' ' || *p == 127) {
1289 co++; // display as ^X, use 2 columns
1291 } while (++co <= l && p++ < end);
1295 static void dot_next(void)
1297 dot = next_line(dot);
1300 static void dot_prev(void)
1302 dot = prev_line(dot);
1305 static void dot_scroll(int cnt, int dir)
1309 for (; cnt > 0; cnt--) {
1312 // ctrl-Y scroll up one line
1313 screenbegin = prev_line(screenbegin);
1316 // ctrl-E scroll down one line
1317 screenbegin = next_line(screenbegin);
1320 // make sure "dot" stays on the screen so we dont scroll off
1321 if (dot < screenbegin)
1323 q = end_screen(); // find new bottom line
1325 dot = begin_line(q); // is dot is below bottom line?
1329 static void dot_skip_over_ws(void)
1332 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1336 static void dot_delete(void) // delete the char at 'dot'
1338 (void) text_hole_delete(dot, dot);
1341 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1343 if (p >= end && end > text) {
1345 indicate_error('1');
1349 indicate_error('2');
1354 //----- Helper Utility Routines --------------------------------
1356 //----------------------------------------------------------------
1357 //----- Char Routines --------------------------------------------
1358 /* Chars that are part of a word-
1359 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1360 * Chars that are Not part of a word (stoppers)
1361 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1362 * Chars that are WhiteSpace
1363 * TAB NEWLINE VT FF RETURN SPACE
1364 * DO NOT COUNT NEWLINE AS WHITESPACE
1367 static Byte *new_screen(int ro, int co)
1372 screensize = ro * co + 8;
1373 screen = xmalloc(screensize);
1374 // initialize the new screen. assume this will be a empty file.
1376 // non-existent text[] lines start with a tilde (~).
1377 for (li = 1; li < ro - 1; li++) {
1378 screen[(li * co) + 0] = '~';
1383 static Byte *new_text(int size)
1386 size = 10240; // have a minimum size for new files
1388 text = xmalloc(size + 8);
1389 memset(text, '\0', size); // clear new text[]
1390 //text += 4; // leave some room for "oops"
1394 #ifdef CONFIG_FEATURE_VI_SEARCH
1395 static int mycmp(Byte * s1, Byte * s2, int len)
1399 i = strncmp((char *) s1, (char *) s2, len);
1400 #ifdef CONFIG_FEATURE_VI_SETOPTS
1402 i = strncasecmp((char *) s1, (char *) s2, len);
1404 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1408 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1410 #ifndef REGEX_SEARCH
1414 len = strlen((char *) pat);
1415 if (dir == FORWARD) {
1416 stop = end - 1; // assume range is p - end-1
1417 if (range == LIMITED)
1418 stop = next_line(p); // range is to next line
1419 for (start = p; start < stop; start++) {
1420 if (mycmp(start, pat, len) == 0) {
1424 } else if (dir == BACK) {
1425 stop = text; // assume range is text - p
1426 if (range == LIMITED)
1427 stop = prev_line(p); // range is to prev line
1428 for (start = p - len; start >= stop; start--) {
1429 if (mycmp(start, pat, len) == 0) {
1434 // pattern not found
1436 #else /*REGEX_SEARCH */
1438 struct re_pattern_buffer preg;
1442 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1448 // assume a LIMITED forward search
1456 // count the number of chars to search over, forward or backward
1460 // RANGE could be negative if we are searching backwards
1463 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1465 // The pattern was not compiled
1466 psbs("bad search pattern: \"%s\": %s", pat, q);
1467 i = 0; // return p if pattern not compiled
1477 // search for the compiled pattern, preg, in p[]
1478 // range < 0- search backward
1479 // range > 0- search forward
1481 // re_search() < 0 not found or error
1482 // re_search() > 0 index of found pattern
1483 // struct pattern char int int int struct reg
1484 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1485 i = re_search(&preg, q, size, 0, range, 0);
1488 i = 0; // return NULL if pattern not found
1491 if (dir == FORWARD) {
1497 #endif /*REGEX_SEARCH */
1499 #endif /* CONFIG_FEATURE_VI_SEARCH */
1501 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1503 if (c == 22) { // Is this an ctrl-V?
1504 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1505 p--; // backup onto ^
1506 refresh(FALSE); // show the ^
1510 file_modified++; // has the file been modified
1511 } else if (c == 27) { // Is this an ESC?
1514 end_cmd_q(); // stop adding to q
1515 last_status_cksum = 0; // force status update
1516 if ((p[-1] != '\n') && (dot>text)) {
1519 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1521 if ((p[-1] != '\n') && (dot>text)) {
1523 p = text_hole_delete(p, p); // shrink buffer 1 char
1526 // insert a char into text[]
1527 Byte *sp; // "save p"
1530 c = '\n'; // translate \r to \n
1531 sp = p; // remember addr of insert
1532 p = stupid_insert(p, c); // insert the char
1533 #ifdef CONFIG_FEATURE_VI_SETOPTS
1534 if (showmatch && strchr(")]}", *sp) != NULL) {
1537 if (autoindent && c == '\n') { // auto indent the new line
1540 q = prev_line(p); // use prev line as templet
1541 for (; isblnk(*q); q++) {
1542 p = stupid_insert(p, *q); // insert the char
1545 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1550 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1552 p = text_hole_make(p, 1);
1555 file_modified++; // has the file been modified
1561 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1563 Byte *save_dot, *p, *q;
1569 if (strchr("cdy><", c)) {
1570 // these cmds operate on whole lines
1571 p = q = begin_line(p);
1572 for (cnt = 1; cnt < cmdcnt; cnt++) {
1576 } else if (strchr("^%$0bBeEft", c)) {
1577 // These cmds operate on char positions
1578 do_cmd(c); // execute movement cmd
1580 } else if (strchr("wW", c)) {
1581 do_cmd(c); // execute movement cmd
1582 // if we are at the next word's first char
1583 // step back one char
1584 // but check the possibilities when it is true
1585 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1586 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1587 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1588 dot--; // move back off of next word
1589 if (dot > text && *dot == '\n')
1590 dot--; // stay off NL
1592 } else if (strchr("H-k{", c)) {
1593 // these operate on multi-lines backwards
1594 q = end_line(dot); // find NL
1595 do_cmd(c); // execute movement cmd
1598 } else if (strchr("L+j}\r\n", c)) {
1599 // these operate on multi-lines forwards
1600 p = begin_line(dot);
1601 do_cmd(c); // execute movement cmd
1602 dot_end(); // find NL
1605 c = 27; // error- return an ESC char
1618 static int st_test(Byte * p, int type, int dir, Byte * tested)
1628 if (type == S_BEFORE_WS) {
1630 test = ((!isspace(c)) || c == '\n');
1632 if (type == S_TO_WS) {
1634 test = ((!isspace(c)) || c == '\n');
1636 if (type == S_OVER_WS) {
1638 test = ((isspace(c)));
1640 if (type == S_END_PUNCT) {
1642 test = ((ispunct(c)));
1644 if (type == S_END_ALNUM) {
1646 test = ((isalnum(c)) || c == '_');
1652 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1656 while (st_test(p, type, dir, &c)) {
1657 // make sure we limit search to correct number of lines
1658 if (c == '\n' && --linecnt < 1)
1660 if (dir >= 0 && p >= end - 1)
1662 if (dir < 0 && p <= text)
1664 p += dir; // move to next char
1669 // find matching char of pair () [] {}
1670 static Byte *find_pair(Byte * p, Byte c)
1677 dir = 1; // assume forward
1701 for (q = p + dir; text <= q && q < end; q += dir) {
1702 // look for match, count levels of pairs (( ))
1704 level++; // increase pair levels
1706 level--; // reduce pair level
1708 break; // found matching pair
1711 q = NULL; // indicate no match
1715 #ifdef CONFIG_FEATURE_VI_SETOPTS
1716 // show the matching char of a pair, () [] {}
1717 static void showmatching(Byte * p)
1721 // we found half of a pair
1722 q = find_pair(p, *p); // get loc of matching char
1724 indicate_error('3'); // no matching char
1726 // "q" now points to matching pair
1727 save_dot = dot; // remember where we are
1728 dot = q; // go to new loc
1729 refresh(FALSE); // let the user see it
1730 (void) mysleep(40); // give user some time
1731 dot = save_dot; // go back to old loc
1735 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1737 // open a hole in text[]
1738 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1747 cnt = end - src; // the rest of buffer
1748 if (memmove(dest, src, cnt) != dest) {
1749 psbs("can't create room for new characters");
1751 memset(p, ' ', size); // clear new hole
1752 end = end + size; // adjust the new END
1753 file_modified++; // has the file been modified
1758 // close a hole in text[]
1759 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1764 // move forwards, from beginning
1768 if (q < p) { // they are backward- swap them
1772 hole_size = q - p + 1;
1774 if (src < text || src > end)
1776 if (dest < text || dest >= end)
1779 goto thd_atend; // just delete the end of the buffer
1780 if (memmove(dest, src, cnt) != dest) {
1781 psbs("can't delete the character");
1784 end = end - hole_size; // adjust the new END
1786 dest = end - 1; // make sure dest in below end-1
1788 dest = end = text; // keep pointers valid
1789 file_modified++; // has the file been modified
1794 // copy text into register, then delete text.
1795 // if dist <= 0, do not include, or go past, a NewLine
1797 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1801 // make sure start <= stop
1803 // they are backwards, reverse them
1809 // we cannot cross NL boundaries
1813 // dont go past a NewLine
1814 for (; p + 1 <= stop; p++) {
1816 stop = p; // "stop" just before NewLine
1822 #ifdef CONFIG_FEATURE_VI_YANKMARK
1823 text_yank(start, stop, YDreg);
1824 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1825 if (yf == YANKDEL) {
1826 p = text_hole_delete(start, stop);
1831 static void show_help(void)
1833 puts("These features are available:"
1834 #ifdef CONFIG_FEATURE_VI_SEARCH
1835 "\n\tPattern searches with / and ?"
1836 #endif /* CONFIG_FEATURE_VI_SEARCH */
1837 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1838 "\n\tLast command repeat with \'.\'"
1839 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1840 #ifdef CONFIG_FEATURE_VI_YANKMARK
1841 "\n\tLine marking with 'x"
1842 "\n\tNamed buffers with \"x"
1843 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1844 #ifdef CONFIG_FEATURE_VI_READONLY
1845 "\n\tReadonly if vi is called as \"view\""
1846 "\n\tReadonly with -R command line arg"
1847 #endif /* CONFIG_FEATURE_VI_READONLY */
1848 #ifdef CONFIG_FEATURE_VI_SET
1849 "\n\tSome colon mode commands with \':\'"
1850 #endif /* CONFIG_FEATURE_VI_SET */
1851 #ifdef CONFIG_FEATURE_VI_SETOPTS
1852 "\n\tSettable options with \":set\""
1853 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1854 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1855 "\n\tSignal catching- ^C"
1856 "\n\tJob suspend and resume with ^Z"
1857 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1858 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1859 "\n\tAdapt to window re-sizes"
1860 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1864 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1869 strcpy((char *) buf, ""); // init buf
1870 if (strlen((char *) s) <= 0)
1871 s = (Byte *) "(NULL)";
1872 for (; *s > '\0'; s++) {
1876 c_is_no_print = c > 127 && !Isprint(c);
1877 if (c_is_no_print) {
1878 strcat((char *) buf, SOn);
1881 if (c < ' ' || c == 127) {
1882 strcat((char *) buf, "^");
1889 strcat((char *) buf, (char *) b);
1891 strcat((char *) buf, SOs);
1893 strcat((char *) buf, "$");
1898 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1899 static void start_new_cmd_q(Byte c)
1902 free(last_modifying_cmd);
1903 // get buffer for new cmd
1904 last_modifying_cmd = xmalloc(BUFSIZ);
1905 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1906 // if there is a current cmd count put it in the buffer first
1908 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1909 else // just save char c onto queue
1910 last_modifying_cmd[0] = c;
1914 static void end_cmd_q(void)
1916 #ifdef CONFIG_FEATURE_VI_YANKMARK
1917 YDreg = 26; // go back to default Yank/Delete reg
1918 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1922 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1924 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1925 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1929 i = strlen((char *) s);
1930 p = text_hole_make(p, i);
1931 strncpy((char *) p, (char *) s, i);
1932 for (cnt = 0; *s != '\0'; s++) {
1936 #ifdef CONFIG_FEATURE_VI_YANKMARK
1937 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1938 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1941 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1943 #ifdef CONFIG_FEATURE_VI_YANKMARK
1944 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1949 if (q < p) { // they are backwards- reverse them
1956 free(t); // if already a yank register, free it
1957 t = xmalloc(cnt + 1); // get a new register
1958 memset(t, '\0', cnt + 1); // clear new text[]
1959 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1964 static Byte what_reg(void)
1968 c = 'D'; // default to D-reg
1969 if (0 <= YDreg && YDreg <= 25)
1970 c = 'a' + (Byte) YDreg;
1978 static void check_context(Byte cmd)
1980 // A context is defined to be "modifying text"
1981 // Any modifying command establishes a new context.
1983 if (dot < context_start || dot > context_end) {
1984 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1985 // we are trying to modify text[]- make this the current context
1986 mark[27] = mark[26]; // move cur to prev
1987 mark[26] = dot; // move local to cur
1988 context_start = prev_line(prev_line(dot));
1989 context_end = next_line(next_line(dot));
1990 //loiter= start_loiter= now;
1996 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2000 // the current context is in mark[26]
2001 // the previous context is in mark[27]
2002 // only swap context if other context is valid
2003 if (text <= mark[27] && mark[27] <= end - 1) {
2005 mark[27] = mark[26];
2007 p = mark[26]; // where we are going- previous context
2008 context_start = prev_line(prev_line(prev_line(p)));
2009 context_end = next_line(next_line(next_line(p)));
2013 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2015 static int isblnk(Byte c) // is the char a blank or tab
2017 return (c == ' ' || c == '\t');
2020 //----- Set terminal attributes --------------------------------
2021 static void rawmode(void)
2023 tcgetattr(0, &term_orig);
2024 term_vi = term_orig;
2025 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2026 term_vi.c_iflag &= (~IXON & ~ICRNL);
2027 term_vi.c_oflag &= (~ONLCR);
2028 term_vi.c_cc[VMIN] = 1;
2029 term_vi.c_cc[VTIME] = 0;
2030 erase_char = term_vi.c_cc[VERASE];
2031 tcsetattr(0, TCSANOW, &term_vi);
2034 static void cookmode(void)
2037 tcsetattr(0, TCSANOW, &term_orig);
2040 //----- Come here when we get a window resize signal ---------
2041 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2042 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2044 signal(SIGWINCH, winch_sig);
2045 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2046 get_terminal_width_height(0, &columns, &rows);
2047 new_screen(rows, columns); // get memory for virtual screen
2048 redraw(TRUE); // re-draw the screen
2051 //----- Come here when we get a continue signal -------------------
2052 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2054 rawmode(); // terminal to "raw"
2055 last_status_cksum = 0; // force status update
2056 redraw(TRUE); // re-draw the screen
2058 signal(SIGTSTP, suspend_sig);
2059 signal(SIGCONT, SIG_DFL);
2060 kill(my_pid, SIGCONT);
2063 //----- Come here when we get a Suspend signal -------------------
2064 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2066 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2067 clear_to_eol(); // Erase to end of line
2068 cookmode(); // terminal to "cooked"
2070 signal(SIGCONT, cont_sig);
2071 signal(SIGTSTP, SIG_DFL);
2072 kill(my_pid, SIGTSTP);
2075 //----- Come here when we get a signal ---------------------------
2076 static void catch_sig(int sig)
2078 signal(SIGINT, catch_sig);
2080 longjmp(restart, sig);
2082 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2084 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2086 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2091 tv.tv_usec = hund * 10000;
2092 select(1, &rfds, NULL, NULL, &tv);
2093 return FD_ISSET(0, &rfds);
2096 #define readbuffer bb_common_bufsiz1
2098 static int readed_for_parse;
2100 //----- IO Routines --------------------------------------------
2101 static Byte readit(void) // read (maybe cursor) key from stdin
2110 static const struct esc_cmds esccmds[] = {
2111 {"OA", (Byte) VI_K_UP}, // cursor key Up
2112 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2113 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2114 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2115 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2116 {"OF", (Byte) VI_K_END}, // Cursor Key End
2117 {"[A", (Byte) VI_K_UP}, // cursor key Up
2118 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2119 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2120 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2121 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2122 {"[F", (Byte) VI_K_END}, // Cursor Key End
2123 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2124 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2125 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2126 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2127 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2128 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2129 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2130 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2131 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2132 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2133 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2134 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2135 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2136 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2137 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2138 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2139 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2140 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2141 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2142 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2143 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2146 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2148 (void) alarm(0); // turn alarm OFF while we wait for input
2150 n = readed_for_parse;
2151 // get input from User- are there already input chars in Q?
2154 // the Q is empty, wait for a typed char
2155 n = read(0, readbuffer, BUFSIZ - 1);
2158 goto ri0; // interrupted sys call
2161 if (errno == EFAULT)
2163 if (errno == EINVAL)
2171 if (readbuffer[0] == 27) {
2172 // This is an ESC char. Is this Esc sequence?
2173 // Could be bare Esc key. See if there are any
2174 // more chars to read after the ESC. This would
2175 // be a Function or Cursor Key sequence.
2179 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2181 // keep reading while there are input chars and room in buffer
2182 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2183 // read the rest of the ESC string
2184 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2190 readed_for_parse = n;
2193 if(c == 27 && n > 1) {
2194 // Maybe cursor or function key?
2195 const struct esc_cmds *eindex;
2197 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2198 int cnt = strlen(eindex->seq);
2202 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2204 // is a Cursor key- put derived value back into Q
2206 // for squeeze out the ESC sequence
2210 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2211 /* defined ESC sequence not found, set only one ESC */
2217 // remove key sequence from Q
2218 readed_for_parse -= n;
2219 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2220 (void) alarm(3); // we are done waiting for input, turn alarm ON
2224 //----- IO Routines --------------------------------------------
2225 static Byte get_one_char(void)
2229 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2230 // ! adding2q && ioq == 0 read()
2231 // ! adding2q && ioq != 0 *ioq
2232 // adding2q *last_modifying_cmd= read()
2234 // we are not adding to the q.
2235 // but, we may be reading from a q
2237 // there is no current q, read from STDIN
2238 c = readit(); // get the users input
2240 // there is a queue to get chars from first
2243 // the end of the q, read from STDIN
2245 ioq_start = ioq = 0;
2246 c = readit(); // get the users input
2250 // adding STDIN chars to q
2251 c = readit(); // get the users input
2252 if (last_modifying_cmd != 0) {
2253 int len = strlen((char *) last_modifying_cmd);
2254 if (len + 1 >= BUFSIZ) {
2255 psbs("last_modifying_cmd overrun");
2257 // add new char to q
2258 last_modifying_cmd[len] = c;
2262 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2263 c = readit(); // get the users input
2264 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2265 return c; // return the char, where ever it came from
2268 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2273 static Byte *obufp = NULL;
2275 strcpy((char *) buf, (char *) prompt);
2276 last_status_cksum = 0; // force status update
2277 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2278 clear_to_eol(); // clear the line
2279 write1((char *) prompt); // write out the :, /, or ? prompt
2281 for (i = strlen((char *) buf); i < BUFSIZ;) {
2282 c = get_one_char(); // read user input
2283 if (c == '\n' || c == '\r' || c == 27)
2284 break; // is this end of input
2285 if (c == erase_char || c == 8 || c == 127) {
2286 // user wants to erase prev char
2287 i--; // backup to prev char
2288 buf[i] = '\0'; // erase the char
2289 buf[i + 1] = '\0'; // null terminate buffer
2290 write1("\b \b"); // erase char on screen
2291 if (i <= 0) { // user backs up before b-o-l, exit
2295 buf[i] = c; // save char in buffer
2296 buf[i + 1] = '\0'; // make sure buffer is null terminated
2297 putchar(c); // echo the char back to user
2303 obufp = (Byte *) xstrdup((char *) buf);
2307 static int file_size(const Byte * fn) // what is the byte size of "fn"
2312 if (fn == 0 || strlen((char *)fn) <= 0)
2315 sr = stat((char *) fn, &st_buf); // see if file exists
2317 cnt = (int) st_buf.st_size;
2322 static int file_insert(Byte * fn, Byte * p, int size)
2327 #ifdef CONFIG_FEATURE_VI_READONLY
2329 #endif /* CONFIG_FEATURE_VI_READONLY */
2330 if (fn == 0 || strlen((char*) fn) <= 0) {
2331 psbs("No filename given");
2335 // OK- this is just a no-op
2340 psbs("Trying to insert a negative number (%d) of characters", size);
2343 if (p < text || p > end) {
2344 psbs("Trying to insert file outside of memory");
2348 // see if we can open the file
2349 #ifdef CONFIG_FEATURE_VI_READONLY
2350 if (vi_readonly) goto fi1; // do not try write-mode
2352 fd = open((char *) fn, O_RDWR); // assume read & write
2354 // could not open for writing- maybe file is read only
2355 #ifdef CONFIG_FEATURE_VI_READONLY
2358 fd = open((char *) fn, O_RDONLY); // try read-only
2360 psbs("\"%s\" %s", fn, "cannot open file");
2363 #ifdef CONFIG_FEATURE_VI_READONLY
2364 // got the file- read-only
2366 #endif /* CONFIG_FEATURE_VI_READONLY */
2368 p = text_hole_make(p, size);
2369 cnt = read(fd, p, size);
2373 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2374 psbs("cannot read file \"%s\"", fn);
2375 } else if (cnt < size) {
2376 // There was a partial read, shrink unused space text[]
2377 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2378 psbs("cannot read all of file \"%s\"", fn);
2386 static int file_write(Byte * fn, Byte * first, Byte * last)
2388 int fd, cnt, charcnt;
2391 psbs("No current filename");
2395 // FIXIT- use the correct umask()
2396 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2399 cnt = last - first + 1;
2400 charcnt = write(fd, first, cnt);
2401 if (charcnt == cnt) {
2403 //file_modified= FALSE; // the file has not been modified
2411 //----- Terminal Drawing ---------------------------------------
2412 // The terminal is made up of 'rows' line of 'columns' columns.
2413 // classically this would be 24 x 80.
2414 // screen coordinates
2420 // 23,0 ... 23,79 status line
2423 //----- Move the cursor to row x col (count from 0, not 1) -------
2424 static void place_cursor(int row, int col, int opti)
2428 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2431 // char cm3[BUFSIZ];
2433 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2435 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2437 if (row < 0) row = 0;
2438 if (row >= rows) row = rows - 1;
2439 if (col < 0) col = 0;
2440 if (col >= columns) col = columns - 1;
2442 //----- 1. Try the standard terminal ESC sequence
2443 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2445 if (! opti) goto pc0;
2447 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2448 //----- find the minimum # of chars to move cursor -------------
2449 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2450 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2452 // move to the correct row
2453 while (row < Rrow) {
2454 // the cursor has to move up
2458 while (row > Rrow) {
2459 // the cursor has to move down
2460 strcat(cm2, CMdown);
2464 // now move to the correct column
2465 strcat(cm2, "\r"); // start at col 0
2466 // just send out orignal source char to get to correct place
2467 screenp = &screen[row * columns]; // start of screen line
2468 strncat(cm2, (char* )screenp, col);
2470 //----- 3. Try some other way of moving cursor
2471 //---------------------------------------------
2473 // pick the shortest cursor motion to send out
2475 if (strlen(cm2) < strlen(cm)) {
2477 } /* else if (strlen(cm3) < strlen(cm)) {
2480 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2482 write1(cm); // move the cursor
2485 //----- Erase from cursor to end of line -----------------------
2486 static void clear_to_eol(void)
2488 write1(Ceol); // Erase from cursor to end of line
2491 //----- Erase from cursor to end of screen -----------------------
2492 static void clear_to_eos(void)
2494 write1(Ceos); // Erase from cursor to end of screen
2497 //----- Start standout mode ------------------------------------
2498 static void standout_start(void) // send "start reverse video" sequence
2500 write1(SOs); // Start reverse video mode
2503 //----- End standout mode --------------------------------------
2504 static void standout_end(void) // send "end reverse video" sequence
2506 write1(SOn); // End reverse video mode
2509 //----- Flash the screen --------------------------------------
2510 static void flash(int h)
2512 standout_start(); // send "start reverse video" sequence
2515 standout_end(); // send "end reverse video" sequence
2519 static void Indicate_Error(void)
2521 #ifdef CONFIG_FEATURE_VI_CRASHME
2523 return; // generate a random command
2524 #endif /* CONFIG_FEATURE_VI_CRASHME */
2526 write1(bell); // send out a bell character
2532 //----- Screen[] Routines --------------------------------------
2533 //----- Erase the Screen[] memory ------------------------------
2534 static void screen_erase(void)
2536 memset(screen, ' ', screensize); // clear new screen
2539 static int bufsum(unsigned char *buf, int count)
2542 unsigned char *e = buf + count;
2548 //----- Draw the status line at bottom of the screen -------------
2549 static void show_status_line(void)
2551 int cnt = 0, cksum = 0;
2553 // either we already have an error or status message, or we
2555 if (!have_status_msg) {
2556 cnt = format_edit_status();
2557 cksum = bufsum(status_buffer, cnt);
2559 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2560 last_status_cksum= cksum; // remember if we have seen this line
2561 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2562 write1((char*)status_buffer);
2564 if (have_status_msg) {
2565 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2567 have_status_msg = 0;
2570 have_status_msg = 0;
2572 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2577 //----- format the status buffer, the bottom line of screen ------
2578 // format status buffer, with STANDOUT mode
2579 static void psbs(const char *format, ...)
2583 va_start(args, format);
2584 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2585 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2586 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2589 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2594 // format status buffer
2595 static void psb(const char *format, ...)
2599 va_start(args, format);
2600 vsprintf((char *) status_buffer, format, args);
2603 have_status_msg = 1;
2608 static void ni(Byte * s) // display messages
2612 print_literal(buf, s);
2613 psbs("\'%s\' is not implemented", buf);
2616 static int format_edit_status(void) // show file status on status line
2618 int cur, percent, ret, trunc_at;
2621 // file_modified is now a counter rather than a flag. this
2622 // helps reduce the amount of line counting we need to do.
2623 // (this will cause a mis-reporting of modified status
2624 // once every MAXINT editing operations.)
2626 // it would be nice to do a similar optimization here -- if
2627 // we haven't done a motion that could have changed which line
2628 // we're on, then we shouldn't have to do this count_lines()
2629 cur = count_lines(text, dot);
2631 // reduce counting -- the total lines can't have
2632 // changed if we haven't done any edits.
2633 if (file_modified != last_file_modified) {
2634 tot = cur + count_lines(dot, end - 1) - 1;
2635 last_file_modified = file_modified;
2638 // current line percent
2639 // ------------- ~~ ----------
2642 percent = (100 * cur) / tot;
2648 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2649 columns : STATUS_BUFFER_LEN-1;
2651 ret = snprintf((char *) status_buffer, trunc_at+1,
2652 #ifdef CONFIG_FEATURE_VI_READONLY
2653 "%c %s%s%s %d/%d %d%%",
2655 "%c %s%s %d/%d %d%%",
2657 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2658 (cfn != 0 ? (char *) cfn : "No file"),
2659 #ifdef CONFIG_FEATURE_VI_READONLY
2660 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2662 (file_modified ? " [modified]" : ""),
2665 if (ret >= 0 && ret < trunc_at)
2666 return ret; /* it all fit */
2668 return trunc_at; /* had to truncate */
2671 //----- Force refresh of all Lines -----------------------------
2672 static void redraw(int full_screen)
2674 place_cursor(0, 0, FALSE); // put cursor in correct place
2675 clear_to_eos(); // tel terminal to erase display
2676 screen_erase(); // erase the internal screen buffer
2677 last_status_cksum = 0; // force status update
2678 refresh(full_screen); // this will redraw the entire display
2682 //----- Format a text[] line into a buffer ---------------------
2683 static void format_line(Byte *dest, Byte *src, int li)
2688 for (co= 0; co < MAX_SCR_COLS; co++) {
2689 c= ' '; // assume blank
2690 if (li > 0 && co == 0) {
2691 c = '~'; // not first line, assume Tilde
2693 // are there chars in text[] and have we gone past the end
2694 if (text < end && src < end) {
2699 if (c > 127 && !Isprint(c)) {
2702 if (c < ' ' || c == 127) {
2706 for (; (co % tabstop) != (tabstop - 1); co++) {
2714 c += '@'; // make it visible
2717 // the co++ is done here so that the column will
2718 // not be overwritten when we blank-out the rest of line
2725 //----- Refresh the changed screen lines -----------------------
2726 // Copy the source line from text[] into the buffer and note
2727 // if the current screenline is different from the new buffer.
2728 // If they differ then that line needs redrawing on the terminal.
2730 static void refresh(int full_screen)
2732 static int old_offset;
2734 Byte buf[MAX_SCR_COLS];
2735 Byte *tp, *sp; // pointer into text[] and screen[]
2736 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2737 int last_li= -2; // last line that changed- for optimizing cursor movement
2738 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2740 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2741 get_terminal_width_height(0, &columns, &rows);
2742 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2743 tp = screenbegin; // index into text[] of top line
2745 // compare text[] to screen[] and mark screen[] lines that need updating
2746 for (li = 0; li < rows - 1; li++) {
2747 int cs, ce; // column start & end
2748 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2749 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2750 // format current text line into buf
2751 format_line(buf, tp, li);
2753 // skip to the end of the current text[] line
2754 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2756 // see if there are any changes between vitual screen and buf
2757 changed = FALSE; // assume no change
2760 sp = &screen[li * columns]; // start of screen line
2762 // force re-draw of every single column from 0 - columns-1
2765 // compare newly formatted buffer with virtual screen
2766 // look forward for first difference between buf and screen
2767 for ( ; cs <= ce; cs++) {
2768 if (buf[cs + offset] != sp[cs]) {
2769 changed = TRUE; // mark for redraw
2774 // look backward for last difference between buf and screen
2775 for ( ; ce >= cs; ce--) {
2776 if (buf[ce + offset] != sp[ce]) {
2777 changed = TRUE; // mark for redraw
2781 // now, cs is index of first diff, and ce is index of last diff
2783 // if horz offset has changed, force a redraw
2784 if (offset != old_offset) {
2789 // make a sanity check of columns indexes
2791 if (ce > columns-1) ce= columns-1;
2792 if (cs > ce) { cs= 0; ce= columns-1; }
2793 // is there a change between vitual screen and buf
2795 // copy changed part of buffer to virtual screen
2796 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2798 // move cursor to column of first change
2799 if (offset != old_offset) {
2800 // opti_cur_move is still too stupid
2801 // to handle offsets correctly
2802 place_cursor(li, cs, FALSE);
2804 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2805 // if this just the next line
2806 // try to optimize cursor movement
2807 // otherwise, use standard ESC sequence
2808 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2810 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2811 place_cursor(li, cs, FALSE); // use standard ESC sequence
2812 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2815 // write line out to terminal
2818 char *out = (char*)sp+cs;
2825 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2827 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2831 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2832 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2835 place_cursor(crow, ccol, FALSE);
2836 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2838 if (offset != old_offset)
2839 old_offset = offset;
2842 //---------------------------------------------------------------------
2843 //----- the Ascii Chart -----------------------------------------------
2845 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2846 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2847 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2848 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2849 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2850 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2851 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2852 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2853 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2854 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2855 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2856 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2857 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2858 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2859 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2860 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2861 //---------------------------------------------------------------------
2863 //----- Execute a Vi Command -----------------------------------
2864 static void do_cmd(Byte c)
2866 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2867 int cnt, i, j, dir, yf;
2869 c1 = c; // quiet the compiler
2870 cnt = yf = dir = 0; // quiet the compiler
2871 p = q = save_dot = msg = buf; // quiet the compiler
2872 memset(buf, '\0', 9); // clear buf
2876 /* if this is a cursor key, skip these checks */
2889 if (cmd_mode == 2) {
2890 // flip-flop Insert/Replace mode
2891 if (c == VI_K_INSERT) goto dc_i;
2892 // we are 'R'eplacing the current *dot with new char
2894 // don't Replace past E-o-l
2895 cmd_mode = 1; // convert to insert
2897 if (1 <= c || Isprint(c)) {
2899 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2900 dot = char_insert(dot, c); // insert new char
2905 if (cmd_mode == 1) {
2906 // hitting "Insert" twice means "R" replace mode
2907 if (c == VI_K_INSERT) goto dc5;
2908 // insert the char c at "dot"
2909 if (1 <= c || Isprint(c)) {
2910 dot = char_insert(dot, c);
2925 #ifdef CONFIG_FEATURE_VI_CRASHME
2926 case 0x14: // dc4 ctrl-T
2927 crashme = (crashme == 0) ? 1 : 0;
2929 #endif /* CONFIG_FEATURE_VI_CRASHME */
2958 //case 'u': // u- FIXME- there is no undo
2960 default: // unrecognised command
2969 end_cmd_q(); // stop adding to q
2970 case 0x00: // nul- ignore
2972 case 2: // ctrl-B scroll up full screen
2973 case VI_K_PAGEUP: // Cursor Key Page Up
2974 dot_scroll(rows - 2, -1);
2976 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2977 case 0x03: // ctrl-C interrupt
2978 longjmp(restart, 1);
2980 case 26: // ctrl-Z suspend
2981 suspend_sig(SIGTSTP);
2983 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2984 case 4: // ctrl-D scroll down half screen
2985 dot_scroll((rows - 2) / 2, 1);
2987 case 5: // ctrl-E scroll down one line
2990 case 6: // ctrl-F scroll down full screen
2991 case VI_K_PAGEDOWN: // Cursor Key Page Down
2992 dot_scroll(rows - 2, 1);
2994 case 7: // ctrl-G show current status
2995 last_status_cksum = 0; // force status update
2997 case 'h': // h- move left
2998 case VI_K_LEFT: // cursor key Left
2999 case 8: // ctrl-H- move left (This may be ERASE char)
3000 case 127: // DEL- move left (This may be ERASE char)
3006 case 10: // Newline ^J
3007 case 'j': // j- goto next line, same col
3008 case VI_K_DOWN: // cursor key Down
3012 dot_next(); // go to next B-o-l
3013 dot = move_to_col(dot, ccol + offset); // try stay in same col
3015 case 12: // ctrl-L force redraw whole screen
3016 case 18: // ctrl-R force redraw
3017 place_cursor(0, 0, FALSE); // put cursor in correct place
3018 clear_to_eos(); // tel terminal to erase display
3020 screen_erase(); // erase the internal screen buffer
3021 last_status_cksum = 0; // force status update
3022 refresh(TRUE); // this will redraw the entire display
3024 case 13: // Carriage Return ^M
3025 case '+': // +- goto next line
3032 case 21: // ctrl-U scroll up half screen
3033 dot_scroll((rows - 2) / 2, -1);
3035 case 25: // ctrl-Y scroll up one line
3041 cmd_mode = 0; // stop insrting
3043 last_status_cksum = 0; // force status update
3045 case ' ': // move right
3046 case 'l': // move right
3047 case VI_K_RIGHT: // Cursor Key Right
3053 #ifdef CONFIG_FEATURE_VI_YANKMARK
3054 case '"': // "- name a register to use for Delete/Yank
3055 c1 = get_one_char();
3063 case '\'': // '- goto a specific mark
3064 c1 = get_one_char();
3070 if (text <= q && q < end) {
3072 dot_begin(); // go to B-o-l
3075 } else if (c1 == '\'') { // goto previous context
3076 dot = swap_context(dot); // swap current and previous context
3077 dot_begin(); // go to B-o-l
3083 case 'm': // m- Mark a line
3084 // this is really stupid. If there are any inserts or deletes
3085 // between text[0] and dot then this mark will not point to the
3086 // correct location! It could be off by many lines!
3087 // Well..., at least its quick and dirty.
3088 c1 = get_one_char();
3092 // remember the line
3093 mark[(int) c1] = dot;
3098 case 'P': // P- Put register before
3099 case 'p': // p- put register after
3102 psbs("Nothing in register %c", what_reg());
3105 // are we putting whole lines or strings
3106 if (strchr((char *) p, '\n') != NULL) {
3108 dot_begin(); // putting lines- Put above
3111 // are we putting after very last line?
3112 if (end_line(dot) == (end - 1)) {
3113 dot = end; // force dot to end of text[]
3115 dot_next(); // next line, then put before
3120 dot_right(); // move to right, can move to NL
3122 dot = string_insert(dot, p); // insert the string
3123 end_cmd_q(); // stop adding to q
3125 case 'U': // U- Undo; replace current line with original version
3126 if (reg[Ureg] != 0) {
3127 p = begin_line(dot);
3129 p = text_hole_delete(p, q); // delete cur line
3130 p = string_insert(p, reg[Ureg]); // insert orig line
3135 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3136 case '$': // $- goto end of line
3137 case VI_K_END: // Cursor Key End
3141 dot = end_line(dot);
3143 case '%': // %- find matching char of pair () [] {}
3144 for (q = dot; q < end && *q != '\n'; q++) {
3145 if (strchr("()[]{}", *q) != NULL) {
3146 // we found half of a pair
3147 p = find_pair(q, *q);
3159 case 'f': // f- forward to a user specified char
3160 last_forward_char = get_one_char(); // get the search char
3162 // dont separate these two commands. 'f' depends on ';'
3164 //**** fall thru to ... ';'
3165 case ';': // ;- look at rest of line for last forward char
3169 if (last_forward_char == 0) break;
3171 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3174 if (*q == last_forward_char)
3177 case '-': // -- goto prev line
3184 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3185 case '.': // .- repeat the last modifying command
3186 // Stuff the last_modifying_cmd back into stdin
3187 // and let it be re-executed.
3188 if (last_modifying_cmd != 0) {
3189 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3192 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3193 #ifdef CONFIG_FEATURE_VI_SEARCH
3194 case '?': // /- search for a pattern
3195 case '/': // /- search for a pattern
3198 q = get_input_line(buf); // get input line- use "status line"
3199 if (strlen((char *) q) == 1)
3200 goto dc3; // if no pat re-use old pat
3201 if (strlen((char *) q) > 1) { // new pat- save it and find
3202 // there is a new pat
3203 free(last_search_pattern);
3204 last_search_pattern = (Byte *) xstrdup((char *) q);
3205 goto dc3; // now find the pattern
3207 // user changed mind and erased the "/"- do nothing
3209 case 'N': // N- backward search for last pattern
3213 dir = BACK; // assume BACKWARD search
3215 if (last_search_pattern[0] == '?') {
3219 goto dc4; // now search for pattern
3221 case 'n': // n- repeat search for last pattern
3222 // search rest of text[] starting at next char
3223 // if search fails return orignal "p" not the "p+1" address
3228 if (last_search_pattern == 0) {
3229 msg = (Byte *) "No previous regular expression";
3232 if (last_search_pattern[0] == '/') {
3233 dir = FORWARD; // assume FORWARD search
3236 if (last_search_pattern[0] == '?') {
3241 q = char_search(p, last_search_pattern + 1, dir, FULL);
3243 dot = q; // good search, update "dot"
3247 // no pattern found between "dot" and "end"- continue at top
3252 q = char_search(p, last_search_pattern + 1, dir, FULL);
3253 if (q != NULL) { // found something
3254 dot = q; // found new pattern- goto it
3255 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3257 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3260 msg = (Byte *) "Pattern not found";
3263 if (*msg) psbs("%s", msg);
3265 case '{': // {- move backward paragraph
3266 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3267 if (q != NULL) { // found blank line
3268 dot = next_line(q); // move to next blank line
3271 case '}': // }- move forward paragraph
3272 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3273 if (q != NULL) { // found blank line
3274 dot = next_line(q); // move to next blank line
3277 #endif /* CONFIG_FEATURE_VI_SEARCH */
3278 case '0': // 0- goto begining of line
3288 if (c == '0' && cmdcnt < 1) {
3289 dot_begin(); // this was a standalone zero
3291 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3294 case ':': // :- the colon mode commands
3295 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3296 #ifdef CONFIG_FEATURE_VI_COLON
3297 colon(p); // execute the command
3298 #else /* CONFIG_FEATURE_VI_COLON */
3300 p++; // move past the ':'
3301 cnt = strlen((char *) p);
3304 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3305 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3306 if (file_modified && p[1] != '!') {
3307 psbs("No write since last change (:quit! overrides)");
3311 } else if (strncasecmp((char *) p, "write", cnt) == 0
3312 || strncasecmp((char *) p, "wq", cnt) == 0
3313 || strncasecmp((char *) p, "wn", cnt) == 0
3314 || strncasecmp((char *) p, "x", cnt) == 0) {
3315 cnt = file_write(cfn, text, end - 1);
3318 psbs("Write error: %s", strerror(errno));
3321 last_file_modified = -1;
3322 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3323 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3324 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3328 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3329 last_status_cksum = 0; // force status update
3330 } else if (sscanf((char *) p, "%d", &j) > 0) {
3331 dot = find_line(j); // go to line # j
3333 } else { // unrecognised cmd
3336 #endif /* CONFIG_FEATURE_VI_COLON */
3338 case '<': // <- Left shift something
3339 case '>': // >- Right shift something
3340 cnt = count_lines(text, dot); // remember what line we are on
3341 c1 = get_one_char(); // get the type of thing to delete
3342 find_range(&p, &q, c1);
3343 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3346 i = count_lines(p, q); // # of lines we are shifting
3347 for ( ; i > 0; i--, p = next_line(p)) {
3349 // shift left- remove tab or 8 spaces
3351 // shrink buffer 1 char
3352 (void) text_hole_delete(p, p);
3353 } else if (*p == ' ') {
3354 // we should be calculating columns, not just SPACE
3355 for (j = 0; *p == ' ' && j < tabstop; j++) {
3356 (void) text_hole_delete(p, p);
3359 } else if (c == '>') {
3360 // shift right -- add tab or 8 spaces
3361 (void) char_insert(p, '\t');
3364 dot = find_line(cnt); // what line were we on
3366 end_cmd_q(); // stop adding to q
3368 case 'A': // A- append at e-o-l
3369 dot_end(); // go to e-o-l
3370 //**** fall thru to ... 'a'
3371 case 'a': // a- append after current char
3376 case 'B': // B- back a blank-delimited Word
3377 case 'E': // E- end of a blank-delimited word
3378 case 'W': // W- forward a blank-delimited word
3385 if (c == 'W' || isspace(dot[dir])) {
3386 dot = skip_thing(dot, 1, dir, S_TO_WS);
3387 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3390 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3392 case 'C': // C- Change to e-o-l
3393 case 'D': // D- delete to e-o-l
3395 dot = dollar_line(dot); // move to before NL
3396 // copy text into a register and delete
3397 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3399 goto dc_i; // start inserting
3400 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3402 end_cmd_q(); // stop adding to q
3403 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3405 case 'G': // G- goto to a line number (default= E-O-F)
3406 dot = end - 1; // assume E-O-F
3408 dot = find_line(cmdcnt); // what line is #cmdcnt
3412 case 'H': // H- goto top line on screen
3414 if (cmdcnt > (rows - 1)) {
3415 cmdcnt = (rows - 1);
3422 case 'I': // I- insert before first non-blank
3425 //**** fall thru to ... 'i'
3426 case 'i': // i- insert before current char
3427 case VI_K_INSERT: // Cursor Key Insert
3429 cmd_mode = 1; // start insrting
3431 case 'J': // J- join current and next lines together
3435 dot_end(); // move to NL
3436 if (dot < end - 1) { // make sure not last char in text[]
3437 *dot++ = ' '; // replace NL with space
3439 while (isblnk(*dot)) { // delete leading WS
3443 end_cmd_q(); // stop adding to q
3445 case 'L': // L- goto bottom line on screen
3447 if (cmdcnt > (rows - 1)) {
3448 cmdcnt = (rows - 1);
3456 case 'M': // M- goto middle line on screen
3458 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3459 dot = next_line(dot);
3461 case 'O': // O- open a empty line above
3463 p = begin_line(dot);
3464 if (p[-1] == '\n') {
3466 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3468 dot = char_insert(dot, '\n');
3471 dot = char_insert(dot, '\n'); // i\n ESC
3476 case 'R': // R- continuous Replace char
3480 case 'X': // X- delete char before dot
3481 case 'x': // x- delete the current char
3482 case 's': // s- substitute the current char
3489 if (dot[dir] != '\n') {
3491 dot--; // delete prev char
3492 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3495 goto dc_i; // start insrting
3496 end_cmd_q(); // stop adding to q
3498 case 'Z': // Z- if modified, {write}; exit
3499 // ZZ means to save file (if necessary), then exit
3500 c1 = get_one_char();
3505 if (file_modified) {
3506 #ifdef CONFIG_FEATURE_VI_READONLY
3507 if (vi_readonly || readonly) {
3508 psbs("\"%s\" File is read only", cfn);
3511 #endif /* CONFIG_FEATURE_VI_READONLY */
3512 cnt = file_write(cfn, text, end - 1);
3515 psbs("Write error: %s", strerror(errno));
3516 } else if (cnt == (end - 1 - text + 1)) {
3523 case '^': // ^- move to first non-blank on line
3527 case 'b': // b- back a word
3528 case 'e': // e- end of word
3535 if ((dot + dir) < text || (dot + dir) > end - 1)
3538 if (isspace(*dot)) {
3539 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3541 if (isalnum(*dot) || *dot == '_') {
3542 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3543 } else if (ispunct(*dot)) {
3544 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3547 case 'c': // c- change something
3548 case 'd': // d- delete something
3549 #ifdef CONFIG_FEATURE_VI_YANKMARK
3550 case 'y': // y- yank something
3551 case 'Y': // Y- Yank a line
3552 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3553 yf = YANKDEL; // assume either "c" or "d"
3554 #ifdef CONFIG_FEATURE_VI_YANKMARK
3555 if (c == 'y' || c == 'Y')
3557 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3560 c1 = get_one_char(); // get the type of thing to delete
3561 find_range(&p, &q, c1);
3562 if (c1 == 27) { // ESC- user changed mind and wants out
3563 c = c1 = 27; // Escape- do nothing
3564 } else if (strchr("wW", c1)) {
3566 // don't include trailing WS as part of word
3567 while (isblnk(*q)) {
3568 if (q <= text || q[-1] == '\n')
3573 dot = yank_delete(p, q, 0, yf); // delete word
3574 } else if (strchr("^0bBeEft$", c1)) {
3575 // single line copy text into a register and delete
3576 dot = yank_delete(p, q, 0, yf); // delete word
3577 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3578 // multiple line copy text into a register and delete
3579 dot = yank_delete(p, q, 1, yf); // delete lines
3581 dot = char_insert(dot, '\n');
3582 // on the last line of file don't move to prev line
3583 if (dot != (end-1)) {
3586 } else if (c == 'd') {
3591 // could not recognize object
3592 c = c1 = 27; // error-
3596 // if CHANGING, not deleting, start inserting after the delete
3598 strcpy((char *) buf, "Change");
3599 goto dc_i; // start inserting
3602 strcpy((char *) buf, "Delete");
3604 #ifdef CONFIG_FEATURE_VI_YANKMARK
3605 if (c == 'y' || c == 'Y') {
3606 strcpy((char *) buf, "Yank");
3609 q = p + strlen((char *) p);
3610 for (cnt = 0; p <= q; p++) {
3614 psb("%s %d lines (%d chars) using [%c]",
3615 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3616 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3617 end_cmd_q(); // stop adding to q
3620 case 'k': // k- goto prev line, same col
3621 case VI_K_UP: // cursor key Up
3626 dot = move_to_col(dot, ccol + offset); // try stay in same col
3628 case 'r': // r- replace the current char with user input
3629 c1 = get_one_char(); // get the replacement char
3632 file_modified++; // has the file been modified
3634 end_cmd_q(); // stop adding to q
3636 case 't': // t- move to char prior to next x
3637 last_forward_char = get_one_char();
3639 if (*dot == last_forward_char)
3641 last_forward_char= 0;
3643 case 'w': // w- forward a word
3647 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3648 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3649 } else if (ispunct(*dot)) { // we are on PUNCT
3650 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3653 dot++; // move over word
3654 if (isspace(*dot)) {
3655 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3659 c1 = get_one_char(); // get the replacement char
3662 cnt = (rows - 2) / 2; // put dot at center
3664 cnt = rows - 2; // put dot at bottom
3665 screenbegin = begin_line(dot); // start dot at top
3666 dot_scroll(cnt, -1);
3668 case '|': // |- move to column "cmdcnt"
3669 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3671 case '~': // ~- flip the case of letters a-z -> A-Z
3675 if (islower(*dot)) {
3676 *dot = toupper(*dot);
3677 file_modified++; // has the file been modified
3678 } else if (isupper(*dot)) {
3679 *dot = tolower(*dot);
3680 file_modified++; // has the file been modified
3683 end_cmd_q(); // stop adding to q
3685 //----- The Cursor and Function Keys -----------------------------
3686 case VI_K_HOME: // Cursor Key Home
3689 // The Fn keys could point to do_macro which could translate them
3690 case VI_K_FUN1: // Function Key F1
3691 case VI_K_FUN2: // Function Key F2
3692 case VI_K_FUN3: // Function Key F3
3693 case VI_K_FUN4: // Function Key F4
3694 case VI_K_FUN5: // Function Key F5
3695 case VI_K_FUN6: // Function Key F6
3696 case VI_K_FUN7: // Function Key F7
3697 case VI_K_FUN8: // Function Key F8
3698 case VI_K_FUN9: // Function Key F9
3699 case VI_K_FUN10: // Function Key F10
3700 case VI_K_FUN11: // Function Key F11
3701 case VI_K_FUN12: // Function Key F12
3706 // if text[] just became empty, add back an empty line
3708 (void) char_insert(text, '\n'); // start empty buf with dummy line
3711 // it is OK for dot to exactly equal to end, otherwise check dot validity
3713 dot = bound_dot(dot); // make sure "dot" is valid
3715 #ifdef CONFIG_FEATURE_VI_YANKMARK
3716 check_context(c); // update the current context
3717 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3720 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3721 cnt = dot - begin_line(dot);
3722 // Try to stay off of the Newline
3723 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3727 #ifdef CONFIG_FEATURE_VI_CRASHME
3728 static int totalcmds = 0;
3729 static int Mp = 85; // Movement command Probability
3730 static int Np = 90; // Non-movement command Probability
3731 static int Dp = 96; // Delete command Probability
3732 static int Ip = 97; // Insert command Probability
3733 static int Yp = 98; // Yank command Probability
3734 static int Pp = 99; // Put command Probability
3735 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3736 char chars[20] = "\t012345 abcdABCD-=.$";
3737 char *words[20] = { "this", "is", "a", "test",
3738 "broadcast", "the", "emergency", "of",
3739 "system", "quick", "brown", "fox",
3740 "jumped", "over", "lazy", "dogs",
3741 "back", "January", "Febuary", "March"
3744 "You should have received a copy of the GNU General Public License\n",
3745 "char c, cm, *cmd, *cmd1;\n",
3746 "generate a command by percentages\n",
3747 "Numbers may be typed as a prefix to some commands.\n",
3748 "Quit, discarding changes!\n",
3749 "Forced write, if permission originally not valid.\n",
3750 "In general, any ex or ed command (such as substitute or delete).\n",
3751 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3752 "Please get w/ me and I will go over it with you.\n",
3753 "The following is a list of scheduled, committed changes.\n",
3754 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3755 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3756 "Any question about transactions please contact Sterling Huxley.\n",
3757 "I will try to get back to you by Friday, December 31.\n",
3758 "This Change will be implemented on Friday.\n",
3759 "Let me know if you have problems accessing this;\n",
3760 "Sterling Huxley recently added you to the access list.\n",
3761 "Would you like to go to lunch?\n",
3762 "The last command will be automatically run.\n",
3763 "This is too much english for a computer geek.\n",
3765 char *multilines[20] = {
3766 "You should have received a copy of the GNU General Public License\n",
3767 "char c, cm, *cmd, *cmd1;\n",
3768 "generate a command by percentages\n",
3769 "Numbers may be typed as a prefix to some commands.\n",
3770 "Quit, discarding changes!\n",
3771 "Forced write, if permission originally not valid.\n",
3772 "In general, any ex or ed command (such as substitute or delete).\n",
3773 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3774 "Please get w/ me and I will go over it with you.\n",
3775 "The following is a list of scheduled, committed changes.\n",
3776 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3777 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3778 "Any question about transactions please contact Sterling Huxley.\n",
3779 "I will try to get back to you by Friday, December 31.\n",
3780 "This Change will be implemented on Friday.\n",
3781 "Let me know if you have problems accessing this;\n",
3782 "Sterling Huxley recently added you to the access list.\n",
3783 "Would you like to go to lunch?\n",
3784 "The last command will be automatically run.\n",
3785 "This is too much english for a computer geek.\n",
3788 // create a random command to execute
3789 static void crash_dummy()
3791 static int sleeptime; // how long to pause between commands
3792 char c, cm, *cmd, *cmd1;
3793 int i, cnt, thing, rbi, startrbi, percent;
3795 // "dot" movement commands
3796 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3798 // is there already a command running?
3799 if (readed_for_parse > 0)
3803 sleeptime = 0; // how long to pause between commands
3804 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3805 // generate a command by percentages
3806 percent = (int) lrand48() % 100; // get a number from 0-99
3807 if (percent < Mp) { // Movement commands
3808 // available commands
3811 } else if (percent < Np) { // non-movement commands
3812 cmd = "mz<>\'\""; // available commands
3814 } else if (percent < Dp) { // Delete commands
3815 cmd = "dx"; // available commands
3817 } else if (percent < Ip) { // Inset commands
3818 cmd = "iIaAsrJ"; // available commands
3820 } else if (percent < Yp) { // Yank commands
3821 cmd = "yY"; // available commands
3823 } else if (percent < Pp) { // Put commands
3824 cmd = "pP"; // available commands
3827 // We do not know how to handle this command, try again
3831 // randomly pick one of the available cmds from "cmd[]"
3832 i = (int) lrand48() % strlen(cmd);
3834 if (strchr(":\024", cm))
3835 goto cd0; // dont allow colon or ctrl-T commands
3836 readbuffer[rbi++] = cm; // put cmd into input buffer
3838 // now we have the command-
3839 // there are 1, 2, and multi char commands
3840 // find out which and generate the rest of command as necessary
3841 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3842 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3843 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3844 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3846 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3848 readbuffer[rbi++] = c; // add movement to input buffer
3850 if (strchr("iIaAsc", cm)) { // multi-char commands
3852 // change some thing
3853 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3855 readbuffer[rbi++] = c; // add movement to input buffer
3857 thing = (int) lrand48() % 4; // what thing to insert
3858 cnt = (int) lrand48() % 10; // how many to insert
3859 for (i = 0; i < cnt; i++) {
3860 if (thing == 0) { // insert chars
3861 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3862 } else if (thing == 1) { // insert words
3863 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3864 strcat((char *) readbuffer, " ");
3865 sleeptime = 0; // how fast to type
3866 } else if (thing == 2) { // insert lines
3867 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3868 sleeptime = 0; // how fast to type
3869 } else { // insert multi-lines
3870 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3871 sleeptime = 0; // how fast to type
3874 strcat((char *) readbuffer, "\033");
3876 readed_for_parse = strlen(readbuffer);
3880 (void) mysleep(sleeptime); // sleep 1/100 sec
3883 // test to see if there are any errors
3884 static void crash_test()
3886 static time_t oldtim;
3888 char d[2], msg[BUFSIZ];
3892 strcat((char *) msg, "end<text ");
3894 if (end > textend) {
3895 strcat((char *) msg, "end>textend ");
3898 strcat((char *) msg, "dot<text ");
3901 strcat((char *) msg, "dot>end ");
3903 if (screenbegin < text) {
3904 strcat((char *) msg, "screenbegin<text ");
3906 if (screenbegin > end - 1) {
3907 strcat((char *) msg, "screenbegin>end-1 ");
3910 if (strlen(msg) > 0) {
3912 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3913 totalcmds, last_input_char, msg, SOs, SOn);
3915 while (read(0, d, 1) > 0) {
3916 if (d[0] == '\n' || d[0] == '\r')
3921 tim = (time_t) time((time_t *) 0);
3922 if (tim >= (oldtim + 3)) {
3923 sprintf((char *) status_buffer,
3924 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3925 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3930 #endif /* CONFIG_FEATURE_VI_CRASHME */