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, *textend; // 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, st, 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 ':'
609 li = st = ch = i = 0;
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 *beg_scr, *end_scr; // begin and end of screen
1083 beg_cur = begin_line(d); // first char of cur line
1085 beg_scr = end_scr = screenbegin; // first char of screen
1086 end_scr = end_screen(); // last char of screen
1088 if (beg_cur < screenbegin) {
1089 // "d" is before top line on screen
1090 // how many lines do we have to move
1091 cnt = count_lines(beg_cur, screenbegin);
1093 screenbegin = beg_cur;
1094 if (cnt > (rows - 1) / 2) {
1095 // we moved too many lines. put "dot" in middle of screen
1096 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1097 screenbegin = prev_line(screenbegin);
1100 } else if (beg_cur > end_scr) {
1101 // "d" is after bottom line on screen
1102 // how many lines do we have to move
1103 cnt = count_lines(end_scr, beg_cur);
1104 if (cnt > (rows - 1) / 2)
1105 goto sc1; // too many lines
1106 for (ro = 0; ro < cnt - 1; ro++) {
1107 // move screen begin the same amount
1108 screenbegin = next_line(screenbegin);
1109 // now, move the end of screen
1110 end_scr = next_line(end_scr);
1111 end_scr = end_line(end_scr);
1114 // "d" is on screen- find out which row
1116 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1122 // find out what col "d" is on
1124 do { // drive "co" to correct column
1125 if (*tp == '\n' || *tp == '\0')
1129 co += ((tabstop - 1) - (co % tabstop));
1130 } else if (*tp < ' ' || *tp == 127) {
1131 co++; // display as ^X, use 2 columns
1133 } while (tp++ < d && ++co);
1135 // "co" is the column where "dot" is.
1136 // The screen has "columns" columns.
1137 // The currently displayed columns are 0+offset -- columns+ofset
1138 // |-------------------------------------------------------------|
1140 // offset | |------- columns ----------------|
1142 // If "co" is already in this range then we do not have to adjust offset
1143 // but, we do have to subtract the "offset" bias from "co".
1144 // If "co" is outside this range then we have to change "offset".
1145 // If the first char of a line is a tab the cursor will try to stay
1146 // in column 7, but we have to set offset to 0.
1148 if (co < 0 + offset) {
1151 if (co >= columns + offset) {
1152 offset = co - columns + 1;
1154 // if the first char of the line is a tab, and "dot" is sitting on it
1155 // force offset to 0.
1156 if (d == beg_cur && *d == '\t') {
1165 //----- Text Movement Routines ---------------------------------
1166 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1168 while (p > text && p[-1] != '\n')
1169 p--; // go to cur line B-o-l
1173 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1175 while (p < end - 1 && *p != '\n')
1176 p++; // go to cur line E-o-l
1180 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1182 while (p < end - 1 && *p != '\n')
1183 p++; // go to cur line E-o-l
1184 // Try to stay off of the Newline
1185 if (*p == '\n' && (p - begin_line(p)) > 0)
1190 static Byte *prev_line(Byte * p) // return pointer first char prev line
1192 p = begin_line(p); // goto begining of cur line
1193 if (p[-1] == '\n' && p > text)
1194 p--; // step to prev line
1195 p = begin_line(p); // goto begining of prev line
1199 static Byte *next_line(Byte * p) // return pointer first char next line
1202 if (*p == '\n' && p < end - 1)
1203 p++; // step to next line
1207 //----- Text Information Routines ------------------------------
1208 static Byte *end_screen(void)
1213 // find new bottom line
1215 for (cnt = 0; cnt < rows - 2; cnt++)
1221 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1226 if (stop < start) { // start and stop are backwards- reverse them
1232 stop = end_line(stop); // get to end of this line
1233 for (q = start; q <= stop && q <= end - 1; q++) {
1240 static Byte *find_line(int li) // find begining of line #li
1244 for (q = text; li > 1; li--) {
1250 //----- Dot Movement Routines ----------------------------------
1251 static void dot_left(void)
1253 if (dot > text && dot[-1] != '\n')
1257 static void dot_right(void)
1259 if (dot < end - 1 && *dot != '\n')
1263 static void dot_begin(void)
1265 dot = begin_line(dot); // return pointer to first char cur line
1268 static void dot_end(void)
1270 dot = end_line(dot); // return pointer to last char cur line
1273 static Byte *move_to_col(Byte * p, int l)
1280 if (*p == '\n' || *p == '\0')
1284 co += ((tabstop - 1) - (co % tabstop));
1285 } else if (*p < ' ' || *p == 127) {
1286 co++; // display as ^X, use 2 columns
1288 } while (++co <= l && p++ < end);
1292 static void dot_next(void)
1294 dot = next_line(dot);
1297 static void dot_prev(void)
1299 dot = prev_line(dot);
1302 static void dot_scroll(int cnt, int dir)
1306 for (; cnt > 0; cnt--) {
1309 // ctrl-Y scroll up one line
1310 screenbegin = prev_line(screenbegin);
1313 // ctrl-E scroll down one line
1314 screenbegin = next_line(screenbegin);
1317 // make sure "dot" stays on the screen so we dont scroll off
1318 if (dot < screenbegin)
1320 q = end_screen(); // find new bottom line
1322 dot = begin_line(q); // is dot is below bottom line?
1326 static void dot_skip_over_ws(void)
1329 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1333 static void dot_delete(void) // delete the char at 'dot'
1335 (void) text_hole_delete(dot, dot);
1338 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1340 if (p >= end && end > text) {
1342 indicate_error('1');
1346 indicate_error('2');
1351 //----- Helper Utility Routines --------------------------------
1353 //----------------------------------------------------------------
1354 //----- Char Routines --------------------------------------------
1355 /* Chars that are part of a word-
1356 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1357 * Chars that are Not part of a word (stoppers)
1358 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1359 * Chars that are WhiteSpace
1360 * TAB NEWLINE VT FF RETURN SPACE
1361 * DO NOT COUNT NEWLINE AS WHITESPACE
1364 static Byte *new_screen(int ro, int co)
1369 screensize = ro * co + 8;
1370 screen = (Byte *) xmalloc(screensize);
1371 // initialize the new screen. assume this will be a empty file.
1373 // non-existent text[] lines start with a tilde (~).
1374 for (li = 1; li < ro - 1; li++) {
1375 screen[(li * co) + 0] = '~';
1380 static Byte *new_text(int size)
1383 size = 10240; // have a minimum size for new files
1385 text = (Byte *) xmalloc(size + 8);
1386 memset(text, '\0', size); // clear new text[]
1387 //text += 4; // leave some room for "oops"
1388 textend = text + size - 1;
1389 //textend -= 4; // leave some root for "oops"
1393 #ifdef CONFIG_FEATURE_VI_SEARCH
1394 static int mycmp(Byte * s1, Byte * s2, int len)
1398 i = strncmp((char *) s1, (char *) s2, len);
1399 #ifdef CONFIG_FEATURE_VI_SETOPTS
1401 i = strncasecmp((char *) s1, (char *) s2, len);
1403 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1407 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1409 #ifndef REGEX_SEARCH
1413 len = strlen((char *) pat);
1414 if (dir == FORWARD) {
1415 stop = end - 1; // assume range is p - end-1
1416 if (range == LIMITED)
1417 stop = next_line(p); // range is to next line
1418 for (start = p; start < stop; start++) {
1419 if (mycmp(start, pat, len) == 0) {
1423 } else if (dir == BACK) {
1424 stop = text; // assume range is text - p
1425 if (range == LIMITED)
1426 stop = prev_line(p); // range is to prev line
1427 for (start = p - len; start >= stop; start--) {
1428 if (mycmp(start, pat, len) == 0) {
1433 // pattern not found
1435 #else /*REGEX_SEARCH */
1437 struct re_pattern_buffer preg;
1441 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1447 // assume a LIMITED forward search
1455 // count the number of chars to search over, forward or backward
1459 // RANGE could be negative if we are searching backwards
1462 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1464 // The pattern was not compiled
1465 psbs("bad search pattern: \"%s\": %s", pat, q);
1466 i = 0; // return p if pattern not compiled
1476 // search for the compiled pattern, preg, in p[]
1477 // range < 0- search backward
1478 // range > 0- search forward
1480 // re_search() < 0 not found or error
1481 // re_search() > 0 index of found pattern
1482 // struct pattern char int int int struct reg
1483 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1484 i = re_search(&preg, q, size, 0, range, 0);
1487 i = 0; // return NULL if pattern not found
1490 if (dir == FORWARD) {
1496 #endif /*REGEX_SEARCH */
1498 #endif /* CONFIG_FEATURE_VI_SEARCH */
1500 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1502 if (c == 22) { // Is this an ctrl-V?
1503 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1504 p--; // backup onto ^
1505 refresh(FALSE); // show the ^
1509 file_modified++; // has the file been modified
1510 } else if (c == 27) { // Is this an ESC?
1513 end_cmd_q(); // stop adding to q
1514 last_status_cksum = 0; // force status update
1515 if ((p[-1] != '\n') && (dot>text)) {
1518 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1520 if ((p[-1] != '\n') && (dot>text)) {
1522 p = text_hole_delete(p, p); // shrink buffer 1 char
1525 // insert a char into text[]
1526 Byte *sp; // "save p"
1529 c = '\n'; // translate \r to \n
1530 sp = p; // remember addr of insert
1531 p = stupid_insert(p, c); // insert the char
1532 #ifdef CONFIG_FEATURE_VI_SETOPTS
1533 if (showmatch && strchr(")]}", *sp) != NULL) {
1536 if (autoindent && c == '\n') { // auto indent the new line
1539 q = prev_line(p); // use prev line as templet
1540 for (; isblnk(*q); q++) {
1541 p = stupid_insert(p, *q); // insert the char
1544 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1549 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1551 p = text_hole_make(p, 1);
1554 file_modified++; // has the file been modified
1560 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1562 Byte *save_dot, *p, *q;
1568 if (strchr("cdy><", c)) {
1569 // these cmds operate on whole lines
1570 p = q = begin_line(p);
1571 for (cnt = 1; cnt < cmdcnt; cnt++) {
1575 } else if (strchr("^%$0bBeEft", c)) {
1576 // These cmds operate on char positions
1577 do_cmd(c); // execute movement cmd
1579 } else if (strchr("wW", c)) {
1580 do_cmd(c); // execute movement cmd
1581 // if we are at the next word's first char
1582 // step back one char
1583 // but check the possibilities when it is true
1584 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1585 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1586 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1587 dot--; // move back off of next word
1588 if (dot > text && *dot == '\n')
1589 dot--; // stay off NL
1591 } else if (strchr("H-k{", c)) {
1592 // these operate on multi-lines backwards
1593 q = end_line(dot); // find NL
1594 do_cmd(c); // execute movement cmd
1597 } else if (strchr("L+j}\r\n", c)) {
1598 // these operate on multi-lines forwards
1599 p = begin_line(dot);
1600 do_cmd(c); // execute movement cmd
1601 dot_end(); // find NL
1604 c = 27; // error- return an ESC char
1617 static int st_test(Byte * p, int type, int dir, Byte * tested)
1627 if (type == S_BEFORE_WS) {
1629 test = ((!isspace(c)) || c == '\n');
1631 if (type == S_TO_WS) {
1633 test = ((!isspace(c)) || c == '\n');
1635 if (type == S_OVER_WS) {
1637 test = ((isspace(c)));
1639 if (type == S_END_PUNCT) {
1641 test = ((ispunct(c)));
1643 if (type == S_END_ALNUM) {
1645 test = ((isalnum(c)) || c == '_');
1651 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1655 while (st_test(p, type, dir, &c)) {
1656 // make sure we limit search to correct number of lines
1657 if (c == '\n' && --linecnt < 1)
1659 if (dir >= 0 && p >= end - 1)
1661 if (dir < 0 && p <= text)
1663 p += dir; // move to next char
1668 // find matching char of pair () [] {}
1669 static Byte *find_pair(Byte * p, Byte c)
1676 dir = 1; // assume forward
1700 for (q = p + dir; text <= q && q < end; q += dir) {
1701 // look for match, count levels of pairs (( ))
1703 level++; // increase pair levels
1705 level--; // reduce pair level
1707 break; // found matching pair
1710 q = NULL; // indicate no match
1714 #ifdef CONFIG_FEATURE_VI_SETOPTS
1715 // show the matching char of a pair, () [] {}
1716 static void showmatching(Byte * p)
1720 // we found half of a pair
1721 q = find_pair(p, *p); // get loc of matching char
1723 indicate_error('3'); // no matching char
1725 // "q" now points to matching pair
1726 save_dot = dot; // remember where we are
1727 dot = q; // go to new loc
1728 refresh(FALSE); // let the user see it
1729 (void) mysleep(40); // give user some time
1730 dot = save_dot; // go back to old loc
1734 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1736 // open a hole in text[]
1737 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1746 cnt = end - src; // the rest of buffer
1747 if (memmove(dest, src, cnt) != dest) {
1748 psbs("can't create room for new characters");
1750 memset(p, ' ', size); // clear new hole
1751 end = end + size; // adjust the new END
1752 file_modified++; // has the file been modified
1757 // close a hole in text[]
1758 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1763 // move forwards, from beginning
1767 if (q < p) { // they are backward- swap them
1771 hole_size = q - p + 1;
1773 if (src < text || src > end)
1775 if (dest < text || dest >= end)
1778 goto thd_atend; // just delete the end of the buffer
1779 if (memmove(dest, src, cnt) != dest) {
1780 psbs("can't delete the character");
1783 end = end - hole_size; // adjust the new END
1785 dest = end - 1; // make sure dest in below end-1
1787 dest = end = text; // keep pointers valid
1788 file_modified++; // has the file been modified
1793 // copy text into register, then delete text.
1794 // if dist <= 0, do not include, or go past, a NewLine
1796 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1800 // make sure start <= stop
1802 // they are backwards, reverse them
1808 // we can not cross NL boundaries
1812 // dont go past a NewLine
1813 for (; p + 1 <= stop; p++) {
1815 stop = p; // "stop" just before NewLine
1821 #ifdef CONFIG_FEATURE_VI_YANKMARK
1822 text_yank(start, stop, YDreg);
1823 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1824 if (yf == YANKDEL) {
1825 p = text_hole_delete(start, stop);
1830 static void show_help(void)
1832 puts("These features are available:"
1833 #ifdef CONFIG_FEATURE_VI_SEARCH
1834 "\n\tPattern searches with / and ?"
1835 #endif /* CONFIG_FEATURE_VI_SEARCH */
1836 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1837 "\n\tLast command repeat with \'.\'"
1838 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1839 #ifdef CONFIG_FEATURE_VI_YANKMARK
1840 "\n\tLine marking with 'x"
1841 "\n\tNamed buffers with \"x"
1842 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1843 #ifdef CONFIG_FEATURE_VI_READONLY
1844 "\n\tReadonly if vi is called as \"view\""
1845 "\n\tReadonly with -R command line arg"
1846 #endif /* CONFIG_FEATURE_VI_READONLY */
1847 #ifdef CONFIG_FEATURE_VI_SET
1848 "\n\tSome colon mode commands with \':\'"
1849 #endif /* CONFIG_FEATURE_VI_SET */
1850 #ifdef CONFIG_FEATURE_VI_SETOPTS
1851 "\n\tSettable options with \":set\""
1852 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1853 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1854 "\n\tSignal catching- ^C"
1855 "\n\tJob suspend and resume with ^Z"
1856 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1857 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1858 "\n\tAdapt to window re-sizes"
1859 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1863 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1868 strcpy((char *) buf, ""); // init buf
1869 if (strlen((char *) s) <= 0)
1870 s = (Byte *) "(NULL)";
1871 for (; *s > '\0'; s++) {
1875 c_is_no_print = c > 127 && !Isprint(c);
1876 if (c_is_no_print) {
1877 strcat((char *) buf, SOn);
1880 if (c < ' ' || c == 127) {
1881 strcat((char *) buf, "^");
1888 strcat((char *) buf, (char *) b);
1890 strcat((char *) buf, SOs);
1892 strcat((char *) buf, "$");
1897 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1898 static void start_new_cmd_q(Byte c)
1901 free(last_modifying_cmd);
1902 // get buffer for new cmd
1903 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1904 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1905 // if there is a current cmd count put it in the buffer first
1907 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1908 else // just save char c onto queue
1909 last_modifying_cmd[0] = c;
1913 static void end_cmd_q(void)
1915 #ifdef CONFIG_FEATURE_VI_YANKMARK
1916 YDreg = 26; // go back to default Yank/Delete reg
1917 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1921 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1923 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1924 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1928 i = strlen((char *) s);
1929 p = text_hole_make(p, i);
1930 strncpy((char *) p, (char *) s, i);
1931 for (cnt = 0; *s != '\0'; s++) {
1935 #ifdef CONFIG_FEATURE_VI_YANKMARK
1936 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1937 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1940 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1942 #ifdef CONFIG_FEATURE_VI_YANKMARK
1943 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1948 if (q < p) { // they are backwards- reverse them
1955 free(t); // if already a yank register, free it
1956 t = (Byte *) xmalloc(cnt + 1); // get a new register
1957 memset(t, '\0', cnt + 1); // clear new text[]
1958 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1963 static Byte what_reg(void)
1969 c = 'D'; // default to D-reg
1970 if (0 <= YDreg && YDreg <= 25)
1971 c = 'a' + (Byte) YDreg;
1979 static void check_context(Byte cmd)
1981 // A context is defined to be "modifying text"
1982 // Any modifying command establishes a new context.
1984 if (dot < context_start || dot > context_end) {
1985 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1986 // we are trying to modify text[]- make this the current context
1987 mark[27] = mark[26]; // move cur to prev
1988 mark[26] = dot; // move local to cur
1989 context_start = prev_line(prev_line(dot));
1990 context_end = next_line(next_line(dot));
1991 //loiter= start_loiter= now;
1997 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2001 // the current context is in mark[26]
2002 // the previous context is in mark[27]
2003 // only swap context if other context is valid
2004 if (text <= mark[27] && mark[27] <= end - 1) {
2006 mark[27] = mark[26];
2008 p = mark[26]; // where we are going- previous context
2009 context_start = prev_line(prev_line(prev_line(p)));
2010 context_end = next_line(next_line(next_line(p)));
2014 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2016 static int isblnk(Byte c) // is the char a blank or tab
2018 return (c == ' ' || c == '\t');
2021 //----- Set terminal attributes --------------------------------
2022 static void rawmode(void)
2024 tcgetattr(0, &term_orig);
2025 term_vi = term_orig;
2026 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2027 term_vi.c_iflag &= (~IXON & ~ICRNL);
2028 term_vi.c_oflag &= (~ONLCR);
2029 term_vi.c_cc[VMIN] = 1;
2030 term_vi.c_cc[VTIME] = 0;
2031 erase_char = term_vi.c_cc[VERASE];
2032 tcsetattr(0, TCSANOW, &term_vi);
2035 static void cookmode(void)
2038 tcsetattr(0, TCSANOW, &term_orig);
2041 //----- Come here when we get a window resize signal ---------
2042 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2043 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2045 signal(SIGWINCH, winch_sig);
2046 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2047 get_terminal_width_height(0, &columns, &rows);
2048 new_screen(rows, columns); // get memory for virtual screen
2049 redraw(TRUE); // re-draw the screen
2052 //----- Come here when we get a continue signal -------------------
2053 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2055 rawmode(); // terminal to "raw"
2056 last_status_cksum = 0; // force status update
2057 redraw(TRUE); // re-draw the screen
2059 signal(SIGTSTP, suspend_sig);
2060 signal(SIGCONT, SIG_DFL);
2061 kill(my_pid, SIGCONT);
2064 //----- Come here when we get a Suspend signal -------------------
2065 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2067 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2068 clear_to_eol(); // Erase to end of line
2069 cookmode(); // terminal to "cooked"
2071 signal(SIGCONT, cont_sig);
2072 signal(SIGTSTP, SIG_DFL);
2073 kill(my_pid, SIGTSTP);
2076 //----- Come here when we get a signal ---------------------------
2077 static void catch_sig(int sig)
2079 signal(SIGINT, catch_sig);
2081 longjmp(restart, sig);
2083 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2085 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2087 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2092 tv.tv_usec = hund * 10000;
2093 select(1, &rfds, NULL, NULL, &tv);
2094 return (FD_ISSET(0, &rfds));
2097 #define readbuffer bb_common_bufsiz1
2099 static int readed_for_parse;
2101 //----- IO Routines --------------------------------------------
2102 static Byte readit(void) // read (maybe cursor) key from stdin
2111 static const struct esc_cmds esccmds[] = {
2112 {"OA", (Byte) VI_K_UP}, // cursor key Up
2113 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2114 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2115 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2116 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2117 {"OF", (Byte) VI_K_END}, // Cursor Key End
2118 {"[A", (Byte) VI_K_UP}, // cursor key Up
2119 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2120 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2121 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2122 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2123 {"[F", (Byte) VI_K_END}, // Cursor Key End
2124 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2125 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2126 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2127 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2128 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2129 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2130 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2131 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2132 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2133 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2134 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2135 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2136 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2137 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2138 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2139 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2140 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2141 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2142 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2143 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2144 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2147 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2149 (void) alarm(0); // turn alarm OFF while we wait for input
2151 n = readed_for_parse;
2152 // get input from User- are there already input chars in Q?
2155 // the Q is empty, wait for a typed char
2156 n = read(0, readbuffer, BUFSIZ - 1);
2159 goto ri0; // interrupted sys call
2162 if (errno == EFAULT)
2164 if (errno == EINVAL)
2172 if (readbuffer[0] == 27) {
2173 // This is an ESC char. Is this Esc sequence?
2174 // Could be bare Esc key. See if there are any
2175 // more chars to read after the ESC. This would
2176 // be a Function or Cursor Key sequence.
2180 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2182 // keep reading while there are input chars and room in buffer
2183 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2184 // read the rest of the ESC string
2185 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2191 readed_for_parse = n;
2194 if(c == 27 && n > 1) {
2195 // Maybe cursor or function key?
2196 const struct esc_cmds *eindex;
2198 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2199 int cnt = strlen(eindex->seq);
2203 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2205 // is a Cursor key- put derived value back into Q
2207 // for squeeze out the ESC sequence
2211 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2212 /* defined ESC sequence not found, set only one ESC */
2218 // remove key sequence from Q
2219 readed_for_parse -= n;
2220 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2221 (void) alarm(3); // we are done waiting for input, turn alarm ON
2225 //----- IO Routines --------------------------------------------
2226 static Byte get_one_char(void)
2230 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2231 // ! adding2q && ioq == 0 read()
2232 // ! adding2q && ioq != 0 *ioq
2233 // adding2q *last_modifying_cmd= read()
2235 // we are not adding to the q.
2236 // but, we may be reading from a q
2238 // there is no current q, read from STDIN
2239 c = readit(); // get the users input
2241 // there is a queue to get chars from first
2244 // the end of the q, read from STDIN
2246 ioq_start = ioq = 0;
2247 c = readit(); // get the users input
2251 // adding STDIN chars to q
2252 c = readit(); // get the users input
2253 if (last_modifying_cmd != 0) {
2254 int len = strlen((char *) last_modifying_cmd);
2255 if (len + 1 >= BUFSIZ) {
2256 psbs("last_modifying_cmd overrun");
2258 // add new char to q
2259 last_modifying_cmd[len] = c;
2263 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2264 c = readit(); // get the users input
2265 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2266 return (c); // return the char, where ever it came from
2269 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2274 static Byte *obufp = NULL;
2276 strcpy((char *) buf, (char *) prompt);
2277 last_status_cksum = 0; // force status update
2278 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2279 clear_to_eol(); // clear the line
2280 write1((char *) prompt); // write out the :, /, or ? prompt
2282 for (i = strlen((char *) buf); i < BUFSIZ;) {
2283 c = get_one_char(); // read user input
2284 if (c == '\n' || c == '\r' || c == 27)
2285 break; // is this end of input
2286 if (c == erase_char || c == 8 || c == 127) {
2287 // user wants to erase prev char
2288 i--; // backup to prev char
2289 buf[i] = '\0'; // erase the char
2290 buf[i + 1] = '\0'; // null terminate buffer
2291 write1("\b \b"); // erase char on screen
2292 if (i <= 0) { // user backs up before b-o-l, exit
2296 buf[i] = c; // save char in buffer
2297 buf[i + 1] = '\0'; // make sure buffer is null terminated
2298 putchar(c); // echo the char back to user
2304 obufp = (Byte *) xstrdup((char *) buf);
2308 static int file_size(const Byte * fn) // what is the byte size of "fn"
2313 if (fn == 0 || strlen((char *)fn) <= 0)
2316 sr = stat((char *) fn, &st_buf); // see if file exists
2318 cnt = (int) st_buf.st_size;
2323 static int file_insert(Byte * fn, Byte * p, int size)
2328 #ifdef CONFIG_FEATURE_VI_READONLY
2330 #endif /* CONFIG_FEATURE_VI_READONLY */
2331 if (fn == 0 || strlen((char*) fn) <= 0) {
2332 psbs("No filename given");
2336 // OK- this is just a no-op
2341 psbs("Trying to insert a negative number (%d) of characters", size);
2344 if (p < text || p > end) {
2345 psbs("Trying to insert file outside of memory");
2349 // see if we can open the file
2350 #ifdef CONFIG_FEATURE_VI_READONLY
2351 if (vi_readonly) goto fi1; // do not try write-mode
2353 fd = open((char *) fn, O_RDWR); // assume read & write
2355 // could not open for writing- maybe file is read only
2356 #ifdef CONFIG_FEATURE_VI_READONLY
2359 fd = open((char *) fn, O_RDONLY); // try read-only
2361 psbs("\"%s\" %s", fn, "could not open file");
2364 #ifdef CONFIG_FEATURE_VI_READONLY
2365 // got the file- read-only
2367 #endif /* CONFIG_FEATURE_VI_READONLY */
2369 p = text_hole_make(p, size);
2370 cnt = read(fd, p, size);
2374 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2375 psbs("could not read file \"%s\"", fn);
2376 } else if (cnt < size) {
2377 // There was a partial read, shrink unused space text[]
2378 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2379 psbs("could not read all of file \"%s\"", fn);
2387 static int file_write(Byte * fn, Byte * first, Byte * last)
2389 int fd, cnt, charcnt;
2392 psbs("No current filename");
2396 // FIXIT- use the correct umask()
2397 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2400 cnt = last - first + 1;
2401 charcnt = write(fd, first, cnt);
2402 if (charcnt == cnt) {
2404 //file_modified= FALSE; // the file has not been modified
2412 //----- Terminal Drawing ---------------------------------------
2413 // The terminal is made up of 'rows' line of 'columns' columns.
2414 // classically this would be 24 x 80.
2415 // screen coordinates
2421 // 23,0 ... 23,79 status line
2424 //----- Move the cursor to row x col (count from 0, not 1) -------
2425 static void place_cursor(int row, int col, int opti)
2429 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2432 // char cm3[BUFSIZ];
2434 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2436 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2438 if (row < 0) row = 0;
2439 if (row >= rows) row = rows - 1;
2440 if (col < 0) col = 0;
2441 if (col >= columns) col = columns - 1;
2443 //----- 1. Try the standard terminal ESC sequence
2444 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2446 if (! opti) goto pc0;
2448 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2449 //----- find the minimum # of chars to move cursor -------------
2450 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2451 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2453 // move to the correct row
2454 while (row < Rrow) {
2455 // the cursor has to move up
2459 while (row > Rrow) {
2460 // the cursor has to move down
2461 strcat(cm2, CMdown);
2465 // now move to the correct column
2466 strcat(cm2, "\r"); // start at col 0
2467 // just send out orignal source char to get to correct place
2468 screenp = &screen[row * columns]; // start of screen line
2469 strncat(cm2, (char* )screenp, col);
2471 //----- 3. Try some other way of moving cursor
2472 //---------------------------------------------
2474 // pick the shortest cursor motion to send out
2476 if (strlen(cm2) < strlen(cm)) {
2478 } /* else if (strlen(cm3) < strlen(cm)) {
2481 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2483 write1(cm); // move the cursor
2486 //----- Erase from cursor to end of line -----------------------
2487 static void clear_to_eol(void)
2489 write1(Ceol); // Erase from cursor to end of line
2492 //----- Erase from cursor to end of screen -----------------------
2493 static void clear_to_eos(void)
2495 write1(Ceos); // Erase from cursor to end of screen
2498 //----- Start standout mode ------------------------------------
2499 static void standout_start(void) // send "start reverse video" sequence
2501 write1(SOs); // Start reverse video mode
2504 //----- End standout mode --------------------------------------
2505 static void standout_end(void) // send "end reverse video" sequence
2507 write1(SOn); // End reverse video mode
2510 //----- Flash the screen --------------------------------------
2511 static void flash(int h)
2513 standout_start(); // send "start reverse video" sequence
2516 standout_end(); // send "end reverse video" sequence
2520 static void Indicate_Error(void)
2522 #ifdef CONFIG_FEATURE_VI_CRASHME
2524 return; // generate a random command
2525 #endif /* CONFIG_FEATURE_VI_CRASHME */
2527 write1(bell); // send out a bell character
2533 //----- Screen[] Routines --------------------------------------
2534 //----- Erase the Screen[] memory ------------------------------
2535 static void screen_erase(void)
2537 memset(screen, ' ', screensize); // clear new screen
2540 static int bufsum(unsigned char *buf, int count)
2543 unsigned char *e = buf + count;
2549 //----- Draw the status line at bottom of the screen -------------
2550 static void show_status_line(void)
2552 int cnt = 0, cksum = 0;
2554 // either we already have an error or status message, or we
2556 if (!have_status_msg) {
2557 cnt = format_edit_status();
2558 cksum = bufsum(status_buffer, cnt);
2560 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2561 last_status_cksum= cksum; // remember if we have seen this line
2562 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2563 write1((char*)status_buffer);
2565 if (have_status_msg) {
2566 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2568 have_status_msg = 0;
2571 have_status_msg = 0;
2573 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2578 //----- format the status buffer, the bottom line of screen ------
2579 // format status buffer, with STANDOUT mode
2580 static void psbs(const char *format, ...)
2584 va_start(args, format);
2585 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2586 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2587 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2590 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2595 // format status buffer
2596 static void psb(const char *format, ...)
2600 va_start(args, format);
2601 vsprintf((char *) status_buffer, format, args);
2604 have_status_msg = 1;
2609 static void ni(Byte * s) // display messages
2613 print_literal(buf, s);
2614 psbs("\'%s\' is not implemented", buf);
2617 static int format_edit_status(void) // show file status on status line
2619 int cur, percent, ret, trunc_at;
2622 // file_modified is now a counter rather than a flag. this
2623 // helps reduce the amount of line counting we need to do.
2624 // (this will cause a mis-reporting of modified status
2625 // once every MAXINT editing operations.)
2627 // it would be nice to do a similar optimization here -- if
2628 // we haven't done a motion that could have changed which line
2629 // we're on, then we shouldn't have to do this count_lines()
2630 cur = count_lines(text, dot);
2632 // reduce counting -- the total lines can't have
2633 // changed if we haven't done any edits.
2634 if (file_modified != last_file_modified) {
2635 tot = cur + count_lines(dot, end - 1) - 1;
2636 last_file_modified = file_modified;
2639 // current line percent
2640 // ------------- ~~ ----------
2643 percent = (100 * cur) / tot;
2649 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2650 columns : STATUS_BUFFER_LEN-1;
2652 ret = snprintf((char *) status_buffer, trunc_at+1,
2653 #ifdef CONFIG_FEATURE_VI_READONLY
2654 "%c %s%s%s %d/%d %d%%",
2656 "%c %s%s %d/%d %d%%",
2658 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2659 (cfn != 0 ? (char *) cfn : "No file"),
2660 #ifdef CONFIG_FEATURE_VI_READONLY
2661 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2663 (file_modified ? " [modified]" : ""),
2666 if (ret >= 0 && ret < trunc_at)
2667 return ret; /* it all fit */
2669 return trunc_at; /* had to truncate */
2672 //----- Force refresh of all Lines -----------------------------
2673 static void redraw(int full_screen)
2675 place_cursor(0, 0, FALSE); // put cursor in correct place
2676 clear_to_eos(); // tel terminal to erase display
2677 screen_erase(); // erase the internal screen buffer
2678 last_status_cksum = 0; // force status update
2679 refresh(full_screen); // this will redraw the entire display
2683 //----- Format a text[] line into a buffer ---------------------
2684 static void format_line(Byte *dest, Byte *src, int li)
2689 for (co= 0; co < MAX_SCR_COLS; co++) {
2690 c= ' '; // assume blank
2691 if (li > 0 && co == 0) {
2692 c = '~'; // not first line, assume Tilde
2694 // are there chars in text[] and have we gone past the end
2695 if (text < end && src < end) {
2700 if (c > 127 && !Isprint(c)) {
2703 if (c < ' ' || c == 127) {
2707 for (; (co % tabstop) != (tabstop - 1); co++) {
2715 c += '@'; // make it visible
2718 // the co++ is done here so that the column will
2719 // not be overwritten when we blank-out the rest of line
2726 //----- Refresh the changed screen lines -----------------------
2727 // Copy the source line from text[] into the buffer and note
2728 // if the current screenline is different from the new buffer.
2729 // If they differ then that line needs redrawing on the terminal.
2731 static void refresh(int full_screen)
2733 static int old_offset;
2735 Byte buf[MAX_SCR_COLS];
2736 Byte *tp, *sp; // pointer into text[] and screen[]
2737 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2738 int last_li= -2; // last line that changed- for optimizing cursor movement
2739 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2741 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2742 get_terminal_width_height(0, &columns, &rows);
2743 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2744 tp = screenbegin; // index into text[] of top line
2746 // compare text[] to screen[] and mark screen[] lines that need updating
2747 for (li = 0; li < rows - 1; li++) {
2748 int cs, ce; // column start & end
2749 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2750 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2751 // format current text line into buf
2752 format_line(buf, tp, li);
2754 // skip to the end of the current text[] line
2755 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2757 // see if there are any changes between vitual screen and buf
2758 changed = FALSE; // assume no change
2761 sp = &screen[li * columns]; // start of screen line
2763 // force re-draw of every single column from 0 - columns-1
2766 // compare newly formatted buffer with virtual screen
2767 // look forward for first difference between buf and screen
2768 for ( ; cs <= ce; cs++) {
2769 if (buf[cs + offset] != sp[cs]) {
2770 changed = TRUE; // mark for redraw
2775 // look backward for last difference between buf and screen
2776 for ( ; ce >= cs; ce--) {
2777 if (buf[ce + offset] != sp[ce]) {
2778 changed = TRUE; // mark for redraw
2782 // now, cs is index of first diff, and ce is index of last diff
2784 // if horz offset has changed, force a redraw
2785 if (offset != old_offset) {
2790 // make a sanity check of columns indexes
2792 if (ce > columns-1) ce= columns-1;
2793 if (cs > ce) { cs= 0; ce= columns-1; }
2794 // is there a change between vitual screen and buf
2796 // copy changed part of buffer to virtual screen
2797 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2799 // move cursor to column of first change
2800 if (offset != old_offset) {
2801 // opti_cur_move is still too stupid
2802 // to handle offsets correctly
2803 place_cursor(li, cs, FALSE);
2805 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2806 // if this just the next line
2807 // try to optimize cursor movement
2808 // otherwise, use standard ESC sequence
2809 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2811 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2812 place_cursor(li, cs, FALSE); // use standard ESC sequence
2813 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2816 // write line out to terminal
2819 char *out = (char*)sp+cs;
2826 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2828 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2832 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2833 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2836 place_cursor(crow, ccol, FALSE);
2837 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2839 if (offset != old_offset)
2840 old_offset = offset;
2843 //---------------------------------------------------------------------
2844 //----- the Ascii Chart -----------------------------------------------
2846 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2847 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2848 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2849 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2850 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2851 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2852 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2853 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2854 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2855 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2856 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2857 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2858 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2859 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2860 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2861 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2862 //---------------------------------------------------------------------
2864 //----- Execute a Vi Command -----------------------------------
2865 static void do_cmd(Byte c)
2867 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2868 int cnt, i, j, dir, yf;
2870 c1 = c; // quiet the compiler
2871 cnt = yf = dir = 0; // quiet the compiler
2872 p = q = save_dot = msg = buf; // quiet the compiler
2873 memset(buf, '\0', 9); // clear buf
2877 /* if this is a cursor key, skip these checks */
2890 if (cmd_mode == 2) {
2891 // flip-flop Insert/Replace mode
2892 if (c == VI_K_INSERT) goto dc_i;
2893 // we are 'R'eplacing the current *dot with new char
2895 // don't Replace past E-o-l
2896 cmd_mode = 1; // convert to insert
2898 if (1 <= c || Isprint(c)) {
2900 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2901 dot = char_insert(dot, c); // insert new char
2906 if (cmd_mode == 1) {
2907 // hitting "Insert" twice means "R" replace mode
2908 if (c == VI_K_INSERT) goto dc5;
2909 // insert the char c at "dot"
2910 if (1 <= c || Isprint(c)) {
2911 dot = char_insert(dot, c);
2926 #ifdef CONFIG_FEATURE_VI_CRASHME
2927 case 0x14: // dc4 ctrl-T
2928 crashme = (crashme == 0) ? 1 : 0;
2930 #endif /* CONFIG_FEATURE_VI_CRASHME */
2959 //case 'u': // u- FIXME- there is no undo
2961 default: // unrecognised command
2970 end_cmd_q(); // stop adding to q
2971 case 0x00: // nul- ignore
2973 case 2: // ctrl-B scroll up full screen
2974 case VI_K_PAGEUP: // Cursor Key Page Up
2975 dot_scroll(rows - 2, -1);
2977 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2978 case 0x03: // ctrl-C interrupt
2979 longjmp(restart, 1);
2981 case 26: // ctrl-Z suspend
2982 suspend_sig(SIGTSTP);
2984 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2985 case 4: // ctrl-D scroll down half screen
2986 dot_scroll((rows - 2) / 2, 1);
2988 case 5: // ctrl-E scroll down one line
2991 case 6: // ctrl-F scroll down full screen
2992 case VI_K_PAGEDOWN: // Cursor Key Page Down
2993 dot_scroll(rows - 2, 1);
2995 case 7: // ctrl-G show current status
2996 last_status_cksum = 0; // force status update
2998 case 'h': // h- move left
2999 case VI_K_LEFT: // cursor key Left
3000 case 8: // ctrl-H- move left (This may be ERASE char)
3001 case 127: // DEL- move left (This may be ERASE char)
3007 case 10: // Newline ^J
3008 case 'j': // j- goto next line, same col
3009 case VI_K_DOWN: // cursor key Down
3013 dot_next(); // go to next B-o-l
3014 dot = move_to_col(dot, ccol + offset); // try stay in same col
3016 case 12: // ctrl-L force redraw whole screen
3017 case 18: // ctrl-R force redraw
3018 place_cursor(0, 0, FALSE); // put cursor in correct place
3019 clear_to_eos(); // tel terminal to erase display
3021 screen_erase(); // erase the internal screen buffer
3022 last_status_cksum = 0; // force status update
3023 refresh(TRUE); // this will redraw the entire display
3025 case 13: // Carriage Return ^M
3026 case '+': // +- goto next line
3033 case 21: // ctrl-U scroll up half screen
3034 dot_scroll((rows - 2) / 2, -1);
3036 case 25: // ctrl-Y scroll up one line
3042 cmd_mode = 0; // stop insrting
3044 last_status_cksum = 0; // force status update
3046 case ' ': // move right
3047 case 'l': // move right
3048 case VI_K_RIGHT: // Cursor Key Right
3054 #ifdef CONFIG_FEATURE_VI_YANKMARK
3055 case '"': // "- name a register to use for Delete/Yank
3056 c1 = get_one_char();
3064 case '\'': // '- goto a specific mark
3065 c1 = get_one_char();
3071 if (text <= q && q < end) {
3073 dot_begin(); // go to B-o-l
3076 } else if (c1 == '\'') { // goto previous context
3077 dot = swap_context(dot); // swap current and previous context
3078 dot_begin(); // go to B-o-l
3084 case 'm': // m- Mark a line
3085 // this is really stupid. If there are any inserts or deletes
3086 // between text[0] and dot then this mark will not point to the
3087 // correct location! It could be off by many lines!
3088 // Well..., at least its quick and dirty.
3089 c1 = get_one_char();
3093 // remember the line
3094 mark[(int) c1] = dot;
3099 case 'P': // P- Put register before
3100 case 'p': // p- put register after
3103 psbs("Nothing in register %c", what_reg());
3106 // are we putting whole lines or strings
3107 if (strchr((char *) p, '\n') != NULL) {
3109 dot_begin(); // putting lines- Put above
3112 // are we putting after very last line?
3113 if (end_line(dot) == (end - 1)) {
3114 dot = end; // force dot to end of text[]
3116 dot_next(); // next line, then put before
3121 dot_right(); // move to right, can move to NL
3123 dot = string_insert(dot, p); // insert the string
3124 end_cmd_q(); // stop adding to q
3126 case 'U': // U- Undo; replace current line with original version
3127 if (reg[Ureg] != 0) {
3128 p = begin_line(dot);
3130 p = text_hole_delete(p, q); // delete cur line
3131 p = string_insert(p, reg[Ureg]); // insert orig line
3136 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3137 case '$': // $- goto end of line
3138 case VI_K_END: // Cursor Key End
3142 dot = end_line(dot);
3144 case '%': // %- find matching char of pair () [] {}
3145 for (q = dot; q < end && *q != '\n'; q++) {
3146 if (strchr("()[]{}", *q) != NULL) {
3147 // we found half of a pair
3148 p = find_pair(q, *q);
3160 case 'f': // f- forward to a user specified char
3161 last_forward_char = get_one_char(); // get the search char
3163 // dont separate these two commands. 'f' depends on ';'
3165 //**** fall thru to ... ';'
3166 case ';': // ;- look at rest of line for last forward char
3170 if (last_forward_char == 0) break;
3172 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3175 if (*q == last_forward_char)
3178 case '-': // -- goto prev line
3185 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3186 case '.': // .- repeat the last modifying command
3187 // Stuff the last_modifying_cmd back into stdin
3188 // and let it be re-executed.
3189 if (last_modifying_cmd != 0) {
3190 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3193 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3194 #ifdef CONFIG_FEATURE_VI_SEARCH
3195 case '?': // /- search for a pattern
3196 case '/': // /- search for a pattern
3199 q = get_input_line(buf); // get input line- use "status line"
3200 if (strlen((char *) q) == 1)
3201 goto dc3; // if no pat re-use old pat
3202 if (strlen((char *) q) > 1) { // new pat- save it and find
3203 // there is a new pat
3204 free(last_search_pattern);
3205 last_search_pattern = (Byte *) xstrdup((char *) q);
3206 goto dc3; // now find the pattern
3208 // user changed mind and erased the "/"- do nothing
3210 case 'N': // N- backward search for last pattern
3214 dir = BACK; // assume BACKWARD search
3216 if (last_search_pattern[0] == '?') {
3220 goto dc4; // now search for pattern
3222 case 'n': // n- repeat search for last pattern
3223 // search rest of text[] starting at next char
3224 // if search fails return orignal "p" not the "p+1" address
3229 if (last_search_pattern == 0) {
3230 msg = (Byte *) "No previous regular expression";
3233 if (last_search_pattern[0] == '/') {
3234 dir = FORWARD; // assume FORWARD search
3237 if (last_search_pattern[0] == '?') {
3242 q = char_search(p, last_search_pattern + 1, dir, FULL);
3244 dot = q; // good search, update "dot"
3248 // no pattern found between "dot" and "end"- continue at top
3253 q = char_search(p, last_search_pattern + 1, dir, FULL);
3254 if (q != NULL) { // found something
3255 dot = q; // found new pattern- goto it
3256 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3258 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3261 msg = (Byte *) "Pattern not found";
3264 if (*msg) psbs("%s", msg);
3266 case '{': // {- move backward paragraph
3267 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3268 if (q != NULL) { // found blank line
3269 dot = next_line(q); // move to next blank line
3272 case '}': // }- move forward paragraph
3273 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3274 if (q != NULL) { // found blank line
3275 dot = next_line(q); // move to next blank line
3278 #endif /* CONFIG_FEATURE_VI_SEARCH */
3279 case '0': // 0- goto begining of line
3289 if (c == '0' && cmdcnt < 1) {
3290 dot_begin(); // this was a standalone zero
3292 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3295 case ':': // :- the colon mode commands
3296 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3297 #ifdef CONFIG_FEATURE_VI_COLON
3298 colon(p); // execute the command
3299 #else /* CONFIG_FEATURE_VI_COLON */
3301 p++; // move past the ':'
3302 cnt = strlen((char *) p);
3305 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3306 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3307 if (file_modified && p[1] != '!') {
3308 psbs("No write since last change (:quit! overrides)");
3312 } else if (strncasecmp((char *) p, "write", cnt) == 0
3313 || strncasecmp((char *) p, "wq", cnt) == 0
3314 || strncasecmp((char *) p, "wn", cnt) == 0
3315 || strncasecmp((char *) p, "x", cnt) == 0) {
3316 cnt = file_write(cfn, text, end - 1);
3319 psbs("Write error: %s", strerror(errno));
3322 last_file_modified = -1;
3323 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3324 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3325 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3329 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3330 last_status_cksum = 0; // force status update
3331 } else if (sscanf((char *) p, "%d", &j) > 0) {
3332 dot = find_line(j); // go to line # j
3334 } else { // unrecognised cmd
3337 #endif /* CONFIG_FEATURE_VI_COLON */
3339 case '<': // <- Left shift something
3340 case '>': // >- Right shift something
3341 cnt = count_lines(text, dot); // remember what line we are on
3342 c1 = get_one_char(); // get the type of thing to delete
3343 find_range(&p, &q, c1);
3344 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3347 i = count_lines(p, q); // # of lines we are shifting
3348 for ( ; i > 0; i--, p = next_line(p)) {
3350 // shift left- remove tab or 8 spaces
3352 // shrink buffer 1 char
3353 (void) text_hole_delete(p, p);
3354 } else if (*p == ' ') {
3355 // we should be calculating columns, not just SPACE
3356 for (j = 0; *p == ' ' && j < tabstop; j++) {
3357 (void) text_hole_delete(p, p);
3360 } else if (c == '>') {
3361 // shift right -- add tab or 8 spaces
3362 (void) char_insert(p, '\t');
3365 dot = find_line(cnt); // what line were we on
3367 end_cmd_q(); // stop adding to q
3369 case 'A': // A- append at e-o-l
3370 dot_end(); // go to e-o-l
3371 //**** fall thru to ... 'a'
3372 case 'a': // a- append after current char
3377 case 'B': // B- back a blank-delimited Word
3378 case 'E': // E- end of a blank-delimited word
3379 case 'W': // W- forward a blank-delimited word
3386 if (c == 'W' || isspace(dot[dir])) {
3387 dot = skip_thing(dot, 1, dir, S_TO_WS);
3388 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3391 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3393 case 'C': // C- Change to e-o-l
3394 case 'D': // D- delete to e-o-l
3396 dot = dollar_line(dot); // move to before NL
3397 // copy text into a register and delete
3398 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3400 goto dc_i; // start inserting
3401 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3403 end_cmd_q(); // stop adding to q
3404 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3406 case 'G': // G- goto to a line number (default= E-O-F)
3407 dot = end - 1; // assume E-O-F
3409 dot = find_line(cmdcnt); // what line is #cmdcnt
3413 case 'H': // H- goto top line on screen
3415 if (cmdcnt > (rows - 1)) {
3416 cmdcnt = (rows - 1);
3423 case 'I': // I- insert before first non-blank
3426 //**** fall thru to ... 'i'
3427 case 'i': // i- insert before current char
3428 case VI_K_INSERT: // Cursor Key Insert
3430 cmd_mode = 1; // start insrting
3432 case 'J': // J- join current and next lines together
3436 dot_end(); // move to NL
3437 if (dot < end - 1) { // make sure not last char in text[]
3438 *dot++ = ' '; // replace NL with space
3440 while (isblnk(*dot)) { // delete leading WS
3444 end_cmd_q(); // stop adding to q
3446 case 'L': // L- goto bottom line on screen
3448 if (cmdcnt > (rows - 1)) {
3449 cmdcnt = (rows - 1);
3457 case 'M': // M- goto middle line on screen
3459 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3460 dot = next_line(dot);
3462 case 'O': // O- open a empty line above
3464 p = begin_line(dot);
3465 if (p[-1] == '\n') {
3467 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3469 dot = char_insert(dot, '\n');
3472 dot = char_insert(dot, '\n'); // i\n ESC
3477 case 'R': // R- continuous Replace char
3481 case 'X': // X- delete char before dot
3482 case 'x': // x- delete the current char
3483 case 's': // s- substitute the current char
3490 if (dot[dir] != '\n') {
3492 dot--; // delete prev char
3493 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3496 goto dc_i; // start insrting
3497 end_cmd_q(); // stop adding to q
3499 case 'Z': // Z- if modified, {write}; exit
3500 // ZZ means to save file (if necessary), then exit
3501 c1 = get_one_char();
3506 if (file_modified) {
3507 #ifdef CONFIG_FEATURE_VI_READONLY
3508 if (vi_readonly || readonly) {
3509 psbs("\"%s\" File is read only", cfn);
3512 #endif /* CONFIG_FEATURE_VI_READONLY */
3513 cnt = file_write(cfn, text, end - 1);
3516 psbs("Write error: %s", strerror(errno));
3517 } else if (cnt == (end - 1 - text + 1)) {
3524 case '^': // ^- move to first non-blank on line
3528 case 'b': // b- back a word
3529 case 'e': // e- end of word
3536 if ((dot + dir) < text || (dot + dir) > end - 1)
3539 if (isspace(*dot)) {
3540 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3542 if (isalnum(*dot) || *dot == '_') {
3543 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3544 } else if (ispunct(*dot)) {
3545 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3548 case 'c': // c- change something
3549 case 'd': // d- delete something
3550 #ifdef CONFIG_FEATURE_VI_YANKMARK
3551 case 'y': // y- yank something
3552 case 'Y': // Y- Yank a line
3553 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3554 yf = YANKDEL; // assume either "c" or "d"
3555 #ifdef CONFIG_FEATURE_VI_YANKMARK
3556 if (c == 'y' || c == 'Y')
3558 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3561 c1 = get_one_char(); // get the type of thing to delete
3562 find_range(&p, &q, c1);
3563 if (c1 == 27) { // ESC- user changed mind and wants out
3564 c = c1 = 27; // Escape- do nothing
3565 } else if (strchr("wW", c1)) {
3567 // don't include trailing WS as part of word
3568 while (isblnk(*q)) {
3569 if (q <= text || q[-1] == '\n')
3574 dot = yank_delete(p, q, 0, yf); // delete word
3575 } else if (strchr("^0bBeEft$", c1)) {
3576 // single line copy text into a register and delete
3577 dot = yank_delete(p, q, 0, yf); // delete word
3578 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3579 // multiple line copy text into a register and delete
3580 dot = yank_delete(p, q, 1, yf); // delete lines
3582 dot = char_insert(dot, '\n');
3583 // on the last line of file don't move to prev line
3584 if (dot != (end-1)) {
3587 } else if (c == 'd') {
3592 // could not recognize object
3593 c = c1 = 27; // error-
3597 // if CHANGING, not deleting, start inserting after the delete
3599 strcpy((char *) buf, "Change");
3600 goto dc_i; // start inserting
3603 strcpy((char *) buf, "Delete");
3605 #ifdef CONFIG_FEATURE_VI_YANKMARK
3606 if (c == 'y' || c == 'Y') {
3607 strcpy((char *) buf, "Yank");
3610 q = p + strlen((char *) p);
3611 for (cnt = 0; p <= q; p++) {
3615 psb("%s %d lines (%d chars) using [%c]",
3616 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3617 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3618 end_cmd_q(); // stop adding to q
3621 case 'k': // k- goto prev line, same col
3622 case VI_K_UP: // cursor key Up
3627 dot = move_to_col(dot, ccol + offset); // try stay in same col
3629 case 'r': // r- replace the current char with user input
3630 c1 = get_one_char(); // get the replacement char
3633 file_modified++; // has the file been modified
3635 end_cmd_q(); // stop adding to q
3637 case 't': // t- move to char prior to next x
3638 last_forward_char = get_one_char();
3640 if (*dot == last_forward_char)
3642 last_forward_char= 0;
3644 case 'w': // w- forward a word
3648 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3649 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3650 } else if (ispunct(*dot)) { // we are on PUNCT
3651 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3654 dot++; // move over word
3655 if (isspace(*dot)) {
3656 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3660 c1 = get_one_char(); // get the replacement char
3663 cnt = (rows - 2) / 2; // put dot at center
3665 cnt = rows - 2; // put dot at bottom
3666 screenbegin = begin_line(dot); // start dot at top
3667 dot_scroll(cnt, -1);
3669 case '|': // |- move to column "cmdcnt"
3670 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3672 case '~': // ~- flip the case of letters a-z -> A-Z
3676 if (islower(*dot)) {
3677 *dot = toupper(*dot);
3678 file_modified++; // has the file been modified
3679 } else if (isupper(*dot)) {
3680 *dot = tolower(*dot);
3681 file_modified++; // has the file been modified
3684 end_cmd_q(); // stop adding to q
3686 //----- The Cursor and Function Keys -----------------------------
3687 case VI_K_HOME: // Cursor Key Home
3690 // The Fn keys could point to do_macro which could translate them
3691 case VI_K_FUN1: // Function Key F1
3692 case VI_K_FUN2: // Function Key F2
3693 case VI_K_FUN3: // Function Key F3
3694 case VI_K_FUN4: // Function Key F4
3695 case VI_K_FUN5: // Function Key F5
3696 case VI_K_FUN6: // Function Key F6
3697 case VI_K_FUN7: // Function Key F7
3698 case VI_K_FUN8: // Function Key F8
3699 case VI_K_FUN9: // Function Key F9
3700 case VI_K_FUN10: // Function Key F10
3701 case VI_K_FUN11: // Function Key F11
3702 case VI_K_FUN12: // Function Key F12
3707 // if text[] just became empty, add back an empty line
3709 (void) char_insert(text, '\n'); // start empty buf with dummy line
3712 // it is OK for dot to exactly equal to end, otherwise check dot validity
3714 dot = bound_dot(dot); // make sure "dot" is valid
3716 #ifdef CONFIG_FEATURE_VI_YANKMARK
3717 check_context(c); // update the current context
3718 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3721 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3722 cnt = dot - begin_line(dot);
3723 // Try to stay off of the Newline
3724 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3728 #ifdef CONFIG_FEATURE_VI_CRASHME
3729 static int totalcmds = 0;
3730 static int Mp = 85; // Movement command Probability
3731 static int Np = 90; // Non-movement command Probability
3732 static int Dp = 96; // Delete command Probability
3733 static int Ip = 97; // Insert command Probability
3734 static int Yp = 98; // Yank command Probability
3735 static int Pp = 99; // Put command Probability
3736 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3737 char chars[20] = "\t012345 abcdABCD-=.$";
3738 char *words[20] = { "this", "is", "a", "test",
3739 "broadcast", "the", "emergency", "of",
3740 "system", "quick", "brown", "fox",
3741 "jumped", "over", "lazy", "dogs",
3742 "back", "January", "Febuary", "March"
3745 "You should have received a copy of the GNU General Public License\n",
3746 "char c, cm, *cmd, *cmd1;\n",
3747 "generate a command by percentages\n",
3748 "Numbers may be typed as a prefix to some commands.\n",
3749 "Quit, discarding changes!\n",
3750 "Forced write, if permission originally not valid.\n",
3751 "In general, any ex or ed command (such as substitute or delete).\n",
3752 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3753 "Please get w/ me and I will go over it with you.\n",
3754 "The following is a list of scheduled, committed changes.\n",
3755 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3756 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3757 "Any question about transactions please contact Sterling Huxley.\n",
3758 "I will try to get back to you by Friday, December 31.\n",
3759 "This Change will be implemented on Friday.\n",
3760 "Let me know if you have problems accessing this;\n",
3761 "Sterling Huxley recently added you to the access list.\n",
3762 "Would you like to go to lunch?\n",
3763 "The last command will be automatically run.\n",
3764 "This is too much english for a computer geek.\n",
3766 char *multilines[20] = {
3767 "You should have received a copy of the GNU General Public License\n",
3768 "char c, cm, *cmd, *cmd1;\n",
3769 "generate a command by percentages\n",
3770 "Numbers may be typed as a prefix to some commands.\n",
3771 "Quit, discarding changes!\n",
3772 "Forced write, if permission originally not valid.\n",
3773 "In general, any ex or ed command (such as substitute or delete).\n",
3774 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3775 "Please get w/ me and I will go over it with you.\n",
3776 "The following is a list of scheduled, committed changes.\n",
3777 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3778 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3779 "Any question about transactions please contact Sterling Huxley.\n",
3780 "I will try to get back to you by Friday, December 31.\n",
3781 "This Change will be implemented on Friday.\n",
3782 "Let me know if you have problems accessing this;\n",
3783 "Sterling Huxley recently added you to the access list.\n",
3784 "Would you like to go to lunch?\n",
3785 "The last command will be automatically run.\n",
3786 "This is too much english for a computer geek.\n",
3789 // create a random command to execute
3790 static void crash_dummy()
3792 static int sleeptime; // how long to pause between commands
3793 char c, cm, *cmd, *cmd1;
3794 int i, cnt, thing, rbi, startrbi, percent;
3796 // "dot" movement commands
3797 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3799 // is there already a command running?
3800 if (readed_for_parse > 0)
3804 sleeptime = 0; // how long to pause between commands
3805 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3806 // generate a command by percentages
3807 percent = (int) lrand48() % 100; // get a number from 0-99
3808 if (percent < Mp) { // Movement commands
3809 // available commands
3812 } else if (percent < Np) { // non-movement commands
3813 cmd = "mz<>\'\""; // available commands
3815 } else if (percent < Dp) { // Delete commands
3816 cmd = "dx"; // available commands
3818 } else if (percent < Ip) { // Inset commands
3819 cmd = "iIaAsrJ"; // available commands
3821 } else if (percent < Yp) { // Yank commands
3822 cmd = "yY"; // available commands
3824 } else if (percent < Pp) { // Put commands
3825 cmd = "pP"; // available commands
3828 // We do not know how to handle this command, try again
3832 // randomly pick one of the available cmds from "cmd[]"
3833 i = (int) lrand48() % strlen(cmd);
3835 if (strchr(":\024", cm))
3836 goto cd0; // dont allow colon or ctrl-T commands
3837 readbuffer[rbi++] = cm; // put cmd into input buffer
3839 // now we have the command-
3840 // there are 1, 2, and multi char commands
3841 // find out which and generate the rest of command as necessary
3842 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3843 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3844 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3845 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3847 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3849 readbuffer[rbi++] = c; // add movement to input buffer
3851 if (strchr("iIaAsc", cm)) { // multi-char commands
3853 // change some thing
3854 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3856 readbuffer[rbi++] = c; // add movement to input buffer
3858 thing = (int) lrand48() % 4; // what thing to insert
3859 cnt = (int) lrand48() % 10; // how many to insert
3860 for (i = 0; i < cnt; i++) {
3861 if (thing == 0) { // insert chars
3862 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3863 } else if (thing == 1) { // insert words
3864 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3865 strcat((char *) readbuffer, " ");
3866 sleeptime = 0; // how fast to type
3867 } else if (thing == 2) { // insert lines
3868 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3869 sleeptime = 0; // how fast to type
3870 } else { // insert multi-lines
3871 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3872 sleeptime = 0; // how fast to type
3875 strcat((char *) readbuffer, "\033");
3877 readed_for_parse = strlen(readbuffer);
3881 (void) mysleep(sleeptime); // sleep 1/100 sec
3884 // test to see if there are any errors
3885 static void crash_test()
3887 static time_t oldtim;
3889 char d[2], msg[BUFSIZ];
3893 strcat((char *) msg, "end<text ");
3895 if (end > textend) {
3896 strcat((char *) msg, "end>textend ");
3899 strcat((char *) msg, "dot<text ");
3902 strcat((char *) msg, "dot>end ");
3904 if (screenbegin < text) {
3905 strcat((char *) msg, "screenbegin<text ");
3907 if (screenbegin > end - 1) {
3908 strcat((char *) msg, "screenbegin>end-1 ");
3911 if (strlen(msg) > 0) {
3913 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3914 totalcmds, last_input_char, msg, SOs, SOn);
3916 while (read(0, d, 1) > 0) {
3917 if (d[0] == '\n' || d[0] == '\r')
3922 tim = (time_t) time((time_t *) 0);
3923 if (tim >= (oldtim + 3)) {
3924 sprintf((char *) status_buffer,
3925 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3926 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3931 #endif /* CONFIG_FEATURE_VI_CRASHME */