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
663 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
664 // :!ls run the <cmd>
665 (void) alarm(0); // wait for input- no alarms
666 place_cursor(rows - 1, 0, FALSE); // go to Status line
667 clear_to_eol(); // clear the line
669 system((char*)(orig_buf+1)); // run the cmd
671 Hit_Return(); // let user see results
672 (void) alarm(3); // done waiting for input
673 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
674 if (b < 0) { // no addr given- use defaults
675 b = e = count_lines(text, dot);
678 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
679 if (b < 0) { // no addr given- use defaults
680 q = begin_line(dot); // assume .,. for the range
683 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
685 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
688 // don't edit, if the current file has been modified
689 if (file_modified && ! useforce) {
690 psbs("No write since last change (:edit! overrides)");
693 if (strlen((char*)args) > 0) {
694 // the user supplied a file name
696 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
697 // no user supplied name- use the current filename
701 // no user file name, no current name- punt
702 psbs("No current filename");
706 // see if file exists- if not, its just a new file request
707 if ((sr=stat((char*)fn, &st_buf)) < 0) {
708 // This is just a request for a new file creation.
709 // The file_insert below will fail but we get
710 // an empty buffer with a file name. Then the "write"
711 // command can do the create.
713 if ((st_buf.st_mode & (S_IFREG)) == 0) {
714 // This is not a regular file
715 psbs("\"%s\" is not a regular file", fn);
718 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
719 // dont have any read permissions
720 psbs("\"%s\" is not readable", fn);
725 // There is a read-able regular file
726 // make this the current file
727 q = (Byte *) xstrdup((char *) fn); // save the cfn
728 free(cfn); // free the old name
729 cfn = q; // remember new cfn
732 // delete all the contents of text[]
733 new_text(2 * file_size(fn));
734 screenbegin = dot = end = text;
737 ch = file_insert(fn, text, file_size(fn));
740 // start empty buf with dummy line
741 (void) char_insert(text, '\n');
745 last_file_modified = -1;
746 #ifdef CONFIG_FEATURE_VI_YANKMARK
747 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
748 free(reg[Ureg]); // free orig line reg- for 'U'
751 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
752 free(reg[YDreg]); // free default yank/delete register
755 for (li = 0; li < 28; li++) {
758 #endif /* CONFIG_FEATURE_VI_YANKMARK */
759 // how many lines in text[]?
760 li = count_lines(text, end - 1);
762 #ifdef CONFIG_FEATURE_VI_READONLY
764 #endif /* CONFIG_FEATURE_VI_READONLY */
766 (sr < 0 ? " [New file]" : ""),
767 #ifdef CONFIG_FEATURE_VI_READONLY
768 ((vi_readonly || readonly) ? " [Read only]" : ""),
769 #endif /* CONFIG_FEATURE_VI_READONLY */
771 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
772 if (b != -1 || e != -1) {
773 ni((Byte *) "No address allowed on this command");
776 if (strlen((char *) args) > 0) {
777 // user wants a new filename
779 cfn = (Byte *) xstrdup((char *) args);
781 // user wants file status info
782 last_status_cksum = 0; // force status update
784 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
785 // print out values of all features
786 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
787 clear_to_eol(); // clear the line
792 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
793 if (b < 0) { // no addr given- use defaults
794 q = begin_line(dot); // assume .,. for the range
797 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
798 clear_to_eol(); // clear the line
800 for (; q <= r; q++) {
804 c_is_no_print = c > 127 && !Isprint(c);
811 } else if (c < ' ' || c == 127) {
822 #ifdef CONFIG_FEATURE_VI_SET
824 #endif /* CONFIG_FEATURE_VI_SET */
826 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
827 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
829 // force end of argv list
836 // don't exit if the file been modified
838 psbs("No write since last change (:%s! overrides)",
839 (*cmd == 'q' ? "quit" : "next"));
842 // are there other file to edit
843 if (*cmd == 'q' && optind < save_argc - 1) {
844 psbs("%d more file to edit", (save_argc - optind - 1));
847 if (*cmd == 'n' && optind >= save_argc - 1) {
848 psbs("No more files to edit");
852 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
854 if (strlen((char *) fn) <= 0) {
855 psbs("No filename given");
858 if (b < 0) { // no addr given- use defaults
859 q = begin_line(dot); // assume "dot"
861 // read after current line- unless user said ":0r foo"
864 #ifdef CONFIG_FEATURE_VI_READONLY
865 l= readonly; // remember current files' status
867 ch = file_insert(fn, q, file_size(fn));
868 #ifdef CONFIG_FEATURE_VI_READONLY
872 goto vc1; // nothing was inserted
873 // how many lines in text[]?
874 li = count_lines(q, q + ch - 1);
876 #ifdef CONFIG_FEATURE_VI_READONLY
878 #endif /* CONFIG_FEATURE_VI_READONLY */
880 #ifdef CONFIG_FEATURE_VI_READONLY
881 ((vi_readonly || readonly) ? " [Read only]" : ""),
882 #endif /* CONFIG_FEATURE_VI_READONLY */
885 // if the insert is before "dot" then we need to update
890 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
891 if (file_modified && ! useforce) {
892 psbs("No write since last change (:rewind! overrides)");
894 // reset the filenames to edit
895 optind = fn_start - 1;
898 #ifdef CONFIG_FEATURE_VI_SET
899 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
900 i = 0; // offset into args
901 if (strlen((char *) args) == 0) {
902 // print out values of all options
903 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
904 clear_to_eol(); // clear the line
905 printf("----------------------------------------\r\n");
906 #ifdef CONFIG_FEATURE_VI_SETOPTS
909 printf("autoindent ");
915 printf("ignorecase ");
918 printf("showmatch ");
919 printf("tabstop=%d ", tabstop);
920 #endif /* CONFIG_FEATURE_VI_SETOPTS */
924 if (strncasecmp((char *) args, "no", 2) == 0)
925 i = 2; // ":set noautoindent"
926 #ifdef CONFIG_FEATURE_VI_SETOPTS
927 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
928 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
929 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
930 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
931 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
932 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
933 if (ch > 0 && ch < columns - 1)
936 #endif /* CONFIG_FEATURE_VI_SETOPTS */
937 #endif /* CONFIG_FEATURE_VI_SET */
938 #ifdef CONFIG_FEATURE_VI_SEARCH
939 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
943 // F points to the "find" pattern
944 // R points to the "replace" pattern
945 // replace the cmd line delimiters "/" with NULLs
946 gflag = 0; // global replace flag
947 c = orig_buf[1]; // what is the delimiter
948 F = orig_buf + 2; // start of "find"
949 R = (Byte *) strchr((char *) F, c); // middle delimiter
950 if (!R) goto colon_s_fail;
951 *R++ = '\0'; // terminate "find"
952 buf1 = (Byte *) strchr((char *) R, c);
953 if (!buf1) goto colon_s_fail;
954 *buf1++ = '\0'; // terminate "replace"
955 if (*buf1 == 'g') { // :s/foo/bar/g
957 gflag++; // turn on gflag
960 if (b < 0) { // maybe :s/foo/bar/
961 q = begin_line(dot); // start with cur line
962 b = count_lines(text, q); // cur line number
965 e = b; // maybe :.s/foo/bar/
966 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
967 ls = q; // orig line start
969 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
971 // we found the "find" pattern- delete it
972 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
973 // inset the "replace" patern
974 (void) string_insert(buf1, R); // insert the string
975 // check for "global" :s/foo/bar/g
977 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
978 q = buf1 + strlen((char *) R);
979 goto vc4; // don't let q move past cur line
985 #endif /* CONFIG_FEATURE_VI_SEARCH */
986 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
987 psb("%s", BB_VER " " BB_BT);
988 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
989 || strncasecmp((char *) cmd, "wq", i) == 0
990 || strncasecmp((char *) cmd, "wn", i) == 0
991 || strncasecmp((char *) cmd, "x", i) == 0) {
992 // is there a file name to write to?
993 if (strlen((char *) args) > 0) {
996 #ifdef CONFIG_FEATURE_VI_READONLY
997 if ((vi_readonly || readonly) && ! useforce) {
998 psbs("\"%s\" File is read only", fn);
1001 #endif /* CONFIG_FEATURE_VI_READONLY */
1002 // how many lines in text[]?
1003 li = count_lines(q, r);
1005 // see if file exists- if not, its just a new file request
1007 // if "fn" is not write-able, chmod u+w
1008 // sprintf(syscmd, "chmod u+w %s", fn);
1012 l = file_write(fn, q, r);
1013 if (useforce && forced) {
1015 // sprintf(syscmd, "chmod u-w %s", fn);
1021 psbs("Write error: %s", strerror(errno));
1023 psb("\"%s\" %dL, %dC", fn, li, l);
1024 if (q == text && r == end - 1 && l == ch) {
1026 last_file_modified = -1;
1028 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1029 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1034 #ifdef CONFIG_FEATURE_VI_READONLY
1036 #endif /* CONFIG_FEATURE_VI_READONLY */
1037 #ifdef CONFIG_FEATURE_VI_YANKMARK
1038 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1039 if (b < 0) { // no addr given- use defaults
1040 q = begin_line(dot); // assume .,. for the range
1043 text_yank(q, r, YDreg);
1044 li = count_lines(q, r);
1045 psb("Yank %d lines (%d chars) into [%c]",
1046 li, strlen((char *) reg[YDreg]), what_reg());
1047 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1053 dot = bound_dot(dot); // make sure "dot" is valid
1055 #ifdef CONFIG_FEATURE_VI_SEARCH
1057 psb(":s expression missing delimiters");
1061 #endif /* CONFIG_FEATURE_VI_COLON */
1063 static void Hit_Return(void)
1067 standout_start(); // start reverse video
1068 write1("[Hit return to continue]");
1069 standout_end(); // end reverse video
1070 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1072 redraw(TRUE); // force redraw all
1075 //----- Synchronize the cursor to Dot --------------------------
1076 static void sync_cursor(Byte * d, int *row, int *col)
1078 Byte *beg_cur; // begin and end of "d" line
1079 Byte *end_scr; // begin and end of screen
1083 beg_cur = begin_line(d); // first char of cur line
1085 end_scr = end_screen(); // last char of screen
1087 if (beg_cur < screenbegin) {
1088 // "d" is before top line on screen
1089 // how many lines do we have to move
1090 cnt = count_lines(beg_cur, screenbegin);
1092 screenbegin = beg_cur;
1093 if (cnt > (rows - 1) / 2) {
1094 // we moved too many lines. put "dot" in middle of screen
1095 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1096 screenbegin = prev_line(screenbegin);
1099 } else if (beg_cur > end_scr) {
1100 // "d" is after bottom line on screen
1101 // how many lines do we have to move
1102 cnt = count_lines(end_scr, beg_cur);
1103 if (cnt > (rows - 1) / 2)
1104 goto sc1; // too many lines
1105 for (ro = 0; ro < cnt - 1; ro++) {
1106 // move screen begin the same amount
1107 screenbegin = next_line(screenbegin);
1108 // now, move the end of screen
1109 end_scr = next_line(end_scr);
1110 end_scr = end_line(end_scr);
1113 // "d" is on screen- find out which row
1115 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1121 // find out what col "d" is on
1123 do { // drive "co" to correct column
1124 if (*tp == '\n' || *tp == '\0')
1128 co += ((tabstop - 1) - (co % tabstop));
1129 } else if (*tp < ' ' || *tp == 127) {
1130 co++; // display as ^X, use 2 columns
1132 } while (tp++ < d && ++co);
1134 // "co" is the column where "dot" is.
1135 // The screen has "columns" columns.
1136 // The currently displayed columns are 0+offset -- columns+ofset
1137 // |-------------------------------------------------------------|
1139 // offset | |------- columns ----------------|
1141 // If "co" is already in this range then we do not have to adjust offset
1142 // but, we do have to subtract the "offset" bias from "co".
1143 // If "co" is outside this range then we have to change "offset".
1144 // If the first char of a line is a tab the cursor will try to stay
1145 // in column 7, but we have to set offset to 0.
1147 if (co < 0 + offset) {
1150 if (co >= columns + offset) {
1151 offset = co - columns + 1;
1153 // if the first char of the line is a tab, and "dot" is sitting on it
1154 // force offset to 0.
1155 if (d == beg_cur && *d == '\t') {
1164 //----- Text Movement Routines ---------------------------------
1165 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1167 while (p > text && p[-1] != '\n')
1168 p--; // go to cur line B-o-l
1172 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1174 while (p < end - 1 && *p != '\n')
1175 p++; // go to cur line E-o-l
1179 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1181 while (p < end - 1 && *p != '\n')
1182 p++; // go to cur line E-o-l
1183 // Try to stay off of the Newline
1184 if (*p == '\n' && (p - begin_line(p)) > 0)
1189 static Byte *prev_line(Byte * p) // return pointer first char prev line
1191 p = begin_line(p); // goto begining of cur line
1192 if (p[-1] == '\n' && p > text)
1193 p--; // step to prev line
1194 p = begin_line(p); // goto begining of prev line
1198 static Byte *next_line(Byte * p) // return pointer first char next line
1201 if (*p == '\n' && p < end - 1)
1202 p++; // step to next line
1206 //----- Text Information Routines ------------------------------
1207 static Byte *end_screen(void)
1212 // find new bottom line
1214 for (cnt = 0; cnt < rows - 2; cnt++)
1220 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1225 if (stop < start) { // start and stop are backwards- reverse them
1231 stop = end_line(stop); // get to end of this line
1232 for (q = start; q <= stop && q <= end - 1; q++) {
1239 static Byte *find_line(int li) // find begining of line #li
1243 for (q = text; li > 1; li--) {
1249 //----- Dot Movement Routines ----------------------------------
1250 static void dot_left(void)
1252 if (dot > text && dot[-1] != '\n')
1256 static void dot_right(void)
1258 if (dot < end - 1 && *dot != '\n')
1262 static void dot_begin(void)
1264 dot = begin_line(dot); // return pointer to first char cur line
1267 static void dot_end(void)
1269 dot = end_line(dot); // return pointer to last char cur line
1272 static Byte *move_to_col(Byte * p, int l)
1279 if (*p == '\n' || *p == '\0')
1283 co += ((tabstop - 1) - (co % tabstop));
1284 } else if (*p < ' ' || *p == 127) {
1285 co++; // display as ^X, use 2 columns
1287 } while (++co <= l && p++ < end);
1291 static void dot_next(void)
1293 dot = next_line(dot);
1296 static void dot_prev(void)
1298 dot = prev_line(dot);
1301 static void dot_scroll(int cnt, int dir)
1305 for (; cnt > 0; cnt--) {
1308 // ctrl-Y scroll up one line
1309 screenbegin = prev_line(screenbegin);
1312 // ctrl-E scroll down one line
1313 screenbegin = next_line(screenbegin);
1316 // make sure "dot" stays on the screen so we dont scroll off
1317 if (dot < screenbegin)
1319 q = end_screen(); // find new bottom line
1321 dot = begin_line(q); // is dot is below bottom line?
1325 static void dot_skip_over_ws(void)
1328 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1332 static void dot_delete(void) // delete the char at 'dot'
1334 (void) text_hole_delete(dot, dot);
1337 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1339 if (p >= end && end > text) {
1341 indicate_error('1');
1345 indicate_error('2');
1350 //----- Helper Utility Routines --------------------------------
1352 //----------------------------------------------------------------
1353 //----- Char Routines --------------------------------------------
1354 /* Chars that are part of a word-
1355 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1356 * Chars that are Not part of a word (stoppers)
1357 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1358 * Chars that are WhiteSpace
1359 * TAB NEWLINE VT FF RETURN SPACE
1360 * DO NOT COUNT NEWLINE AS WHITESPACE
1363 static Byte *new_screen(int ro, int co)
1368 screensize = ro * co + 8;
1369 screen = (Byte *) xmalloc(screensize);
1370 // initialize the new screen. assume this will be a empty file.
1372 // non-existent text[] lines start with a tilde (~).
1373 for (li = 1; li < ro - 1; li++) {
1374 screen[(li * co) + 0] = '~';
1379 static Byte *new_text(int size)
1382 size = 10240; // have a minimum size for new files
1384 text = (Byte *) xmalloc(size + 8);
1385 memset(text, '\0', size); // clear new text[]
1386 //text += 4; // leave some room for "oops"
1390 #ifdef CONFIG_FEATURE_VI_SEARCH
1391 static int mycmp(Byte * s1, Byte * s2, int len)
1395 i = strncmp((char *) s1, (char *) s2, len);
1396 #ifdef CONFIG_FEATURE_VI_SETOPTS
1398 i = strncasecmp((char *) s1, (char *) s2, len);
1400 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1404 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1406 #ifndef REGEX_SEARCH
1410 len = strlen((char *) pat);
1411 if (dir == FORWARD) {
1412 stop = end - 1; // assume range is p - end-1
1413 if (range == LIMITED)
1414 stop = next_line(p); // range is to next line
1415 for (start = p; start < stop; start++) {
1416 if (mycmp(start, pat, len) == 0) {
1420 } else if (dir == BACK) {
1421 stop = text; // assume range is text - p
1422 if (range == LIMITED)
1423 stop = prev_line(p); // range is to prev line
1424 for (start = p - len; start >= stop; start--) {
1425 if (mycmp(start, pat, len) == 0) {
1430 // pattern not found
1432 #else /*REGEX_SEARCH */
1434 struct re_pattern_buffer preg;
1438 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1444 // assume a LIMITED forward search
1452 // count the number of chars to search over, forward or backward
1456 // RANGE could be negative if we are searching backwards
1459 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1461 // The pattern was not compiled
1462 psbs("bad search pattern: \"%s\": %s", pat, q);
1463 i = 0; // return p if pattern not compiled
1473 // search for the compiled pattern, preg, in p[]
1474 // range < 0- search backward
1475 // range > 0- search forward
1477 // re_search() < 0 not found or error
1478 // re_search() > 0 index of found pattern
1479 // struct pattern char int int int struct reg
1480 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1481 i = re_search(&preg, q, size, 0, range, 0);
1484 i = 0; // return NULL if pattern not found
1487 if (dir == FORWARD) {
1493 #endif /*REGEX_SEARCH */
1495 #endif /* CONFIG_FEATURE_VI_SEARCH */
1497 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1499 if (c == 22) { // Is this an ctrl-V?
1500 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1501 p--; // backup onto ^
1502 refresh(FALSE); // show the ^
1506 file_modified++; // has the file been modified
1507 } else if (c == 27) { // Is this an ESC?
1510 end_cmd_q(); // stop adding to q
1511 last_status_cksum = 0; // force status update
1512 if ((p[-1] != '\n') && (dot>text)) {
1515 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1517 if ((p[-1] != '\n') && (dot>text)) {
1519 p = text_hole_delete(p, p); // shrink buffer 1 char
1522 // insert a char into text[]
1523 Byte *sp; // "save p"
1526 c = '\n'; // translate \r to \n
1527 sp = p; // remember addr of insert
1528 p = stupid_insert(p, c); // insert the char
1529 #ifdef CONFIG_FEATURE_VI_SETOPTS
1530 if (showmatch && strchr(")]}", *sp) != NULL) {
1533 if (autoindent && c == '\n') { // auto indent the new line
1536 q = prev_line(p); // use prev line as templet
1537 for (; isblnk(*q); q++) {
1538 p = stupid_insert(p, *q); // insert the char
1541 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1546 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1548 p = text_hole_make(p, 1);
1551 file_modified++; // has the file been modified
1557 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1559 Byte *save_dot, *p, *q;
1565 if (strchr("cdy><", c)) {
1566 // these cmds operate on whole lines
1567 p = q = begin_line(p);
1568 for (cnt = 1; cnt < cmdcnt; cnt++) {
1572 } else if (strchr("^%$0bBeEft", c)) {
1573 // These cmds operate on char positions
1574 do_cmd(c); // execute movement cmd
1576 } else if (strchr("wW", c)) {
1577 do_cmd(c); // execute movement cmd
1578 // if we are at the next word's first char
1579 // step back one char
1580 // but check the possibilities when it is true
1581 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1582 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1583 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1584 dot--; // move back off of next word
1585 if (dot > text && *dot == '\n')
1586 dot--; // stay off NL
1588 } else if (strchr("H-k{", c)) {
1589 // these operate on multi-lines backwards
1590 q = end_line(dot); // find NL
1591 do_cmd(c); // execute movement cmd
1594 } else if (strchr("L+j}\r\n", c)) {
1595 // these operate on multi-lines forwards
1596 p = begin_line(dot);
1597 do_cmd(c); // execute movement cmd
1598 dot_end(); // find NL
1601 c = 27; // error- return an ESC char
1614 static int st_test(Byte * p, int type, int dir, Byte * tested)
1624 if (type == S_BEFORE_WS) {
1626 test = ((!isspace(c)) || c == '\n');
1628 if (type == S_TO_WS) {
1630 test = ((!isspace(c)) || c == '\n');
1632 if (type == S_OVER_WS) {
1634 test = ((isspace(c)));
1636 if (type == S_END_PUNCT) {
1638 test = ((ispunct(c)));
1640 if (type == S_END_ALNUM) {
1642 test = ((isalnum(c)) || c == '_');
1648 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1652 while (st_test(p, type, dir, &c)) {
1653 // make sure we limit search to correct number of lines
1654 if (c == '\n' && --linecnt < 1)
1656 if (dir >= 0 && p >= end - 1)
1658 if (dir < 0 && p <= text)
1660 p += dir; // move to next char
1665 // find matching char of pair () [] {}
1666 static Byte *find_pair(Byte * p, Byte c)
1673 dir = 1; // assume forward
1697 for (q = p + dir; text <= q && q < end; q += dir) {
1698 // look for match, count levels of pairs (( ))
1700 level++; // increase pair levels
1702 level--; // reduce pair level
1704 break; // found matching pair
1707 q = NULL; // indicate no match
1711 #ifdef CONFIG_FEATURE_VI_SETOPTS
1712 // show the matching char of a pair, () [] {}
1713 static void showmatching(Byte * p)
1717 // we found half of a pair
1718 q = find_pair(p, *p); // get loc of matching char
1720 indicate_error('3'); // no matching char
1722 // "q" now points to matching pair
1723 save_dot = dot; // remember where we are
1724 dot = q; // go to new loc
1725 refresh(FALSE); // let the user see it
1726 (void) mysleep(40); // give user some time
1727 dot = save_dot; // go back to old loc
1731 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1733 // open a hole in text[]
1734 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1743 cnt = end - src; // the rest of buffer
1744 if (memmove(dest, src, cnt) != dest) {
1745 psbs("can't create room for new characters");
1747 memset(p, ' ', size); // clear new hole
1748 end = end + size; // adjust the new END
1749 file_modified++; // has the file been modified
1754 // close a hole in text[]
1755 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1760 // move forwards, from beginning
1764 if (q < p) { // they are backward- swap them
1768 hole_size = q - p + 1;
1770 if (src < text || src > end)
1772 if (dest < text || dest >= end)
1775 goto thd_atend; // just delete the end of the buffer
1776 if (memmove(dest, src, cnt) != dest) {
1777 psbs("can't delete the character");
1780 end = end - hole_size; // adjust the new END
1782 dest = end - 1; // make sure dest in below end-1
1784 dest = end = text; // keep pointers valid
1785 file_modified++; // has the file been modified
1790 // copy text into register, then delete text.
1791 // if dist <= 0, do not include, or go past, a NewLine
1793 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1797 // make sure start <= stop
1799 // they are backwards, reverse them
1805 // we cannot cross NL boundaries
1809 // dont go past a NewLine
1810 for (; p + 1 <= stop; p++) {
1812 stop = p; // "stop" just before NewLine
1818 #ifdef CONFIG_FEATURE_VI_YANKMARK
1819 text_yank(start, stop, YDreg);
1820 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1821 if (yf == YANKDEL) {
1822 p = text_hole_delete(start, stop);
1827 static void show_help(void)
1829 puts("These features are available:"
1830 #ifdef CONFIG_FEATURE_VI_SEARCH
1831 "\n\tPattern searches with / and ?"
1832 #endif /* CONFIG_FEATURE_VI_SEARCH */
1833 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1834 "\n\tLast command repeat with \'.\'"
1835 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1836 #ifdef CONFIG_FEATURE_VI_YANKMARK
1837 "\n\tLine marking with 'x"
1838 "\n\tNamed buffers with \"x"
1839 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1840 #ifdef CONFIG_FEATURE_VI_READONLY
1841 "\n\tReadonly if vi is called as \"view\""
1842 "\n\tReadonly with -R command line arg"
1843 #endif /* CONFIG_FEATURE_VI_READONLY */
1844 #ifdef CONFIG_FEATURE_VI_SET
1845 "\n\tSome colon mode commands with \':\'"
1846 #endif /* CONFIG_FEATURE_VI_SET */
1847 #ifdef CONFIG_FEATURE_VI_SETOPTS
1848 "\n\tSettable options with \":set\""
1849 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1850 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1851 "\n\tSignal catching- ^C"
1852 "\n\tJob suspend and resume with ^Z"
1853 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1854 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1855 "\n\tAdapt to window re-sizes"
1856 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1860 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1865 strcpy((char *) buf, ""); // init buf
1866 if (strlen((char *) s) <= 0)
1867 s = (Byte *) "(NULL)";
1868 for (; *s > '\0'; s++) {
1872 c_is_no_print = c > 127 && !Isprint(c);
1873 if (c_is_no_print) {
1874 strcat((char *) buf, SOn);
1877 if (c < ' ' || c == 127) {
1878 strcat((char *) buf, "^");
1885 strcat((char *) buf, (char *) b);
1887 strcat((char *) buf, SOs);
1889 strcat((char *) buf, "$");
1894 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1895 static void start_new_cmd_q(Byte c)
1898 free(last_modifying_cmd);
1899 // get buffer for new cmd
1900 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1901 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1902 // if there is a current cmd count put it in the buffer first
1904 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1905 else // just save char c onto queue
1906 last_modifying_cmd[0] = c;
1910 static void end_cmd_q(void)
1912 #ifdef CONFIG_FEATURE_VI_YANKMARK
1913 YDreg = 26; // go back to default Yank/Delete reg
1914 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1918 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1920 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1921 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1925 i = strlen((char *) s);
1926 p = text_hole_make(p, i);
1927 strncpy((char *) p, (char *) s, i);
1928 for (cnt = 0; *s != '\0'; s++) {
1932 #ifdef CONFIG_FEATURE_VI_YANKMARK
1933 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1934 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1937 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1939 #ifdef CONFIG_FEATURE_VI_YANKMARK
1940 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1945 if (q < p) { // they are backwards- reverse them
1952 free(t); // if already a yank register, free it
1953 t = (Byte *) xmalloc(cnt + 1); // get a new register
1954 memset(t, '\0', cnt + 1); // clear new text[]
1955 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1960 static Byte what_reg(void)
1964 c = 'D'; // default to D-reg
1965 if (0 <= YDreg && YDreg <= 25)
1966 c = 'a' + (Byte) YDreg;
1974 static void check_context(Byte cmd)
1976 // A context is defined to be "modifying text"
1977 // Any modifying command establishes a new context.
1979 if (dot < context_start || dot > context_end) {
1980 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1981 // we are trying to modify text[]- make this the current context
1982 mark[27] = mark[26]; // move cur to prev
1983 mark[26] = dot; // move local to cur
1984 context_start = prev_line(prev_line(dot));
1985 context_end = next_line(next_line(dot));
1986 //loiter= start_loiter= now;
1992 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
1996 // the current context is in mark[26]
1997 // the previous context is in mark[27]
1998 // only swap context if other context is valid
1999 if (text <= mark[27] && mark[27] <= end - 1) {
2001 mark[27] = mark[26];
2003 p = mark[26]; // where we are going- previous context
2004 context_start = prev_line(prev_line(prev_line(p)));
2005 context_end = next_line(next_line(next_line(p)));
2009 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2011 static int isblnk(Byte c) // is the char a blank or tab
2013 return (c == ' ' || c == '\t');
2016 //----- Set terminal attributes --------------------------------
2017 static void rawmode(void)
2019 tcgetattr(0, &term_orig);
2020 term_vi = term_orig;
2021 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2022 term_vi.c_iflag &= (~IXON & ~ICRNL);
2023 term_vi.c_oflag &= (~ONLCR);
2024 term_vi.c_cc[VMIN] = 1;
2025 term_vi.c_cc[VTIME] = 0;
2026 erase_char = term_vi.c_cc[VERASE];
2027 tcsetattr(0, TCSANOW, &term_vi);
2030 static void cookmode(void)
2033 tcsetattr(0, TCSANOW, &term_orig);
2036 //----- Come here when we get a window resize signal ---------
2037 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2038 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2040 signal(SIGWINCH, winch_sig);
2041 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2042 get_terminal_width_height(0, &columns, &rows);
2043 new_screen(rows, columns); // get memory for virtual screen
2044 redraw(TRUE); // re-draw the screen
2047 //----- Come here when we get a continue signal -------------------
2048 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2050 rawmode(); // terminal to "raw"
2051 last_status_cksum = 0; // force status update
2052 redraw(TRUE); // re-draw the screen
2054 signal(SIGTSTP, suspend_sig);
2055 signal(SIGCONT, SIG_DFL);
2056 kill(my_pid, SIGCONT);
2059 //----- Come here when we get a Suspend signal -------------------
2060 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2062 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2063 clear_to_eol(); // Erase to end of line
2064 cookmode(); // terminal to "cooked"
2066 signal(SIGCONT, cont_sig);
2067 signal(SIGTSTP, SIG_DFL);
2068 kill(my_pid, SIGTSTP);
2071 //----- Come here when we get a signal ---------------------------
2072 static void catch_sig(int sig)
2074 signal(SIGINT, catch_sig);
2076 longjmp(restart, sig);
2078 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2080 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2082 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2087 tv.tv_usec = hund * 10000;
2088 select(1, &rfds, NULL, NULL, &tv);
2089 return (FD_ISSET(0, &rfds));
2092 #define readbuffer bb_common_bufsiz1
2094 static int readed_for_parse;
2096 //----- IO Routines --------------------------------------------
2097 static Byte readit(void) // read (maybe cursor) key from stdin
2106 static const struct esc_cmds esccmds[] = {
2107 {"OA", (Byte) VI_K_UP}, // cursor key Up
2108 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2109 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2110 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2111 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2112 {"OF", (Byte) VI_K_END}, // Cursor Key End
2113 {"[A", (Byte) VI_K_UP}, // cursor key Up
2114 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2115 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2116 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2117 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2118 {"[F", (Byte) VI_K_END}, // Cursor Key End
2119 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2120 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2121 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2122 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2123 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2124 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2125 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2126 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2127 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2128 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2129 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2130 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2131 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2132 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2133 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2134 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2135 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2136 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2137 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2138 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2139 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2142 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2144 (void) alarm(0); // turn alarm OFF while we wait for input
2146 n = readed_for_parse;
2147 // get input from User- are there already input chars in Q?
2150 // the Q is empty, wait for a typed char
2151 n = read(0, readbuffer, BUFSIZ - 1);
2154 goto ri0; // interrupted sys call
2157 if (errno == EFAULT)
2159 if (errno == EINVAL)
2167 if (readbuffer[0] == 27) {
2168 // This is an ESC char. Is this Esc sequence?
2169 // Could be bare Esc key. See if there are any
2170 // more chars to read after the ESC. This would
2171 // be a Function or Cursor Key sequence.
2175 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2177 // keep reading while there are input chars and room in buffer
2178 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2179 // read the rest of the ESC string
2180 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2186 readed_for_parse = n;
2189 if(c == 27 && n > 1) {
2190 // Maybe cursor or function key?
2191 const struct esc_cmds *eindex;
2193 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2194 int cnt = strlen(eindex->seq);
2198 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2200 // is a Cursor key- put derived value back into Q
2202 // for squeeze out the ESC sequence
2206 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2207 /* defined ESC sequence not found, set only one ESC */
2213 // remove key sequence from Q
2214 readed_for_parse -= n;
2215 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2216 (void) alarm(3); // we are done waiting for input, turn alarm ON
2220 //----- IO Routines --------------------------------------------
2221 static Byte get_one_char(void)
2225 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2226 // ! adding2q && ioq == 0 read()
2227 // ! adding2q && ioq != 0 *ioq
2228 // adding2q *last_modifying_cmd= read()
2230 // we are not adding to the q.
2231 // but, we may be reading from a q
2233 // there is no current q, read from STDIN
2234 c = readit(); // get the users input
2236 // there is a queue to get chars from first
2239 // the end of the q, read from STDIN
2241 ioq_start = ioq = 0;
2242 c = readit(); // get the users input
2246 // adding STDIN chars to q
2247 c = readit(); // get the users input
2248 if (last_modifying_cmd != 0) {
2249 int len = strlen((char *) last_modifying_cmd);
2250 if (len + 1 >= BUFSIZ) {
2251 psbs("last_modifying_cmd overrun");
2253 // add new char to q
2254 last_modifying_cmd[len] = c;
2258 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2259 c = readit(); // get the users input
2260 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2261 return (c); // return the char, where ever it came from
2264 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2269 static Byte *obufp = NULL;
2271 strcpy((char *) buf, (char *) prompt);
2272 last_status_cksum = 0; // force status update
2273 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2274 clear_to_eol(); // clear the line
2275 write1((char *) prompt); // write out the :, /, or ? prompt
2277 for (i = strlen((char *) buf); i < BUFSIZ;) {
2278 c = get_one_char(); // read user input
2279 if (c == '\n' || c == '\r' || c == 27)
2280 break; // is this end of input
2281 if (c == erase_char || c == 8 || c == 127) {
2282 // user wants to erase prev char
2283 i--; // backup to prev char
2284 buf[i] = '\0'; // erase the char
2285 buf[i + 1] = '\0'; // null terminate buffer
2286 write1("\b \b"); // erase char on screen
2287 if (i <= 0) { // user backs up before b-o-l, exit
2291 buf[i] = c; // save char in buffer
2292 buf[i + 1] = '\0'; // make sure buffer is null terminated
2293 putchar(c); // echo the char back to user
2299 obufp = (Byte *) xstrdup((char *) buf);
2303 static int file_size(const Byte * fn) // what is the byte size of "fn"
2308 if (fn == 0 || strlen((char *)fn) <= 0)
2311 sr = stat((char *) fn, &st_buf); // see if file exists
2313 cnt = (int) st_buf.st_size;
2318 static int file_insert(Byte * fn, Byte * p, int size)
2323 #ifdef CONFIG_FEATURE_VI_READONLY
2325 #endif /* CONFIG_FEATURE_VI_READONLY */
2326 if (fn == 0 || strlen((char*) fn) <= 0) {
2327 psbs("No filename given");
2331 // OK- this is just a no-op
2336 psbs("Trying to insert a negative number (%d) of characters", size);
2339 if (p < text || p > end) {
2340 psbs("Trying to insert file outside of memory");
2344 // see if we can open the file
2345 #ifdef CONFIG_FEATURE_VI_READONLY
2346 if (vi_readonly) goto fi1; // do not try write-mode
2348 fd = open((char *) fn, O_RDWR); // assume read & write
2350 // could not open for writing- maybe file is read only
2351 #ifdef CONFIG_FEATURE_VI_READONLY
2354 fd = open((char *) fn, O_RDONLY); // try read-only
2356 psbs("\"%s\" %s", fn, "cannot open file");
2359 #ifdef CONFIG_FEATURE_VI_READONLY
2360 // got the file- read-only
2362 #endif /* CONFIG_FEATURE_VI_READONLY */
2364 p = text_hole_make(p, size);
2365 cnt = read(fd, p, size);
2369 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2370 psbs("cannot read file \"%s\"", fn);
2371 } else if (cnt < size) {
2372 // There was a partial read, shrink unused space text[]
2373 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2374 psbs("cannot read all of file \"%s\"", fn);
2382 static int file_write(Byte * fn, Byte * first, Byte * last)
2384 int fd, cnt, charcnt;
2387 psbs("No current filename");
2391 // FIXIT- use the correct umask()
2392 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2395 cnt = last - first + 1;
2396 charcnt = write(fd, first, cnt);
2397 if (charcnt == cnt) {
2399 //file_modified= FALSE; // the file has not been modified
2407 //----- Terminal Drawing ---------------------------------------
2408 // The terminal is made up of 'rows' line of 'columns' columns.
2409 // classically this would be 24 x 80.
2410 // screen coordinates
2416 // 23,0 ... 23,79 status line
2419 //----- Move the cursor to row x col (count from 0, not 1) -------
2420 static void place_cursor(int row, int col, int opti)
2424 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2427 // char cm3[BUFSIZ];
2429 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2431 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2433 if (row < 0) row = 0;
2434 if (row >= rows) row = rows - 1;
2435 if (col < 0) col = 0;
2436 if (col >= columns) col = columns - 1;
2438 //----- 1. Try the standard terminal ESC sequence
2439 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2441 if (! opti) goto pc0;
2443 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2444 //----- find the minimum # of chars to move cursor -------------
2445 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2446 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2448 // move to the correct row
2449 while (row < Rrow) {
2450 // the cursor has to move up
2454 while (row > Rrow) {
2455 // the cursor has to move down
2456 strcat(cm2, CMdown);
2460 // now move to the correct column
2461 strcat(cm2, "\r"); // start at col 0
2462 // just send out orignal source char to get to correct place
2463 screenp = &screen[row * columns]; // start of screen line
2464 strncat(cm2, (char* )screenp, col);
2466 //----- 3. Try some other way of moving cursor
2467 //---------------------------------------------
2469 // pick the shortest cursor motion to send out
2471 if (strlen(cm2) < strlen(cm)) {
2473 } /* else if (strlen(cm3) < strlen(cm)) {
2476 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2478 write1(cm); // move the cursor
2481 //----- Erase from cursor to end of line -----------------------
2482 static void clear_to_eol(void)
2484 write1(Ceol); // Erase from cursor to end of line
2487 //----- Erase from cursor to end of screen -----------------------
2488 static void clear_to_eos(void)
2490 write1(Ceos); // Erase from cursor to end of screen
2493 //----- Start standout mode ------------------------------------
2494 static void standout_start(void) // send "start reverse video" sequence
2496 write1(SOs); // Start reverse video mode
2499 //----- End standout mode --------------------------------------
2500 static void standout_end(void) // send "end reverse video" sequence
2502 write1(SOn); // End reverse video mode
2505 //----- Flash the screen --------------------------------------
2506 static void flash(int h)
2508 standout_start(); // send "start reverse video" sequence
2511 standout_end(); // send "end reverse video" sequence
2515 static void Indicate_Error(void)
2517 #ifdef CONFIG_FEATURE_VI_CRASHME
2519 return; // generate a random command
2520 #endif /* CONFIG_FEATURE_VI_CRASHME */
2522 write1(bell); // send out a bell character
2528 //----- Screen[] Routines --------------------------------------
2529 //----- Erase the Screen[] memory ------------------------------
2530 static void screen_erase(void)
2532 memset(screen, ' ', screensize); // clear new screen
2535 static int bufsum(unsigned char *buf, int count)
2538 unsigned char *e = buf + count;
2544 //----- Draw the status line at bottom of the screen -------------
2545 static void show_status_line(void)
2547 int cnt = 0, cksum = 0;
2549 // either we already have an error or status message, or we
2551 if (!have_status_msg) {
2552 cnt = format_edit_status();
2553 cksum = bufsum(status_buffer, cnt);
2555 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2556 last_status_cksum= cksum; // remember if we have seen this line
2557 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2558 write1((char*)status_buffer);
2560 if (have_status_msg) {
2561 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2563 have_status_msg = 0;
2566 have_status_msg = 0;
2568 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2573 //----- format the status buffer, the bottom line of screen ------
2574 // format status buffer, with STANDOUT mode
2575 static void psbs(const char *format, ...)
2579 va_start(args, format);
2580 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2581 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2582 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2585 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2590 // format status buffer
2591 static void psb(const char *format, ...)
2595 va_start(args, format);
2596 vsprintf((char *) status_buffer, format, args);
2599 have_status_msg = 1;
2604 static void ni(Byte * s) // display messages
2608 print_literal(buf, s);
2609 psbs("\'%s\' is not implemented", buf);
2612 static int format_edit_status(void) // show file status on status line
2614 int cur, percent, ret, trunc_at;
2617 // file_modified is now a counter rather than a flag. this
2618 // helps reduce the amount of line counting we need to do.
2619 // (this will cause a mis-reporting of modified status
2620 // once every MAXINT editing operations.)
2622 // it would be nice to do a similar optimization here -- if
2623 // we haven't done a motion that could have changed which line
2624 // we're on, then we shouldn't have to do this count_lines()
2625 cur = count_lines(text, dot);
2627 // reduce counting -- the total lines can't have
2628 // changed if we haven't done any edits.
2629 if (file_modified != last_file_modified) {
2630 tot = cur + count_lines(dot, end - 1) - 1;
2631 last_file_modified = file_modified;
2634 // current line percent
2635 // ------------- ~~ ----------
2638 percent = (100 * cur) / tot;
2644 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2645 columns : STATUS_BUFFER_LEN-1;
2647 ret = snprintf((char *) status_buffer, trunc_at+1,
2648 #ifdef CONFIG_FEATURE_VI_READONLY
2649 "%c %s%s%s %d/%d %d%%",
2651 "%c %s%s %d/%d %d%%",
2653 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2654 (cfn != 0 ? (char *) cfn : "No file"),
2655 #ifdef CONFIG_FEATURE_VI_READONLY
2656 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2658 (file_modified ? " [modified]" : ""),
2661 if (ret >= 0 && ret < trunc_at)
2662 return ret; /* it all fit */
2664 return trunc_at; /* had to truncate */
2667 //----- Force refresh of all Lines -----------------------------
2668 static void redraw(int full_screen)
2670 place_cursor(0, 0, FALSE); // put cursor in correct place
2671 clear_to_eos(); // tel terminal to erase display
2672 screen_erase(); // erase the internal screen buffer
2673 last_status_cksum = 0; // force status update
2674 refresh(full_screen); // this will redraw the entire display
2678 //----- Format a text[] line into a buffer ---------------------
2679 static void format_line(Byte *dest, Byte *src, int li)
2684 for (co= 0; co < MAX_SCR_COLS; co++) {
2685 c= ' '; // assume blank
2686 if (li > 0 && co == 0) {
2687 c = '~'; // not first line, assume Tilde
2689 // are there chars in text[] and have we gone past the end
2690 if (text < end && src < end) {
2695 if (c > 127 && !Isprint(c)) {
2698 if (c < ' ' || c == 127) {
2702 for (; (co % tabstop) != (tabstop - 1); co++) {
2710 c += '@'; // make it visible
2713 // the co++ is done here so that the column will
2714 // not be overwritten when we blank-out the rest of line
2721 //----- Refresh the changed screen lines -----------------------
2722 // Copy the source line from text[] into the buffer and note
2723 // if the current screenline is different from the new buffer.
2724 // If they differ then that line needs redrawing on the terminal.
2726 static void refresh(int full_screen)
2728 static int old_offset;
2730 Byte buf[MAX_SCR_COLS];
2731 Byte *tp, *sp; // pointer into text[] and screen[]
2732 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2733 int last_li= -2; // last line that changed- for optimizing cursor movement
2734 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2736 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2737 get_terminal_width_height(0, &columns, &rows);
2738 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2739 tp = screenbegin; // index into text[] of top line
2741 // compare text[] to screen[] and mark screen[] lines that need updating
2742 for (li = 0; li < rows - 1; li++) {
2743 int cs, ce; // column start & end
2744 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2745 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2746 // format current text line into buf
2747 format_line(buf, tp, li);
2749 // skip to the end of the current text[] line
2750 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2752 // see if there are any changes between vitual screen and buf
2753 changed = FALSE; // assume no change
2756 sp = &screen[li * columns]; // start of screen line
2758 // force re-draw of every single column from 0 - columns-1
2761 // compare newly formatted buffer with virtual screen
2762 // look forward for first difference between buf and screen
2763 for ( ; cs <= ce; cs++) {
2764 if (buf[cs + offset] != sp[cs]) {
2765 changed = TRUE; // mark for redraw
2770 // look backward for last difference between buf and screen
2771 for ( ; ce >= cs; ce--) {
2772 if (buf[ce + offset] != sp[ce]) {
2773 changed = TRUE; // mark for redraw
2777 // now, cs is index of first diff, and ce is index of last diff
2779 // if horz offset has changed, force a redraw
2780 if (offset != old_offset) {
2785 // make a sanity check of columns indexes
2787 if (ce > columns-1) ce= columns-1;
2788 if (cs > ce) { cs= 0; ce= columns-1; }
2789 // is there a change between vitual screen and buf
2791 // copy changed part of buffer to virtual screen
2792 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2794 // move cursor to column of first change
2795 if (offset != old_offset) {
2796 // opti_cur_move is still too stupid
2797 // to handle offsets correctly
2798 place_cursor(li, cs, FALSE);
2800 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2801 // if this just the next line
2802 // try to optimize cursor movement
2803 // otherwise, use standard ESC sequence
2804 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2806 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2807 place_cursor(li, cs, FALSE); // use standard ESC sequence
2808 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2811 // write line out to terminal
2814 char *out = (char*)sp+cs;
2821 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2823 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2827 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2828 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2831 place_cursor(crow, ccol, FALSE);
2832 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2834 if (offset != old_offset)
2835 old_offset = offset;
2838 //---------------------------------------------------------------------
2839 //----- the Ascii Chart -----------------------------------------------
2841 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2842 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2843 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2844 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2845 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2846 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2847 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2848 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2849 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2850 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2851 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2852 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2853 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2854 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2855 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2856 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2857 //---------------------------------------------------------------------
2859 //----- Execute a Vi Command -----------------------------------
2860 static void do_cmd(Byte c)
2862 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2863 int cnt, i, j, dir, yf;
2865 c1 = c; // quiet the compiler
2866 cnt = yf = dir = 0; // quiet the compiler
2867 p = q = save_dot = msg = buf; // quiet the compiler
2868 memset(buf, '\0', 9); // clear buf
2872 /* if this is a cursor key, skip these checks */
2885 if (cmd_mode == 2) {
2886 // flip-flop Insert/Replace mode
2887 if (c == VI_K_INSERT) goto dc_i;
2888 // we are 'R'eplacing the current *dot with new char
2890 // don't Replace past E-o-l
2891 cmd_mode = 1; // convert to insert
2893 if (1 <= c || Isprint(c)) {
2895 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2896 dot = char_insert(dot, c); // insert new char
2901 if (cmd_mode == 1) {
2902 // hitting "Insert" twice means "R" replace mode
2903 if (c == VI_K_INSERT) goto dc5;
2904 // insert the char c at "dot"
2905 if (1 <= c || Isprint(c)) {
2906 dot = char_insert(dot, c);
2921 #ifdef CONFIG_FEATURE_VI_CRASHME
2922 case 0x14: // dc4 ctrl-T
2923 crashme = (crashme == 0) ? 1 : 0;
2925 #endif /* CONFIG_FEATURE_VI_CRASHME */
2954 //case 'u': // u- FIXME- there is no undo
2956 default: // unrecognised command
2965 end_cmd_q(); // stop adding to q
2966 case 0x00: // nul- ignore
2968 case 2: // ctrl-B scroll up full screen
2969 case VI_K_PAGEUP: // Cursor Key Page Up
2970 dot_scroll(rows - 2, -1);
2972 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2973 case 0x03: // ctrl-C interrupt
2974 longjmp(restart, 1);
2976 case 26: // ctrl-Z suspend
2977 suspend_sig(SIGTSTP);
2979 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2980 case 4: // ctrl-D scroll down half screen
2981 dot_scroll((rows - 2) / 2, 1);
2983 case 5: // ctrl-E scroll down one line
2986 case 6: // ctrl-F scroll down full screen
2987 case VI_K_PAGEDOWN: // Cursor Key Page Down
2988 dot_scroll(rows - 2, 1);
2990 case 7: // ctrl-G show current status
2991 last_status_cksum = 0; // force status update
2993 case 'h': // h- move left
2994 case VI_K_LEFT: // cursor key Left
2995 case 8: // ctrl-H- move left (This may be ERASE char)
2996 case 127: // DEL- move left (This may be ERASE char)
3002 case 10: // Newline ^J
3003 case 'j': // j- goto next line, same col
3004 case VI_K_DOWN: // cursor key Down
3008 dot_next(); // go to next B-o-l
3009 dot = move_to_col(dot, ccol + offset); // try stay in same col
3011 case 12: // ctrl-L force redraw whole screen
3012 case 18: // ctrl-R force redraw
3013 place_cursor(0, 0, FALSE); // put cursor in correct place
3014 clear_to_eos(); // tel terminal to erase display
3016 screen_erase(); // erase the internal screen buffer
3017 last_status_cksum = 0; // force status update
3018 refresh(TRUE); // this will redraw the entire display
3020 case 13: // Carriage Return ^M
3021 case '+': // +- goto next line
3028 case 21: // ctrl-U scroll up half screen
3029 dot_scroll((rows - 2) / 2, -1);
3031 case 25: // ctrl-Y scroll up one line
3037 cmd_mode = 0; // stop insrting
3039 last_status_cksum = 0; // force status update
3041 case ' ': // move right
3042 case 'l': // move right
3043 case VI_K_RIGHT: // Cursor Key Right
3049 #ifdef CONFIG_FEATURE_VI_YANKMARK
3050 case '"': // "- name a register to use for Delete/Yank
3051 c1 = get_one_char();
3059 case '\'': // '- goto a specific mark
3060 c1 = get_one_char();
3066 if (text <= q && q < end) {
3068 dot_begin(); // go to B-o-l
3071 } else if (c1 == '\'') { // goto previous context
3072 dot = swap_context(dot); // swap current and previous context
3073 dot_begin(); // go to B-o-l
3079 case 'm': // m- Mark a line
3080 // this is really stupid. If there are any inserts or deletes
3081 // between text[0] and dot then this mark will not point to the
3082 // correct location! It could be off by many lines!
3083 // Well..., at least its quick and dirty.
3084 c1 = get_one_char();
3088 // remember the line
3089 mark[(int) c1] = dot;
3094 case 'P': // P- Put register before
3095 case 'p': // p- put register after
3098 psbs("Nothing in register %c", what_reg());
3101 // are we putting whole lines or strings
3102 if (strchr((char *) p, '\n') != NULL) {
3104 dot_begin(); // putting lines- Put above
3107 // are we putting after very last line?
3108 if (end_line(dot) == (end - 1)) {
3109 dot = end; // force dot to end of text[]
3111 dot_next(); // next line, then put before
3116 dot_right(); // move to right, can move to NL
3118 dot = string_insert(dot, p); // insert the string
3119 end_cmd_q(); // stop adding to q
3121 case 'U': // U- Undo; replace current line with original version
3122 if (reg[Ureg] != 0) {
3123 p = begin_line(dot);
3125 p = text_hole_delete(p, q); // delete cur line
3126 p = string_insert(p, reg[Ureg]); // insert orig line
3131 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3132 case '$': // $- goto end of line
3133 case VI_K_END: // Cursor Key End
3137 dot = end_line(dot);
3139 case '%': // %- find matching char of pair () [] {}
3140 for (q = dot; q < end && *q != '\n'; q++) {
3141 if (strchr("()[]{}", *q) != NULL) {
3142 // we found half of a pair
3143 p = find_pair(q, *q);
3155 case 'f': // f- forward to a user specified char
3156 last_forward_char = get_one_char(); // get the search char
3158 // dont separate these two commands. 'f' depends on ';'
3160 //**** fall thru to ... ';'
3161 case ';': // ;- look at rest of line for last forward char
3165 if (last_forward_char == 0) break;
3167 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3170 if (*q == last_forward_char)
3173 case '-': // -- goto prev line
3180 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3181 case '.': // .- repeat the last modifying command
3182 // Stuff the last_modifying_cmd back into stdin
3183 // and let it be re-executed.
3184 if (last_modifying_cmd != 0) {
3185 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3188 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3189 #ifdef CONFIG_FEATURE_VI_SEARCH
3190 case '?': // /- search for a pattern
3191 case '/': // /- search for a pattern
3194 q = get_input_line(buf); // get input line- use "status line"
3195 if (strlen((char *) q) == 1)
3196 goto dc3; // if no pat re-use old pat
3197 if (strlen((char *) q) > 1) { // new pat- save it and find
3198 // there is a new pat
3199 free(last_search_pattern);
3200 last_search_pattern = (Byte *) xstrdup((char *) q);
3201 goto dc3; // now find the pattern
3203 // user changed mind and erased the "/"- do nothing
3205 case 'N': // N- backward search for last pattern
3209 dir = BACK; // assume BACKWARD search
3211 if (last_search_pattern[0] == '?') {
3215 goto dc4; // now search for pattern
3217 case 'n': // n- repeat search for last pattern
3218 // search rest of text[] starting at next char
3219 // if search fails return orignal "p" not the "p+1" address
3224 if (last_search_pattern == 0) {
3225 msg = (Byte *) "No previous regular expression";
3228 if (last_search_pattern[0] == '/') {
3229 dir = FORWARD; // assume FORWARD search
3232 if (last_search_pattern[0] == '?') {
3237 q = char_search(p, last_search_pattern + 1, dir, FULL);
3239 dot = q; // good search, update "dot"
3243 // no pattern found between "dot" and "end"- continue at top
3248 q = char_search(p, last_search_pattern + 1, dir, FULL);
3249 if (q != NULL) { // found something
3250 dot = q; // found new pattern- goto it
3251 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3253 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3256 msg = (Byte *) "Pattern not found";
3259 if (*msg) psbs("%s", msg);
3261 case '{': // {- move backward paragraph
3262 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3263 if (q != NULL) { // found blank line
3264 dot = next_line(q); // move to next blank line
3267 case '}': // }- move forward paragraph
3268 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3269 if (q != NULL) { // found blank line
3270 dot = next_line(q); // move to next blank line
3273 #endif /* CONFIG_FEATURE_VI_SEARCH */
3274 case '0': // 0- goto begining of line
3284 if (c == '0' && cmdcnt < 1) {
3285 dot_begin(); // this was a standalone zero
3287 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3290 case ':': // :- the colon mode commands
3291 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3292 #ifdef CONFIG_FEATURE_VI_COLON
3293 colon(p); // execute the command
3294 #else /* CONFIG_FEATURE_VI_COLON */
3296 p++; // move past the ':'
3297 cnt = strlen((char *) p);
3300 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3301 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3302 if (file_modified && p[1] != '!') {
3303 psbs("No write since last change (:quit! overrides)");
3307 } else if (strncasecmp((char *) p, "write", cnt) == 0
3308 || strncasecmp((char *) p, "wq", cnt) == 0
3309 || strncasecmp((char *) p, "wn", cnt) == 0
3310 || strncasecmp((char *) p, "x", cnt) == 0) {
3311 cnt = file_write(cfn, text, end - 1);
3314 psbs("Write error: %s", strerror(errno));
3317 last_file_modified = -1;
3318 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3319 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3320 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3324 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3325 last_status_cksum = 0; // force status update
3326 } else if (sscanf((char *) p, "%d", &j) > 0) {
3327 dot = find_line(j); // go to line # j
3329 } else { // unrecognised cmd
3332 #endif /* CONFIG_FEATURE_VI_COLON */
3334 case '<': // <- Left shift something
3335 case '>': // >- Right shift something
3336 cnt = count_lines(text, dot); // remember what line we are on
3337 c1 = get_one_char(); // get the type of thing to delete
3338 find_range(&p, &q, c1);
3339 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3342 i = count_lines(p, q); // # of lines we are shifting
3343 for ( ; i > 0; i--, p = next_line(p)) {
3345 // shift left- remove tab or 8 spaces
3347 // shrink buffer 1 char
3348 (void) text_hole_delete(p, p);
3349 } else if (*p == ' ') {
3350 // we should be calculating columns, not just SPACE
3351 for (j = 0; *p == ' ' && j < tabstop; j++) {
3352 (void) text_hole_delete(p, p);
3355 } else if (c == '>') {
3356 // shift right -- add tab or 8 spaces
3357 (void) char_insert(p, '\t');
3360 dot = find_line(cnt); // what line were we on
3362 end_cmd_q(); // stop adding to q
3364 case 'A': // A- append at e-o-l
3365 dot_end(); // go to e-o-l
3366 //**** fall thru to ... 'a'
3367 case 'a': // a- append after current char
3372 case 'B': // B- back a blank-delimited Word
3373 case 'E': // E- end of a blank-delimited word
3374 case 'W': // W- forward a blank-delimited word
3381 if (c == 'W' || isspace(dot[dir])) {
3382 dot = skip_thing(dot, 1, dir, S_TO_WS);
3383 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3386 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3388 case 'C': // C- Change to e-o-l
3389 case 'D': // D- delete to e-o-l
3391 dot = dollar_line(dot); // move to before NL
3392 // copy text into a register and delete
3393 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3395 goto dc_i; // start inserting
3396 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3398 end_cmd_q(); // stop adding to q
3399 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3401 case 'G': // G- goto to a line number (default= E-O-F)
3402 dot = end - 1; // assume E-O-F
3404 dot = find_line(cmdcnt); // what line is #cmdcnt
3408 case 'H': // H- goto top line on screen
3410 if (cmdcnt > (rows - 1)) {
3411 cmdcnt = (rows - 1);
3418 case 'I': // I- insert before first non-blank
3421 //**** fall thru to ... 'i'
3422 case 'i': // i- insert before current char
3423 case VI_K_INSERT: // Cursor Key Insert
3425 cmd_mode = 1; // start insrting
3427 case 'J': // J- join current and next lines together
3431 dot_end(); // move to NL
3432 if (dot < end - 1) { // make sure not last char in text[]
3433 *dot++ = ' '; // replace NL with space
3435 while (isblnk(*dot)) { // delete leading WS
3439 end_cmd_q(); // stop adding to q
3441 case 'L': // L- goto bottom line on screen
3443 if (cmdcnt > (rows - 1)) {
3444 cmdcnt = (rows - 1);
3452 case 'M': // M- goto middle line on screen
3454 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3455 dot = next_line(dot);
3457 case 'O': // O- open a empty line above
3459 p = begin_line(dot);
3460 if (p[-1] == '\n') {
3462 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3464 dot = char_insert(dot, '\n');
3467 dot = char_insert(dot, '\n'); // i\n ESC
3472 case 'R': // R- continuous Replace char
3476 case 'X': // X- delete char before dot
3477 case 'x': // x- delete the current char
3478 case 's': // s- substitute the current char
3485 if (dot[dir] != '\n') {
3487 dot--; // delete prev char
3488 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3491 goto dc_i; // start insrting
3492 end_cmd_q(); // stop adding to q
3494 case 'Z': // Z- if modified, {write}; exit
3495 // ZZ means to save file (if necessary), then exit
3496 c1 = get_one_char();
3501 if (file_modified) {
3502 #ifdef CONFIG_FEATURE_VI_READONLY
3503 if (vi_readonly || readonly) {
3504 psbs("\"%s\" File is read only", cfn);
3507 #endif /* CONFIG_FEATURE_VI_READONLY */
3508 cnt = file_write(cfn, text, end - 1);
3511 psbs("Write error: %s", strerror(errno));
3512 } else if (cnt == (end - 1 - text + 1)) {
3519 case '^': // ^- move to first non-blank on line
3523 case 'b': // b- back a word
3524 case 'e': // e- end of word
3531 if ((dot + dir) < text || (dot + dir) > end - 1)
3534 if (isspace(*dot)) {
3535 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3537 if (isalnum(*dot) || *dot == '_') {
3538 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3539 } else if (ispunct(*dot)) {
3540 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3543 case 'c': // c- change something
3544 case 'd': // d- delete something
3545 #ifdef CONFIG_FEATURE_VI_YANKMARK
3546 case 'y': // y- yank something
3547 case 'Y': // Y- Yank a line
3548 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3549 yf = YANKDEL; // assume either "c" or "d"
3550 #ifdef CONFIG_FEATURE_VI_YANKMARK
3551 if (c == 'y' || c == 'Y')
3553 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3556 c1 = get_one_char(); // get the type of thing to delete
3557 find_range(&p, &q, c1);
3558 if (c1 == 27) { // ESC- user changed mind and wants out
3559 c = c1 = 27; // Escape- do nothing
3560 } else if (strchr("wW", c1)) {
3562 // don't include trailing WS as part of word
3563 while (isblnk(*q)) {
3564 if (q <= text || q[-1] == '\n')
3569 dot = yank_delete(p, q, 0, yf); // delete word
3570 } else if (strchr("^0bBeEft$", c1)) {
3571 // single line copy text into a register and delete
3572 dot = yank_delete(p, q, 0, yf); // delete word
3573 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3574 // multiple line copy text into a register and delete
3575 dot = yank_delete(p, q, 1, yf); // delete lines
3577 dot = char_insert(dot, '\n');
3578 // on the last line of file don't move to prev line
3579 if (dot != (end-1)) {
3582 } else if (c == 'd') {
3587 // could not recognize object
3588 c = c1 = 27; // error-
3592 // if CHANGING, not deleting, start inserting after the delete
3594 strcpy((char *) buf, "Change");
3595 goto dc_i; // start inserting
3598 strcpy((char *) buf, "Delete");
3600 #ifdef CONFIG_FEATURE_VI_YANKMARK
3601 if (c == 'y' || c == 'Y') {
3602 strcpy((char *) buf, "Yank");
3605 q = p + strlen((char *) p);
3606 for (cnt = 0; p <= q; p++) {
3610 psb("%s %d lines (%d chars) using [%c]",
3611 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3612 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3613 end_cmd_q(); // stop adding to q
3616 case 'k': // k- goto prev line, same col
3617 case VI_K_UP: // cursor key Up
3622 dot = move_to_col(dot, ccol + offset); // try stay in same col
3624 case 'r': // r- replace the current char with user input
3625 c1 = get_one_char(); // get the replacement char
3628 file_modified++; // has the file been modified
3630 end_cmd_q(); // stop adding to q
3632 case 't': // t- move to char prior to next x
3633 last_forward_char = get_one_char();
3635 if (*dot == last_forward_char)
3637 last_forward_char= 0;
3639 case 'w': // w- forward a word
3643 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3644 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3645 } else if (ispunct(*dot)) { // we are on PUNCT
3646 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3649 dot++; // move over word
3650 if (isspace(*dot)) {
3651 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3655 c1 = get_one_char(); // get the replacement char
3658 cnt = (rows - 2) / 2; // put dot at center
3660 cnt = rows - 2; // put dot at bottom
3661 screenbegin = begin_line(dot); // start dot at top
3662 dot_scroll(cnt, -1);
3664 case '|': // |- move to column "cmdcnt"
3665 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3667 case '~': // ~- flip the case of letters a-z -> A-Z
3671 if (islower(*dot)) {
3672 *dot = toupper(*dot);
3673 file_modified++; // has the file been modified
3674 } else if (isupper(*dot)) {
3675 *dot = tolower(*dot);
3676 file_modified++; // has the file been modified
3679 end_cmd_q(); // stop adding to q
3681 //----- The Cursor and Function Keys -----------------------------
3682 case VI_K_HOME: // Cursor Key Home
3685 // The Fn keys could point to do_macro which could translate them
3686 case VI_K_FUN1: // Function Key F1
3687 case VI_K_FUN2: // Function Key F2
3688 case VI_K_FUN3: // Function Key F3
3689 case VI_K_FUN4: // Function Key F4
3690 case VI_K_FUN5: // Function Key F5
3691 case VI_K_FUN6: // Function Key F6
3692 case VI_K_FUN7: // Function Key F7
3693 case VI_K_FUN8: // Function Key F8
3694 case VI_K_FUN9: // Function Key F9
3695 case VI_K_FUN10: // Function Key F10
3696 case VI_K_FUN11: // Function Key F11
3697 case VI_K_FUN12: // Function Key F12
3702 // if text[] just became empty, add back an empty line
3704 (void) char_insert(text, '\n'); // start empty buf with dummy line
3707 // it is OK for dot to exactly equal to end, otherwise check dot validity
3709 dot = bound_dot(dot); // make sure "dot" is valid
3711 #ifdef CONFIG_FEATURE_VI_YANKMARK
3712 check_context(c); // update the current context
3713 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3716 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3717 cnt = dot - begin_line(dot);
3718 // Try to stay off of the Newline
3719 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3723 #ifdef CONFIG_FEATURE_VI_CRASHME
3724 static int totalcmds = 0;
3725 static int Mp = 85; // Movement command Probability
3726 static int Np = 90; // Non-movement command Probability
3727 static int Dp = 96; // Delete command Probability
3728 static int Ip = 97; // Insert command Probability
3729 static int Yp = 98; // Yank command Probability
3730 static int Pp = 99; // Put command Probability
3731 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3732 char chars[20] = "\t012345 abcdABCD-=.$";
3733 char *words[20] = { "this", "is", "a", "test",
3734 "broadcast", "the", "emergency", "of",
3735 "system", "quick", "brown", "fox",
3736 "jumped", "over", "lazy", "dogs",
3737 "back", "January", "Febuary", "March"
3740 "You should have received a copy of the GNU General Public License\n",
3741 "char c, cm, *cmd, *cmd1;\n",
3742 "generate a command by percentages\n",
3743 "Numbers may be typed as a prefix to some commands.\n",
3744 "Quit, discarding changes!\n",
3745 "Forced write, if permission originally not valid.\n",
3746 "In general, any ex or ed command (such as substitute or delete).\n",
3747 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3748 "Please get w/ me and I will go over it with you.\n",
3749 "The following is a list of scheduled, committed changes.\n",
3750 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3751 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3752 "Any question about transactions please contact Sterling Huxley.\n",
3753 "I will try to get back to you by Friday, December 31.\n",
3754 "This Change will be implemented on Friday.\n",
3755 "Let me know if you have problems accessing this;\n",
3756 "Sterling Huxley recently added you to the access list.\n",
3757 "Would you like to go to lunch?\n",
3758 "The last command will be automatically run.\n",
3759 "This is too much english for a computer geek.\n",
3761 char *multilines[20] = {
3762 "You should have received a copy of the GNU General Public License\n",
3763 "char c, cm, *cmd, *cmd1;\n",
3764 "generate a command by percentages\n",
3765 "Numbers may be typed as a prefix to some commands.\n",
3766 "Quit, discarding changes!\n",
3767 "Forced write, if permission originally not valid.\n",
3768 "In general, any ex or ed command (such as substitute or delete).\n",
3769 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3770 "Please get w/ me and I will go over it with you.\n",
3771 "The following is a list of scheduled, committed changes.\n",
3772 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3773 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3774 "Any question about transactions please contact Sterling Huxley.\n",
3775 "I will try to get back to you by Friday, December 31.\n",
3776 "This Change will be implemented on Friday.\n",
3777 "Let me know if you have problems accessing this;\n",
3778 "Sterling Huxley recently added you to the access list.\n",
3779 "Would you like to go to lunch?\n",
3780 "The last command will be automatically run.\n",
3781 "This is too much english for a computer geek.\n",
3784 // create a random command to execute
3785 static void crash_dummy()
3787 static int sleeptime; // how long to pause between commands
3788 char c, cm, *cmd, *cmd1;
3789 int i, cnt, thing, rbi, startrbi, percent;
3791 // "dot" movement commands
3792 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3794 // is there already a command running?
3795 if (readed_for_parse > 0)
3799 sleeptime = 0; // how long to pause between commands
3800 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3801 // generate a command by percentages
3802 percent = (int) lrand48() % 100; // get a number from 0-99
3803 if (percent < Mp) { // Movement commands
3804 // available commands
3807 } else if (percent < Np) { // non-movement commands
3808 cmd = "mz<>\'\""; // available commands
3810 } else if (percent < Dp) { // Delete commands
3811 cmd = "dx"; // available commands
3813 } else if (percent < Ip) { // Inset commands
3814 cmd = "iIaAsrJ"; // available commands
3816 } else if (percent < Yp) { // Yank commands
3817 cmd = "yY"; // available commands
3819 } else if (percent < Pp) { // Put commands
3820 cmd = "pP"; // available commands
3823 // We do not know how to handle this command, try again
3827 // randomly pick one of the available cmds from "cmd[]"
3828 i = (int) lrand48() % strlen(cmd);
3830 if (strchr(":\024", cm))
3831 goto cd0; // dont allow colon or ctrl-T commands
3832 readbuffer[rbi++] = cm; // put cmd into input buffer
3834 // now we have the command-
3835 // there are 1, 2, and multi char commands
3836 // find out which and generate the rest of command as necessary
3837 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3838 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3839 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3840 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3842 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3844 readbuffer[rbi++] = c; // add movement to input buffer
3846 if (strchr("iIaAsc", cm)) { // multi-char commands
3848 // change some thing
3849 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3851 readbuffer[rbi++] = c; // add movement to input buffer
3853 thing = (int) lrand48() % 4; // what thing to insert
3854 cnt = (int) lrand48() % 10; // how many to insert
3855 for (i = 0; i < cnt; i++) {
3856 if (thing == 0) { // insert chars
3857 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3858 } else if (thing == 1) { // insert words
3859 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3860 strcat((char *) readbuffer, " ");
3861 sleeptime = 0; // how fast to type
3862 } else if (thing == 2) { // insert lines
3863 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3864 sleeptime = 0; // how fast to type
3865 } else { // insert multi-lines
3866 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3867 sleeptime = 0; // how fast to type
3870 strcat((char *) readbuffer, "\033");
3872 readed_for_parse = strlen(readbuffer);
3876 (void) mysleep(sleeptime); // sleep 1/100 sec
3879 // test to see if there are any errors
3880 static void crash_test()
3882 static time_t oldtim;
3884 char d[2], msg[BUFSIZ];
3888 strcat((char *) msg, "end<text ");
3890 if (end > textend) {
3891 strcat((char *) msg, "end>textend ");
3894 strcat((char *) msg, "dot<text ");
3897 strcat((char *) msg, "dot>end ");
3899 if (screenbegin < text) {
3900 strcat((char *) msg, "screenbegin<text ");
3902 if (screenbegin > end - 1) {
3903 strcat((char *) msg, "screenbegin>end-1 ");
3906 if (strlen(msg) > 0) {
3908 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3909 totalcmds, last_input_char, msg, SOs, SOn);
3911 while (read(0, d, 1) > 0) {
3912 if (d[0] == '\n' || d[0] == '\r')
3917 tim = (time_t) time((time_t *) 0);
3918 if (tim >= (oldtim + 3)) {
3919 sprintf((char *) status_buffer,
3920 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3921 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3926 #endif /* CONFIG_FEATURE_VI_CRASHME */