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"
26 #define ENABLE_FEATURE_VI_CRASHME 0
28 #if ENABLE_LOCALE_SUPPORT
29 #define Isprint(c) isprint((c))
31 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
34 #define MAX_SCR_COLS BUFSIZ
36 // Misc. non-Ascii keys that report an escape sequence
37 #define VI_K_UP 128 // cursor key Up
38 #define VI_K_DOWN 129 // cursor key Down
39 #define VI_K_RIGHT 130 // Cursor Key Right
40 #define VI_K_LEFT 131 // cursor key Left
41 #define VI_K_HOME 132 // Cursor Key Home
42 #define VI_K_END 133 // Cursor Key End
43 #define VI_K_INSERT 134 // Cursor Key Insert
44 #define VI_K_PAGEUP 135 // Cursor Key Page Up
45 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
46 #define VI_K_FUN1 137 // Function Key F1
47 #define VI_K_FUN2 138 // Function Key F2
48 #define VI_K_FUN3 139 // Function Key F3
49 #define VI_K_FUN4 140 // Function Key F4
50 #define VI_K_FUN5 141 // Function Key F5
51 #define VI_K_FUN6 142 // Function Key F6
52 #define VI_K_FUN7 143 // Function Key F7
53 #define VI_K_FUN8 144 // Function Key F8
54 #define VI_K_FUN9 145 // Function Key F9
55 #define VI_K_FUN10 146 // Function Key F10
56 #define VI_K_FUN11 147 // Function Key F11
57 #define VI_K_FUN12 148 // Function Key F12
59 /* vt102 typical ESC sequence */
60 /* terminal standout start/normal ESC sequence */
61 static const char SOs[] = "\033[7m";
62 static const char SOn[] = "\033[0m";
63 /* terminal bell sequence */
64 static const char bell[] = "\007";
65 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
66 static const char Ceol[] = "\033[0K";
67 static const char Ceos [] = "\033[0J";
68 /* Cursor motion arbitrary destination ESC sequence */
69 static const char CMrc[] = "\033[%d;%dH";
70 /* Cursor motion up and down ESC sequence */
71 static const char CMup[] = "\033[A";
72 static const char CMdown[] = "\n";
78 FORWARD = 1, // code depends on "1" for array index
79 BACK = -1, // code depends on "-1" for array index
80 LIMITED = 0, // how much of text[] in char_search
81 FULL = 1, // how much of text[] in char_search
83 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
84 S_TO_WS = 2, // used in skip_thing() for moving "dot"
85 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
86 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
87 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
90 typedef unsigned char Byte;
93 #define VI_AUTOINDENT 1
94 #define VI_SHOWMATCH 2
95 #define VI_IGNORECASE 4
96 #define VI_ERR_METHOD 8
97 #define autoindent (vi_setops & VI_AUTOINDENT)
98 #define showmatch (vi_setops & VI_SHOWMATCH )
99 #define ignorecase (vi_setops & VI_IGNORECASE)
100 /* indicate error with beep or flash */
101 #define err_method (vi_setops & VI_ERR_METHOD)
104 static int editing; // >0 while we are editing a file
105 static int cmd_mode; // 0=command 1=insert 2=replace
106 static int file_modified; // buffer contents changed
107 static int last_file_modified = -1;
108 static int fn_start; // index of first cmd line file name
109 static int save_argc; // how many file names on cmd line
110 static int cmdcnt; // repetition count
111 static fd_set rfds; // use select() for small sleeps
112 static struct timeval tv; // use select() for small sleeps
113 static int rows, columns; // the terminal screen is this size
114 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
115 static Byte *status_buffer; // mesages to the user
116 #define STATUS_BUFFER_LEN 200
117 static int have_status_msg; // is default edit status needed?
118 static int last_status_cksum; // hash of current status line
119 static Byte *cfn; // previous, current, and next file name
120 static Byte *text, *end; // pointers to the user data in memory
121 static Byte *screen; // pointer to the virtual screen buffer
122 static int screensize; // and its size
123 static Byte *screenbegin; // index into text[], of top line on the screen
124 static Byte *dot; // where all the action takes place
126 static struct termios term_orig, term_vi; // remember what the cooked mode was
127 static Byte erase_char; // the users erase character
128 static Byte last_input_char; // last char read from user
129 static Byte last_forward_char; // last char searched for with 'f'
131 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
132 static int last_row; // where the cursor was last moved to
134 #if ENABLE_FEATURE_VI_USE_SIGNALS
135 static jmp_buf restart; // catch_sig()
137 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
140 #if ENABLE_FEATURE_VI_DOT_CMD
141 static int adding2q; // are we currently adding user input to q
142 static Byte *last_modifying_cmd; // last modifying cmd for "."
143 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
145 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
146 static Byte *modifying_cmds; // cmds that modify text[]
148 #if ENABLE_FEATURE_VI_READONLY
149 static int vi_readonly, readonly;
151 #if ENABLE_FEATURE_VI_YANKMARK
152 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
153 static int YDreg, Ureg; // default delete register and orig line for "U"
154 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
155 static Byte *context_start, *context_end;
157 #if ENABLE_FEATURE_VI_SEARCH
158 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
162 static void edit_file(Byte *); // edit one file
163 static void do_cmd(Byte); // execute a command
164 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
165 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
166 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
167 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
168 static Byte *next_line(Byte *); // return pointer to next line B-o-l
169 static Byte *end_screen(void); // get pointer to last char on screen
170 static int count_lines(Byte *, Byte *); // count line from start to stop
171 static Byte *find_line(int); // find begining of line #li
172 static Byte *move_to_col(Byte *, int); // move "p" to column l
173 static int isblnk(Byte); // is the char a blank or tab
174 static void dot_left(void); // move dot left- dont leave line
175 static void dot_right(void); // move dot right- dont leave line
176 static void dot_begin(void); // move dot to B-o-l
177 static void dot_end(void); // move dot to E-o-l
178 static void dot_next(void); // move dot to next line B-o-l
179 static void dot_prev(void); // move dot to prev line B-o-l
180 static void dot_scroll(int, int); // move the screen up or down
181 static void dot_skip_over_ws(void); // move dot pat WS
182 static void dot_delete(void); // delete the char at 'dot'
183 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
184 static Byte *new_screen(int, int); // malloc virtual screen memory
185 static Byte *new_text(int); // malloc memory for text[] buffer
186 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
187 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
188 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
189 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
190 static Byte *skip_thing(Byte *, int, int, int); // skip some object
191 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
192 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
193 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
194 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
195 static void show_help(void); // display some help info
196 static void rawmode(void); // set "raw" mode on tty
197 static void cookmode(void); // return to "cooked" mode on tty
198 static int mysleep(int); // sleep for 'h' 1/100 seconds
199 static Byte readit(void); // read (maybe cursor) key from stdin
200 static Byte get_one_char(void); // read 1 char from stdin
201 static int file_size(const Byte *); // what is the byte size of "fn"
202 static int file_insert(Byte *, Byte *, int);
203 static int file_write(Byte *, Byte *, Byte *);
204 static void place_cursor(int, int, int);
205 static void screen_erase(void);
206 static void clear_to_eol(void);
207 static void clear_to_eos(void);
208 static void standout_start(void); // send "start reverse video" sequence
209 static void standout_end(void); // send "end reverse video" sequence
210 static void flash(int); // flash the terminal screen
211 static void show_status_line(void); // put a message on the bottom line
212 static void psb(const char *, ...); // Print Status Buf
213 static void psbs(const char *, ...); // Print Status Buf in standout mode
214 static void ni(Byte *); // display messages
215 static int format_edit_status(void); // format file status on status line
216 static void redraw(int); // force a full screen refresh
217 static void format_line(Byte*, Byte*, int);
218 static void refresh(int); // update the terminal from screen[]
220 static void Indicate_Error(void); // use flash or beep to indicate error
221 #define indicate_error(c) Indicate_Error()
222 static void Hit_Return(void);
224 #if ENABLE_FEATURE_VI_SEARCH
225 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
226 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
228 #if ENABLE_FEATURE_VI_COLON
229 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
230 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
231 static void colon(Byte *); // execute the "colon" mode cmds
233 #if ENABLE_FEATURE_VI_USE_SIGNALS
234 static void winch_sig(int); // catch window size changes
235 static void suspend_sig(int); // catch ctrl-Z
236 static void catch_sig(int); // catch ctrl-C and alarm time-outs
238 #if ENABLE_FEATURE_VI_DOT_CMD
239 static void start_new_cmd_q(Byte); // new queue for command
240 static void end_cmd_q(void); // stop saving input chars
242 #define end_cmd_q() ((void)0)
244 #if ENABLE_FEATURE_VI_SETOPTS
245 static void showmatching(Byte *); // show the matching pair () [] {}
247 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
248 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
250 #if ENABLE_FEATURE_VI_YANKMARK
251 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
252 static Byte what_reg(void); // what is letter of current YDreg
253 static void check_context(Byte); // remember context for '' command
255 #if ENABLE_FEATURE_VI_CRASHME
256 static void crash_dummy();
257 static void crash_test();
258 static int crashme = 0;
262 static void write1(const char *out)
267 int vi_main(int argc, char **argv);
268 int vi_main(int argc, char **argv)
271 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
273 #if ENABLE_FEATURE_VI_YANKMARK
276 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
279 #if ENABLE_FEATURE_VI_CRASHME
280 (void) srand((long) my_pid);
283 status_buffer = (Byte *)STATUS_BUFFER;
284 last_status_cksum = 0;
286 #if ENABLE_FEATURE_VI_READONLY
287 vi_readonly = readonly = FALSE;
288 if (strncmp(argv[0], "view", 4) == 0) {
293 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
294 #if ENABLE_FEATURE_VI_YANKMARK
295 for (i = 0; i < 28; i++) {
297 } // init the yank regs
299 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
300 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
303 // 1- process $HOME/.exrc file
304 // 2- process EXINIT variable from environment
305 // 3- process command line args
306 while ((c = getopt(argc, argv, "hCR")) != -1) {
308 #if ENABLE_FEATURE_VI_CRASHME
313 #if ENABLE_FEATURE_VI_READONLY
314 case 'R': // Read-only flag
319 //case 'r': // recover flag- ignore- we don't use tmp file
320 //case 'x': // encryption flag- ignore
321 //case 'c': // execute command first
322 //case 'h': // help -- just use default
329 // The argv array can be used by the ":next" and ":rewind" commands
331 fn_start = optind; // remember first file name for :next and :rew
334 //----- This is the main file handling loop --------------
335 if (optind >= argc) {
336 editing = 1; // 0= exit, 1= one file, 2= multiple files
339 for (; optind < argc; optind++) {
340 editing = 1; // 0=exit, 1=one file, 2+ =many files
342 cfn = (Byte *) xstrdup(argv[optind]);
346 //-----------------------------------------------------------
351 static void edit_file(Byte * fn)
356 #if ENABLE_FEATURE_VI_USE_SIGNALS
359 #if ENABLE_FEATURE_VI_YANKMARK
360 static Byte *cur_line;
367 if (ENABLE_FEATURE_VI_WIN_RESIZE)
368 get_terminal_width_height(0, &columns, &rows);
369 new_screen(rows, columns); // get memory for virtual screen
371 cnt = file_size(fn); // file size
372 size = 2 * cnt; // 200% of file size
373 new_text(size); // get a text[] buffer
374 screenbegin = dot = end = text;
376 ch= file_insert(fn, text, cnt);
379 (void) char_insert(text, '\n'); // start empty buf with dummy line
382 last_file_modified = -1;
383 #if ENABLE_FEATURE_VI_YANKMARK
384 YDreg = 26; // default Yank/Delete reg
385 Ureg = 27; // hold orig line for "U" cmd
386 for (cnt = 0; cnt < 28; cnt++) {
389 mark[26] = mark[27] = text; // init "previous context"
392 last_forward_char = last_input_char = '\0';
396 #if ENABLE_FEATURE_VI_USE_SIGNALS
398 signal(SIGWINCH, winch_sig);
399 signal(SIGTSTP, suspend_sig);
400 sig = setjmp(restart);
402 screenbegin = dot = text;
407 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
410 offset = 0; // no horizontal offset
412 #if ENABLE_FEATURE_VI_DOT_CMD
413 free(last_modifying_cmd);
415 ioq = ioq_start = last_modifying_cmd = 0;
418 redraw(FALSE); // dont force every col re-draw
421 //------This is the main Vi cmd handling loop -----------------------
422 while (editing > 0) {
423 #if ENABLE_FEATURE_VI_CRASHME
425 if ((end - text) > 1) {
426 crash_dummy(); // generate a random command
430 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
435 last_input_char = c = get_one_char(); // get a cmd from user
436 #if ENABLE_FEATURE_VI_YANKMARK
437 // save a copy of the current line- for the 'U" command
438 if (begin_line(dot) != cur_line) {
439 cur_line = begin_line(dot);
440 text_yank(begin_line(dot), end_line(dot), Ureg);
443 #if ENABLE_FEATURE_VI_DOT_CMD
444 // These are commands that change text[].
445 // Remember the input for the "." command
446 if (!adding2q && ioq_start == 0
447 && strchr((char *) modifying_cmds, c) != NULL) {
451 do_cmd(c); // execute the user command
453 // poll to see if there is input already waiting. if we are
454 // not able to display output fast enough to keep up, skip
455 // the display update until we catch up with input.
456 if (mysleep(0) == 0) {
457 // no input pending- so update output
461 #if ENABLE_FEATURE_VI_CRASHME
463 crash_test(); // test editor variables
466 //-------------------------------------------------------------------
468 place_cursor(rows, 0, FALSE); // go to bottom of screen
469 clear_to_eol(); // Erase to end of line
473 //----- The Colon commands -------------------------------------
474 #if ENABLE_FEATURE_VI_COLON
475 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
480 #if ENABLE_FEATURE_VI_YANKMARK
483 #if ENABLE_FEATURE_VI_SEARCH
484 Byte *pat, buf[BUFSIZ];
487 *addr = -1; // assume no addr
488 if (*p == '.') { // the current line
491 *addr = count_lines(text, q);
492 #if ENABLE_FEATURE_VI_YANKMARK
493 } else if (*p == '\'') { // is this a mark addr
497 if (c >= 'a' && c <= 'z') {
501 if (q != NULL) { // is mark valid
502 *addr = count_lines(text, q); // count lines
506 #if ENABLE_FEATURE_VI_SEARCH
507 } else if (*p == '/') { // a search pattern
515 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
518 q = char_search(dot, pat, FORWARD, FULL);
520 *addr = count_lines(text, q);
524 } else if (*p == '$') { // the last line in file
526 q = begin_line(end - 1);
527 *addr = count_lines(text, q);
528 } else if (isdigit(*p)) { // specific line number
529 sscanf((char *) p, "%d%n", addr, &st);
531 } else { // I don't reconise this
532 // unrecognised address- assume -1
538 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
540 //----- get the address' i.e., 1,3 'a,'b -----
541 // get FIRST addr, if present
543 p++; // skip over leading spaces
544 if (*p == '%') { // alias for 1,$
547 *e = count_lines(text, end-1);
550 p = get_one_address(p, b);
553 if (*p == ',') { // is there a address separator
557 // get SECOND addr, if present
558 p = get_one_address(p, e);
562 p++; // skip over trailing spaces
566 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
567 static void setops(const Byte *args, const char *opname, int flg_no,
568 const char *short_opname, int opt)
570 const char *a = (char *) args + flg_no;
571 int l = strlen(opname) - 1; /* opname have + ' ' */
573 if (strncasecmp(a, opname, l) == 0 ||
574 strncasecmp(a, short_opname, 2) == 0) {
583 static void colon(Byte * buf)
585 Byte c, *orig_buf, *buf1, *q, *r;
586 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
587 int i, l, li, ch, b, e;
588 int useforce = FALSE, forced = FALSE;
591 // :3154 // if (-e line 3154) goto it else stay put
592 // :4,33w! foo // write a portion of buffer to file "foo"
593 // :w // write all of buffer to current file
595 // :q! // quit- dont care about modified file
596 // :'a,'z!sort -u // filter block through sort
597 // :'f // goto mark "f"
598 // :'fl // list literal the mark "f" line
599 // :.r bar // read file "bar" into buffer before dot
600 // :/123/,/abc/d // delete lines from "123" line to "abc" line
601 // :/xyz/ // goto the "xyz" line
602 // :s/find/replace/ // substitute pattern "find" with "replace"
603 // :!<cmd> // run <cmd> then return
606 if (strlen((char *) buf) <= 0)
609 buf++; // move past the ':'
613 q = text; // assume 1,$ for the range
615 li = count_lines(text, end - 1);
616 fn = cfn; // default to current file
617 memset(cmd, '\0', BUFSIZ); // clear cmd[]
618 memset(args, '\0', BUFSIZ); // clear args[]
620 // look for optional address(es) :. :1 :1,9 :'q,'a :%
621 buf = get_address(buf, &b, &e);
623 // remember orig command line
626 // get the COMMAND into cmd[]
628 while (*buf != '\0') {
636 strcpy((char *) args, (char *) buf);
637 buf1 = (Byte*)last_char_is((char *)cmd, '!');
640 *buf1 = '\0'; // get rid of !
643 // if there is only one addr, then the addr
644 // is the line number of the single line the
645 // user wants. So, reset the end
646 // pointer to point at end of the "b" line
647 q = find_line(b); // what line is #b
652 // we were given two addrs. change the
653 // end pointer to the addr given by user.
654 r = find_line(e); // what line is #e
658 // ------------ now look for the command ------------
659 i = strlen((char *) cmd);
660 if (i == 0) { // :123CR goto line #123
662 dot = find_line(b); // what line is #b
666 #if ENABLE_FEATURE_ALLOW_EXEC
667 else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
668 // :!ls run the <cmd>
669 (void) alarm(0); // wait for input- no alarms
670 place_cursor(rows - 1, 0, FALSE); // go to Status line
671 clear_to_eol(); // clear the line
673 system((char*)(orig_buf+1)); // run the cmd
675 Hit_Return(); // let user see results
676 (void) alarm(3); // done waiting for input
679 else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
680 if (b < 0) { // no addr given- use defaults
681 b = e = count_lines(text, dot);
684 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
685 if (b < 0) { // no addr given- use defaults
686 q = begin_line(dot); // assume .,. for the range
689 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
691 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
694 // don't edit, if the current file has been modified
695 if (file_modified && ! useforce) {
696 psbs("No write since last change (:edit! overrides)");
699 if (strlen((char*)args) > 0) {
700 // the user supplied a file name
702 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
703 // no user supplied name- use the current filename
707 // no user file name, no current name- punt
708 psbs("No current filename");
712 // see if file exists- if not, its just a new file request
713 if ((sr=stat((char*)fn, &st_buf)) < 0) {
714 // This is just a request for a new file creation.
715 // The file_insert below will fail but we get
716 // an empty buffer with a file name. Then the "write"
717 // command can do the create.
719 if ((st_buf.st_mode & (S_IFREG)) == 0) {
720 // This is not a regular file
721 psbs("\"%s\" is not a regular file", fn);
724 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
725 // dont have any read permissions
726 psbs("\"%s\" is not readable", fn);
731 // There is a read-able regular file
732 // make this the current file
733 q = (Byte *) xstrdup((char *) fn); // save the cfn
734 free(cfn); // free the old name
735 cfn = q; // remember new cfn
738 // delete all the contents of text[]
739 new_text(2 * file_size(fn));
740 screenbegin = dot = end = text;
743 ch = file_insert(fn, text, file_size(fn));
746 // start empty buf with dummy line
747 (void) char_insert(text, '\n');
751 last_file_modified = -1;
752 #if ENABLE_FEATURE_VI_YANKMARK
753 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
754 free(reg[Ureg]); // free orig line reg- for 'U'
757 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
758 free(reg[YDreg]); // free default yank/delete register
761 for (li = 0; li < 28; li++) {
765 // how many lines in text[]?
766 li = count_lines(text, end - 1);
768 #if ENABLE_FEATURE_VI_READONLY
772 (sr < 0 ? " [New file]" : ""),
773 #if ENABLE_FEATURE_VI_READONLY
774 ((vi_readonly || readonly) ? " [Read only]" : ""),
777 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
778 if (b != -1 || e != -1) {
779 ni((Byte *) "No address allowed on this command");
782 if (strlen((char *) args) > 0) {
783 // user wants a new filename
785 cfn = (Byte *) xstrdup((char *) args);
787 // user wants file status info
788 last_status_cksum = 0; // force status update
790 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
791 // print out values of all features
792 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
793 clear_to_eol(); // clear the line
798 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
799 if (b < 0) { // no addr given- use defaults
800 q = begin_line(dot); // assume .,. for the range
803 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
804 clear_to_eol(); // clear the line
806 for (; q <= r; q++) {
810 c_is_no_print = c > 127 && !Isprint(c);
817 } else if (c < ' ' || c == 127) {
828 #if ENABLE_FEATURE_VI_SET
832 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
833 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
835 // force end of argv list
842 // don't exit if the file been modified
844 psbs("No write since last change (:%s! overrides)",
845 (*cmd == 'q' ? "quit" : "next"));
848 // are there other file to edit
849 if (*cmd == 'q' && optind < save_argc - 1) {
850 psbs("%d more file to edit", (save_argc - optind - 1));
853 if (*cmd == 'n' && optind >= save_argc - 1) {
854 psbs("No more files to edit");
858 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
860 if (strlen((char *) fn) <= 0) {
861 psbs("No filename given");
864 if (b < 0) { // no addr given- use defaults
865 q = begin_line(dot); // assume "dot"
867 // read after current line- unless user said ":0r foo"
870 #if ENABLE_FEATURE_VI_READONLY
871 l= readonly; // remember current files' status
873 ch = file_insert(fn, q, file_size(fn));
874 #if ENABLE_FEATURE_VI_READONLY
878 goto vc1; // nothing was inserted
879 // how many lines in text[]?
880 li = count_lines(q, q + ch - 1);
882 #if ENABLE_FEATURE_VI_READONLY
886 #if ENABLE_FEATURE_VI_READONLY
887 ((vi_readonly || readonly) ? " [Read only]" : ""),
891 // if the insert is before "dot" then we need to update
896 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
897 if (file_modified && ! useforce) {
898 psbs("No write since last change (:rewind! overrides)");
900 // reset the filenames to edit
901 optind = fn_start - 1;
904 #if ENABLE_FEATURE_VI_SET
905 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
906 i = 0; // offset into args
907 if (strlen((char *) args) == 0) {
908 // print out values of all options
909 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
910 clear_to_eol(); // clear the line
911 printf("----------------------------------------\r\n");
912 #if ENABLE_FEATURE_VI_SETOPTS
915 printf("autoindent ");
921 printf("ignorecase ");
924 printf("showmatch ");
925 printf("tabstop=%d ", tabstop);
930 if (strncasecmp((char *) args, "no", 2) == 0)
931 i = 2; // ":set noautoindent"
932 #if ENABLE_FEATURE_VI_SETOPTS
933 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
934 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
935 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
936 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
937 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
938 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
939 if (ch > 0 && ch < columns - 1)
942 #endif /* FEATURE_VI_SETOPTS */
943 #endif /* FEATURE_VI_SET */
944 #if ENABLE_FEATURE_VI_SEARCH
945 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
949 // F points to the "find" pattern
950 // R points to the "replace" pattern
951 // replace the cmd line delimiters "/" with NULLs
952 gflag = 0; // global replace flag
953 c = orig_buf[1]; // what is the delimiter
954 F = orig_buf + 2; // start of "find"
955 R = (Byte *) strchr((char *) F, c); // middle delimiter
956 if (!R) goto colon_s_fail;
957 *R++ = '\0'; // terminate "find"
958 buf1 = (Byte *) strchr((char *) R, c);
959 if (!buf1) goto colon_s_fail;
960 *buf1++ = '\0'; // terminate "replace"
961 if (*buf1 == 'g') { // :s/foo/bar/g
963 gflag++; // turn on gflag
966 if (b < 0) { // maybe :s/foo/bar/
967 q = begin_line(dot); // start with cur line
968 b = count_lines(text, q); // cur line number
971 e = b; // maybe :.s/foo/bar/
972 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
973 ls = q; // orig line start
975 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
977 // we found the "find" pattern- delete it
978 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
979 // inset the "replace" patern
980 (void) string_insert(buf1, R); // insert the string
981 // check for "global" :s/foo/bar/g
983 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
984 q = buf1 + strlen((char *) R);
985 goto vc4; // don't let q move past cur line
991 #endif /* FEATURE_VI_SEARCH */
992 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
993 psb("%s", BB_VER " " BB_BT);
994 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
995 || strncasecmp((char *) cmd, "wq", i) == 0
996 || strncasecmp((char *) cmd, "wn", i) == 0
997 || strncasecmp((char *) cmd, "x", i) == 0) {
998 // is there a file name to write to?
999 if (strlen((char *) args) > 0) {
1002 #if ENABLE_FEATURE_VI_READONLY
1003 if ((vi_readonly || readonly) && ! useforce) {
1004 psbs("\"%s\" File is read only", fn);
1008 // how many lines in text[]?
1009 li = count_lines(q, r);
1011 // see if file exists- if not, its just a new file request
1013 // if "fn" is not write-able, chmod u+w
1014 // sprintf(syscmd, "chmod u+w %s", fn);
1018 l = file_write(fn, q, r);
1019 if (useforce && forced) {
1021 // sprintf(syscmd, "chmod u-w %s", fn);
1027 psbs("Write error: %s", strerror(errno));
1029 psb("\"%s\" %dL, %dC", fn, li, l);
1030 if (q == text && r == end - 1 && l == ch) {
1032 last_file_modified = -1;
1034 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1035 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1040 #if ENABLE_FEATURE_VI_READONLY
1043 #if ENABLE_FEATURE_VI_YANKMARK
1044 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1045 if (b < 0) { // no addr given- use defaults
1046 q = begin_line(dot); // assume .,. for the range
1049 text_yank(q, r, YDreg);
1050 li = count_lines(q, r);
1051 psb("Yank %d lines (%d chars) into [%c]",
1052 li, strlen((char *) reg[YDreg]), what_reg());
1059 dot = bound_dot(dot); // make sure "dot" is valid
1061 #if ENABLE_FEATURE_VI_SEARCH
1063 psb(":s expression missing delimiters");
1067 #endif /* FEATURE_VI_COLON */
1069 static void Hit_Return(void)
1073 standout_start(); // start reverse video
1074 write1("[Hit return to continue]");
1075 standout_end(); // end reverse video
1076 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1078 redraw(TRUE); // force redraw all
1081 //----- Synchronize the cursor to Dot --------------------------
1082 static void sync_cursor(Byte * d, int *row, int *col)
1084 Byte *beg_cur; // begin and end of "d" line
1085 Byte *end_scr; // begin and end of screen
1089 beg_cur = begin_line(d); // first char of cur line
1091 end_scr = end_screen(); // last char of screen
1093 if (beg_cur < screenbegin) {
1094 // "d" is before top line on screen
1095 // how many lines do we have to move
1096 cnt = count_lines(beg_cur, screenbegin);
1098 screenbegin = beg_cur;
1099 if (cnt > (rows - 1) / 2) {
1100 // we moved too many lines. put "dot" in middle of screen
1101 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1102 screenbegin = prev_line(screenbegin);
1105 } else if (beg_cur > end_scr) {
1106 // "d" is after bottom line on screen
1107 // how many lines do we have to move
1108 cnt = count_lines(end_scr, beg_cur);
1109 if (cnt > (rows - 1) / 2)
1110 goto sc1; // too many lines
1111 for (ro = 0; ro < cnt - 1; ro++) {
1112 // move screen begin the same amount
1113 screenbegin = next_line(screenbegin);
1114 // now, move the end of screen
1115 end_scr = next_line(end_scr);
1116 end_scr = end_line(end_scr);
1119 // "d" is on screen- find out which row
1121 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1127 // find out what col "d" is on
1129 do { // drive "co" to correct column
1130 if (*tp == '\n' || *tp == '\0')
1134 co += ((tabstop - 1) - (co % tabstop));
1135 } else if (*tp < ' ' || *tp == 127) {
1136 co++; // display as ^X, use 2 columns
1138 } while (tp++ < d && ++co);
1140 // "co" is the column where "dot" is.
1141 // The screen has "columns" columns.
1142 // The currently displayed columns are 0+offset -- columns+ofset
1143 // |-------------------------------------------------------------|
1145 // offset | |------- columns ----------------|
1147 // If "co" is already in this range then we do not have to adjust offset
1148 // but, we do have to subtract the "offset" bias from "co".
1149 // If "co" is outside this range then we have to change "offset".
1150 // If the first char of a line is a tab the cursor will try to stay
1151 // in column 7, but we have to set offset to 0.
1153 if (co < 0 + offset) {
1156 if (co >= columns + offset) {
1157 offset = co - columns + 1;
1159 // if the first char of the line is a tab, and "dot" is sitting on it
1160 // force offset to 0.
1161 if (d == beg_cur && *d == '\t') {
1170 //----- Text Movement Routines ---------------------------------
1171 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1173 while (p > text && p[-1] != '\n')
1174 p--; // go to cur line B-o-l
1178 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1180 while (p < end - 1 && *p != '\n')
1181 p++; // go to cur line E-o-l
1185 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1187 while (p < end - 1 && *p != '\n')
1188 p++; // go to cur line E-o-l
1189 // Try to stay off of the Newline
1190 if (*p == '\n' && (p - begin_line(p)) > 0)
1195 static Byte *prev_line(Byte * p) // return pointer first char prev line
1197 p = begin_line(p); // goto begining of cur line
1198 if (p[-1] == '\n' && p > text)
1199 p--; // step to prev line
1200 p = begin_line(p); // goto begining of prev line
1204 static Byte *next_line(Byte * p) // return pointer first char next line
1207 if (*p == '\n' && p < end - 1)
1208 p++; // step to next line
1212 //----- Text Information Routines ------------------------------
1213 static Byte *end_screen(void)
1218 // find new bottom line
1220 for (cnt = 0; cnt < rows - 2; cnt++)
1226 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1231 if (stop < start) { // start and stop are backwards- reverse them
1237 stop = end_line(stop); // get to end of this line
1238 for (q = start; q <= stop && q <= end - 1; q++) {
1245 static Byte *find_line(int li) // find begining of line #li
1249 for (q = text; li > 1; li--) {
1255 //----- Dot Movement Routines ----------------------------------
1256 static void dot_left(void)
1258 if (dot > text && dot[-1] != '\n')
1262 static void dot_right(void)
1264 if (dot < end - 1 && *dot != '\n')
1268 static void dot_begin(void)
1270 dot = begin_line(dot); // return pointer to first char cur line
1273 static void dot_end(void)
1275 dot = end_line(dot); // return pointer to last char cur line
1278 static Byte *move_to_col(Byte * p, int l)
1285 if (*p == '\n' || *p == '\0')
1289 co += ((tabstop - 1) - (co % tabstop));
1290 } else if (*p < ' ' || *p == 127) {
1291 co++; // display as ^X, use 2 columns
1293 } while (++co <= l && p++ < end);
1297 static void dot_next(void)
1299 dot = next_line(dot);
1302 static void dot_prev(void)
1304 dot = prev_line(dot);
1307 static void dot_scroll(int cnt, int dir)
1311 for (; cnt > 0; cnt--) {
1314 // ctrl-Y scroll up one line
1315 screenbegin = prev_line(screenbegin);
1318 // ctrl-E scroll down one line
1319 screenbegin = next_line(screenbegin);
1322 // make sure "dot" stays on the screen so we dont scroll off
1323 if (dot < screenbegin)
1325 q = end_screen(); // find new bottom line
1327 dot = begin_line(q); // is dot is below bottom line?
1331 static void dot_skip_over_ws(void)
1334 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1338 static void dot_delete(void) // delete the char at 'dot'
1340 (void) text_hole_delete(dot, dot);
1343 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1345 if (p >= end && end > text) {
1347 indicate_error('1');
1351 indicate_error('2');
1356 //----- Helper Utility Routines --------------------------------
1358 //----------------------------------------------------------------
1359 //----- Char Routines --------------------------------------------
1360 /* Chars that are part of a word-
1361 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1362 * Chars that are Not part of a word (stoppers)
1363 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1364 * Chars that are WhiteSpace
1365 * TAB NEWLINE VT FF RETURN SPACE
1366 * DO NOT COUNT NEWLINE AS WHITESPACE
1369 static Byte *new_screen(int ro, int co)
1374 screensize = ro * co + 8;
1375 screen = xmalloc(screensize);
1376 // initialize the new screen. assume this will be a empty file.
1378 // non-existent text[] lines start with a tilde (~).
1379 for (li = 1; li < ro - 1; li++) {
1380 screen[(li * co) + 0] = '~';
1385 static Byte *new_text(int size)
1388 size = 10240; // have a minimum size for new files
1390 text = xmalloc(size + 8);
1391 memset(text, '\0', size); // clear new text[]
1392 //text += 4; // leave some room for "oops"
1396 #if ENABLE_FEATURE_VI_SEARCH
1397 static int mycmp(Byte * s1, Byte * s2, int len)
1401 i = strncmp((char *) s1, (char *) s2, len);
1402 #if ENABLE_FEATURE_VI_SETOPTS
1404 i = strncasecmp((char *) s1, (char *) s2, len);
1410 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1412 #ifndef REGEX_SEARCH
1416 len = strlen((char *) pat);
1417 if (dir == FORWARD) {
1418 stop = end - 1; // assume range is p - end-1
1419 if (range == LIMITED)
1420 stop = next_line(p); // range is to next line
1421 for (start = p; start < stop; start++) {
1422 if (mycmp(start, pat, len) == 0) {
1426 } else if (dir == BACK) {
1427 stop = text; // assume range is text - p
1428 if (range == LIMITED)
1429 stop = prev_line(p); // range is to prev line
1430 for (start = p - len; start >= stop; start--) {
1431 if (mycmp(start, pat, len) == 0) {
1436 // pattern not found
1438 #else /*REGEX_SEARCH */
1440 struct re_pattern_buffer preg;
1444 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1450 // assume a LIMITED forward search
1458 // count the number of chars to search over, forward or backward
1462 // RANGE could be negative if we are searching backwards
1465 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1467 // The pattern was not compiled
1468 psbs("bad search pattern: \"%s\": %s", pat, q);
1469 i = 0; // return p if pattern not compiled
1479 // search for the compiled pattern, preg, in p[]
1480 // range < 0- search backward
1481 // range > 0- search forward
1483 // re_search() < 0 not found or error
1484 // re_search() > 0 index of found pattern
1485 // struct pattern char int int int struct reg
1486 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1487 i = re_search(&preg, q, size, 0, range, 0);
1490 i = 0; // return NULL if pattern not found
1493 if (dir == FORWARD) {
1499 #endif /* REGEX_SEARCH */
1501 #endif /* FEATURE_VI_SEARCH */
1503 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1505 if (c == 22) { // Is this an ctrl-V?
1506 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1507 p--; // backup onto ^
1508 refresh(FALSE); // show the ^
1512 file_modified++; // has the file been modified
1513 } else if (c == 27) { // Is this an ESC?
1516 end_cmd_q(); // stop adding to q
1517 last_status_cksum = 0; // force status update
1518 if ((p[-1] != '\n') && (dot>text)) {
1521 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1523 if ((p[-1] != '\n') && (dot>text)) {
1525 p = text_hole_delete(p, p); // shrink buffer 1 char
1528 // insert a char into text[]
1529 Byte *sp; // "save p"
1532 c = '\n'; // translate \r to \n
1533 sp = p; // remember addr of insert
1534 p = stupid_insert(p, c); // insert the char
1535 #if ENABLE_FEATURE_VI_SETOPTS
1536 if (showmatch && strchr(")]}", *sp) != NULL) {
1539 if (autoindent && c == '\n') { // auto indent the new line
1542 q = prev_line(p); // use prev line as templet
1543 for (; isblnk(*q); q++) {
1544 p = stupid_insert(p, *q); // insert the char
1552 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1554 p = text_hole_make(p, 1);
1557 file_modified++; // has the file been modified
1563 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1565 Byte *save_dot, *p, *q;
1571 if (strchr("cdy><", c)) {
1572 // these cmds operate on whole lines
1573 p = q = begin_line(p);
1574 for (cnt = 1; cnt < cmdcnt; cnt++) {
1578 } else if (strchr("^%$0bBeEft", c)) {
1579 // These cmds operate on char positions
1580 do_cmd(c); // execute movement cmd
1582 } else if (strchr("wW", c)) {
1583 do_cmd(c); // execute movement cmd
1584 // if we are at the next word's first char
1585 // step back one char
1586 // but check the possibilities when it is true
1587 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1588 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1589 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1590 dot--; // move back off of next word
1591 if (dot > text && *dot == '\n')
1592 dot--; // stay off NL
1594 } else if (strchr("H-k{", c)) {
1595 // these operate on multi-lines backwards
1596 q = end_line(dot); // find NL
1597 do_cmd(c); // execute movement cmd
1600 } else if (strchr("L+j}\r\n", c)) {
1601 // these operate on multi-lines forwards
1602 p = begin_line(dot);
1603 do_cmd(c); // execute movement cmd
1604 dot_end(); // find NL
1607 c = 27; // error- return an ESC char
1620 static int st_test(Byte * p, int type, int dir, Byte * tested)
1630 if (type == S_BEFORE_WS) {
1632 test = ((!isspace(c)) || c == '\n');
1634 if (type == S_TO_WS) {
1636 test = ((!isspace(c)) || c == '\n');
1638 if (type == S_OVER_WS) {
1640 test = ((isspace(c)));
1642 if (type == S_END_PUNCT) {
1644 test = ((ispunct(c)));
1646 if (type == S_END_ALNUM) {
1648 test = ((isalnum(c)) || c == '_');
1654 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1658 while (st_test(p, type, dir, &c)) {
1659 // make sure we limit search to correct number of lines
1660 if (c == '\n' && --linecnt < 1)
1662 if (dir >= 0 && p >= end - 1)
1664 if (dir < 0 && p <= text)
1666 p += dir; // move to next char
1671 // find matching char of pair () [] {}
1672 static Byte *find_pair(Byte * p, Byte c)
1679 dir = 1; // assume forward
1703 for (q = p + dir; text <= q && q < end; q += dir) {
1704 // look for match, count levels of pairs (( ))
1706 level++; // increase pair levels
1708 level--; // reduce pair level
1710 break; // found matching pair
1713 q = NULL; // indicate no match
1717 #if ENABLE_FEATURE_VI_SETOPTS
1718 // show the matching char of a pair, () [] {}
1719 static void showmatching(Byte * p)
1723 // we found half of a pair
1724 q = find_pair(p, *p); // get loc of matching char
1726 indicate_error('3'); // no matching char
1728 // "q" now points to matching pair
1729 save_dot = dot; // remember where we are
1730 dot = q; // go to new loc
1731 refresh(FALSE); // let the user see it
1732 (void) mysleep(40); // give user some time
1733 dot = save_dot; // go back to old loc
1737 #endif /* FEATURE_VI_SETOPTS */
1739 // open a hole in text[]
1740 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1749 cnt = end - src; // the rest of buffer
1750 if (memmove(dest, src, cnt) != dest) {
1751 psbs("can't create room for new characters");
1753 memset(p, ' ', size); // clear new hole
1754 end = end + size; // adjust the new END
1755 file_modified++; // has the file been modified
1760 // close a hole in text[]
1761 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1766 // move forwards, from beginning
1770 if (q < p) { // they are backward- swap them
1774 hole_size = q - p + 1;
1776 if (src < text || src > end)
1778 if (dest < text || dest >= end)
1781 goto thd_atend; // just delete the end of the buffer
1782 if (memmove(dest, src, cnt) != dest) {
1783 psbs("can't delete the character");
1786 end = end - hole_size; // adjust the new END
1788 dest = end - 1; // make sure dest in below end-1
1790 dest = end = text; // keep pointers valid
1791 file_modified++; // has the file been modified
1796 // copy text into register, then delete text.
1797 // if dist <= 0, do not include, or go past, a NewLine
1799 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1803 // make sure start <= stop
1805 // they are backwards, reverse them
1811 // we cannot cross NL boundaries
1815 // dont go past a NewLine
1816 for (; p + 1 <= stop; p++) {
1818 stop = p; // "stop" just before NewLine
1824 #if ENABLE_FEATURE_VI_YANKMARK
1825 text_yank(start, stop, YDreg);
1827 if (yf == YANKDEL) {
1828 p = text_hole_delete(start, stop);
1833 static void show_help(void)
1835 puts("These features are available:"
1836 #if ENABLE_FEATURE_VI_SEARCH
1837 "\n\tPattern searches with / and ?"
1839 #if ENABLE_FEATURE_VI_DOT_CMD
1840 "\n\tLast command repeat with \'.\'"
1842 #if ENABLE_FEATURE_VI_YANKMARK
1843 "\n\tLine marking with 'x"
1844 "\n\tNamed buffers with \"x"
1846 #if ENABLE_FEATURE_VI_READONLY
1847 "\n\tReadonly if vi is called as \"view\""
1848 "\n\tReadonly with -R command line arg"
1850 #if ENABLE_FEATURE_VI_SET
1851 "\n\tSome colon mode commands with \':\'"
1853 #if ENABLE_FEATURE_VI_SETOPTS
1854 "\n\tSettable options with \":set\""
1856 #if ENABLE_FEATURE_VI_USE_SIGNALS
1857 "\n\tSignal catching- ^C"
1858 "\n\tJob suspend and resume with ^Z"
1860 #if ENABLE_FEATURE_VI_WIN_RESIZE
1861 "\n\tAdapt to window re-sizes"
1866 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1871 strcpy((char *) buf, ""); // init buf
1872 if (strlen((char *) s) <= 0)
1873 s = (Byte *) "(NULL)";
1874 for (; *s > '\0'; s++) {
1878 c_is_no_print = c > 127 && !Isprint(c);
1879 if (c_is_no_print) {
1880 strcat((char *) buf, SOn);
1883 if (c < ' ' || c == 127) {
1884 strcat((char *) buf, "^");
1891 strcat((char *) buf, (char *) b);
1893 strcat((char *) buf, SOs);
1895 strcat((char *) buf, "$");
1900 #if ENABLE_FEATURE_VI_DOT_CMD
1901 static void start_new_cmd_q(Byte c)
1904 free(last_modifying_cmd);
1905 // get buffer for new cmd
1906 last_modifying_cmd = xmalloc(BUFSIZ);
1907 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1908 // if there is a current cmd count put it in the buffer first
1910 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1911 else // just save char c onto queue
1912 last_modifying_cmd[0] = c;
1916 static void end_cmd_q(void)
1918 #if ENABLE_FEATURE_VI_YANKMARK
1919 YDreg = 26; // go back to default Yank/Delete reg
1924 #endif /* FEATURE_VI_DOT_CMD */
1926 #if ENABLE_FEATURE_VI_YANKMARK \
1927 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1928 || ENABLE_FEATURE_VI_CRASHME
1929 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1933 i = strlen((char *) s);
1934 p = text_hole_make(p, i);
1935 strncpy((char *) p, (char *) s, i);
1936 for (cnt = 0; *s != '\0'; s++) {
1940 #if ENABLE_FEATURE_VI_YANKMARK
1941 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1947 #if ENABLE_FEATURE_VI_YANKMARK
1948 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1953 if (q < p) { // they are backwards- reverse them
1960 free(t); // if already a yank register, free it
1961 t = xmalloc(cnt + 1); // get a new register
1962 memset(t, '\0', cnt + 1); // clear new text[]
1963 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1968 static Byte what_reg(void)
1972 c = 'D'; // default to D-reg
1973 if (0 <= YDreg && YDreg <= 25)
1974 c = 'a' + (Byte) YDreg;
1982 static void check_context(Byte cmd)
1984 // A context is defined to be "modifying text"
1985 // Any modifying command establishes a new context.
1987 if (dot < context_start || dot > context_end) {
1988 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1989 // we are trying to modify text[]- make this the current context
1990 mark[27] = mark[26]; // move cur to prev
1991 mark[26] = dot; // move local to cur
1992 context_start = prev_line(prev_line(dot));
1993 context_end = next_line(next_line(dot));
1994 //loiter= start_loiter= now;
2000 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2004 // the current context is in mark[26]
2005 // the previous context is in mark[27]
2006 // only swap context if other context is valid
2007 if (text <= mark[27] && mark[27] <= end - 1) {
2009 mark[27] = mark[26];
2011 p = mark[26]; // where we are going- previous context
2012 context_start = prev_line(prev_line(prev_line(p)));
2013 context_end = next_line(next_line(next_line(p)));
2017 #endif /* FEATURE_VI_YANKMARK */
2019 static int isblnk(Byte c) // is the char a blank or tab
2021 return (c == ' ' || c == '\t');
2024 //----- Set terminal attributes --------------------------------
2025 static void rawmode(void)
2027 tcgetattr(0, &term_orig);
2028 term_vi = term_orig;
2029 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2030 term_vi.c_iflag &= (~IXON & ~ICRNL);
2031 term_vi.c_oflag &= (~ONLCR);
2032 term_vi.c_cc[VMIN] = 1;
2033 term_vi.c_cc[VTIME] = 0;
2034 erase_char = term_vi.c_cc[VERASE];
2035 tcsetattr(0, TCSANOW, &term_vi);
2038 static void cookmode(void)
2041 tcsetattr(0, TCSANOW, &term_orig);
2044 //----- Come here when we get a window resize signal ---------
2045 #if ENABLE_FEATURE_VI_USE_SIGNALS
2046 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2048 signal(SIGWINCH, winch_sig);
2049 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2050 get_terminal_width_height(0, &columns, &rows);
2051 new_screen(rows, columns); // get memory for virtual screen
2052 redraw(TRUE); // re-draw the screen
2055 //----- Come here when we get a continue signal -------------------
2056 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2058 rawmode(); // terminal to "raw"
2059 last_status_cksum = 0; // force status update
2060 redraw(TRUE); // re-draw the screen
2062 signal(SIGTSTP, suspend_sig);
2063 signal(SIGCONT, SIG_DFL);
2064 kill(my_pid, SIGCONT);
2067 //----- Come here when we get a Suspend signal -------------------
2068 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2070 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2071 clear_to_eol(); // Erase to end of line
2072 cookmode(); // terminal to "cooked"
2074 signal(SIGCONT, cont_sig);
2075 signal(SIGTSTP, SIG_DFL);
2076 kill(my_pid, SIGTSTP);
2079 //----- Come here when we get a signal ---------------------------
2080 static void catch_sig(int sig)
2082 signal(SIGINT, catch_sig);
2084 longjmp(restart, sig);
2086 #endif /* FEATURE_VI_USE_SIGNALS */
2088 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2090 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2095 tv.tv_usec = hund * 10000;
2096 select(1, &rfds, NULL, NULL, &tv);
2097 return FD_ISSET(0, &rfds);
2100 #define readbuffer bb_common_bufsiz1
2102 static int readed_for_parse;
2104 //----- IO Routines --------------------------------------------
2105 static Byte readit(void) // read (maybe cursor) key from stdin
2114 static const struct esc_cmds esccmds[] = {
2115 {"OA", (Byte) VI_K_UP}, // cursor key Up
2116 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2117 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2118 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2119 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2120 {"OF", (Byte) VI_K_END}, // Cursor Key End
2121 {"[A", (Byte) VI_K_UP}, // cursor key Up
2122 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2123 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2124 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2125 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2126 {"[F", (Byte) VI_K_END}, // Cursor Key End
2127 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2128 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2129 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2130 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2131 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2132 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2133 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2134 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2135 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2136 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2137 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2138 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2139 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2140 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2141 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2142 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2143 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2144 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2145 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2146 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2147 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2150 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2152 (void) alarm(0); // turn alarm OFF while we wait for input
2154 n = readed_for_parse;
2155 // get input from User- are there already input chars in Q?
2158 // the Q is empty, wait for a typed char
2159 n = read(0, readbuffer, BUFSIZ - 1);
2162 goto ri0; // interrupted sys call
2165 if (errno == EFAULT)
2167 if (errno == EINVAL)
2175 if (readbuffer[0] == 27) {
2176 // This is an ESC char. Is this Esc sequence?
2177 // Could be bare Esc key. See if there are any
2178 // more chars to read after the ESC. This would
2179 // be a Function or Cursor Key sequence.
2183 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2185 // keep reading while there are input chars and room in buffer
2186 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2187 // read the rest of the ESC string
2188 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2194 readed_for_parse = n;
2197 if(c == 27 && n > 1) {
2198 // Maybe cursor or function key?
2199 const struct esc_cmds *eindex;
2201 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2202 int cnt = strlen(eindex->seq);
2206 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2208 // is a Cursor key- put derived value back into Q
2210 // for squeeze out the ESC sequence
2214 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2215 /* defined ESC sequence not found, set only one ESC */
2221 // remove key sequence from Q
2222 readed_for_parse -= n;
2223 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2224 (void) alarm(3); // we are done waiting for input, turn alarm ON
2228 //----- IO Routines --------------------------------------------
2229 static Byte get_one_char(void)
2233 #if ENABLE_FEATURE_VI_DOT_CMD
2234 // ! adding2q && ioq == 0 read()
2235 // ! adding2q && ioq != 0 *ioq
2236 // adding2q *last_modifying_cmd= read()
2238 // we are not adding to the q.
2239 // but, we may be reading from a q
2241 // there is no current q, read from STDIN
2242 c = readit(); // get the users input
2244 // there is a queue to get chars from first
2247 // the end of the q, read from STDIN
2249 ioq_start = ioq = 0;
2250 c = readit(); // get the users input
2254 // adding STDIN chars to q
2255 c = readit(); // get the users input
2256 if (last_modifying_cmd != 0) {
2257 int len = strlen((char *) last_modifying_cmd);
2258 if (len + 1 >= BUFSIZ) {
2259 psbs("last_modifying_cmd overrun");
2261 // add new char to q
2262 last_modifying_cmd[len] = c;
2267 c = readit(); // get the users input
2268 #endif /* FEATURE_VI_DOT_CMD */
2269 return c; // return the char, where ever it came from
2272 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2277 static Byte *obufp = NULL;
2279 strcpy((char *) buf, (char *) prompt);
2280 last_status_cksum = 0; // force status update
2281 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2282 clear_to_eol(); // clear the line
2283 write1((char *) prompt); // write out the :, /, or ? prompt
2285 for (i = strlen((char *) buf); i < BUFSIZ;) {
2286 c = get_one_char(); // read user input
2287 if (c == '\n' || c == '\r' || c == 27)
2288 break; // is this end of input
2289 if (c == erase_char || c == 8 || c == 127) {
2290 // user wants to erase prev char
2291 i--; // backup to prev char
2292 buf[i] = '\0'; // erase the char
2293 buf[i + 1] = '\0'; // null terminate buffer
2294 write1("\b \b"); // erase char on screen
2295 if (i <= 0) { // user backs up before b-o-l, exit
2299 buf[i] = c; // save char in buffer
2300 buf[i + 1] = '\0'; // make sure buffer is null terminated
2301 putchar(c); // echo the char back to user
2307 obufp = (Byte *) xstrdup((char *) buf);
2311 static int file_size(const Byte * fn) // what is the byte size of "fn"
2316 if (fn == 0 || strlen((char *)fn) <= 0)
2319 sr = stat((char *) fn, &st_buf); // see if file exists
2321 cnt = (int) st_buf.st_size;
2326 static int file_insert(Byte * fn, Byte * p, int size)
2331 #if ENABLE_FEATURE_VI_READONLY
2334 if (fn == 0 || strlen((char*) fn) <= 0) {
2335 psbs("No filename given");
2339 // OK- this is just a no-op
2344 psbs("Trying to insert a negative number (%d) of characters", size);
2347 if (p < text || p > end) {
2348 psbs("Trying to insert file outside of memory");
2352 // see if we can open the file
2353 #if ENABLE_FEATURE_VI_READONLY
2354 if (vi_readonly) goto fi1; // do not try write-mode
2356 fd = open((char *) fn, O_RDWR); // assume read & write
2358 // could not open for writing- maybe file is read only
2359 #if ENABLE_FEATURE_VI_READONLY
2362 fd = open((char *) fn, O_RDONLY); // try read-only
2364 psbs("\"%s\" %s", fn, "cannot open file");
2367 #if ENABLE_FEATURE_VI_READONLY
2368 // got the file- read-only
2372 p = text_hole_make(p, size);
2373 cnt = read(fd, p, size);
2377 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2378 psbs("cannot read file \"%s\"", fn);
2379 } else if (cnt < size) {
2380 // There was a partial read, shrink unused space text[]
2381 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2382 psbs("cannot read all of file \"%s\"", fn);
2390 static int file_write(Byte * fn, Byte * first, Byte * last)
2392 int fd, cnt, charcnt;
2395 psbs("No current filename");
2399 // FIXIT- use the correct umask()
2400 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2403 cnt = last - first + 1;
2404 charcnt = write(fd, first, cnt);
2405 if (charcnt == cnt) {
2407 //file_modified= FALSE; // the file has not been modified
2415 //----- Terminal Drawing ---------------------------------------
2416 // The terminal is made up of 'rows' line of 'columns' columns.
2417 // classically this would be 24 x 80.
2418 // screen coordinates
2424 // 23,0 ... 23,79 status line
2427 //----- Move the cursor to row x col (count from 0, not 1) -------
2428 static void place_cursor(int row, int col, int opti)
2432 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2435 // char cm3[BUFSIZ];
2439 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2441 if (row < 0) row = 0;
2442 if (row >= rows) row = rows - 1;
2443 if (col < 0) col = 0;
2444 if (col >= columns) col = columns - 1;
2446 //----- 1. Try the standard terminal ESC sequence
2447 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2449 if (! opti) goto pc0;
2451 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2452 //----- find the minimum # of chars to move cursor -------------
2453 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2454 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2456 // move to the correct row
2457 while (row < Rrow) {
2458 // the cursor has to move up
2462 while (row > Rrow) {
2463 // the cursor has to move down
2464 strcat(cm2, CMdown);
2468 // now move to the correct column
2469 strcat(cm2, "\r"); // start at col 0
2470 // just send out orignal source char to get to correct place
2471 screenp = &screen[row * columns]; // start of screen line
2472 strncat(cm2, (char* )screenp, col);
2474 //----- 3. Try some other way of moving cursor
2475 //---------------------------------------------
2477 // pick the shortest cursor motion to send out
2479 if (strlen(cm2) < strlen(cm)) {
2481 } /* else if (strlen(cm3) < strlen(cm)) {
2484 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2486 write1(cm); // move the cursor
2489 //----- Erase from cursor to end of line -----------------------
2490 static void clear_to_eol(void)
2492 write1(Ceol); // Erase from cursor to end of line
2495 //----- Erase from cursor to end of screen -----------------------
2496 static void clear_to_eos(void)
2498 write1(Ceos); // Erase from cursor to end of screen
2501 //----- Start standout mode ------------------------------------
2502 static void standout_start(void) // send "start reverse video" sequence
2504 write1(SOs); // Start reverse video mode
2507 //----- End standout mode --------------------------------------
2508 static void standout_end(void) // send "end reverse video" sequence
2510 write1(SOn); // End reverse video mode
2513 //----- Flash the screen --------------------------------------
2514 static void flash(int h)
2516 standout_start(); // send "start reverse video" sequence
2519 standout_end(); // send "end reverse video" sequence
2523 static void Indicate_Error(void)
2525 #if ENABLE_FEATURE_VI_CRASHME
2527 return; // generate a random command
2530 write1(bell); // send out a bell character
2536 //----- Screen[] Routines --------------------------------------
2537 //----- Erase the Screen[] memory ------------------------------
2538 static void screen_erase(void)
2540 memset(screen, ' ', screensize); // clear new screen
2543 static int bufsum(unsigned char *buf, int count)
2546 unsigned char *e = buf + count;
2552 //----- Draw the status line at bottom of the screen -------------
2553 static void show_status_line(void)
2555 int cnt = 0, cksum = 0;
2557 // either we already have an error or status message, or we
2559 if (!have_status_msg) {
2560 cnt = format_edit_status();
2561 cksum = bufsum(status_buffer, cnt);
2563 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2564 last_status_cksum= cksum; // remember if we have seen this line
2565 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2566 write1((char*)status_buffer);
2568 if (have_status_msg) {
2569 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2571 have_status_msg = 0;
2574 have_status_msg = 0;
2576 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2581 //----- format the status buffer, the bottom line of screen ------
2582 // format status buffer, with STANDOUT mode
2583 static void psbs(const char *format, ...)
2587 va_start(args, format);
2588 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2589 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2590 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2593 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2598 // format status buffer
2599 static void psb(const char *format, ...)
2603 va_start(args, format);
2604 vsprintf((char *) status_buffer, format, args);
2607 have_status_msg = 1;
2612 static void ni(Byte * s) // display messages
2616 print_literal(buf, s);
2617 psbs("\'%s\' is not implemented", buf);
2620 static int format_edit_status(void) // show file status on status line
2622 int cur, percent, ret, trunc_at;
2625 // file_modified is now a counter rather than a flag. this
2626 // helps reduce the amount of line counting we need to do.
2627 // (this will cause a mis-reporting of modified status
2628 // once every MAXINT editing operations.)
2630 // it would be nice to do a similar optimization here -- if
2631 // we haven't done a motion that could have changed which line
2632 // we're on, then we shouldn't have to do this count_lines()
2633 cur = count_lines(text, dot);
2635 // reduce counting -- the total lines can't have
2636 // changed if we haven't done any edits.
2637 if (file_modified != last_file_modified) {
2638 tot = cur + count_lines(dot, end - 1) - 1;
2639 last_file_modified = file_modified;
2642 // current line percent
2643 // ------------- ~~ ----------
2646 percent = (100 * cur) / tot;
2652 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2653 columns : STATUS_BUFFER_LEN-1;
2655 ret = snprintf((char *) status_buffer, trunc_at+1,
2656 #if ENABLE_FEATURE_VI_READONLY
2657 "%c %s%s%s %d/%d %d%%",
2659 "%c %s%s %d/%d %d%%",
2661 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2662 (cfn != 0 ? (char *) cfn : "No file"),
2663 #if ENABLE_FEATURE_VI_READONLY
2664 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2666 (file_modified ? " [modified]" : ""),
2669 if (ret >= 0 && ret < trunc_at)
2670 return ret; /* it all fit */
2672 return trunc_at; /* had to truncate */
2675 //----- Force refresh of all Lines -----------------------------
2676 static void redraw(int full_screen)
2678 place_cursor(0, 0, FALSE); // put cursor in correct place
2679 clear_to_eos(); // tel terminal to erase display
2680 screen_erase(); // erase the internal screen buffer
2681 last_status_cksum = 0; // force status update
2682 refresh(full_screen); // this will redraw the entire display
2686 //----- Format a text[] line into a buffer ---------------------
2687 static void format_line(Byte *dest, Byte *src, int li)
2692 for (co= 0; co < MAX_SCR_COLS; co++) {
2693 c= ' '; // assume blank
2694 if (li > 0 && co == 0) {
2695 c = '~'; // not first line, assume Tilde
2697 // are there chars in text[] and have we gone past the end
2698 if (text < end && src < end) {
2703 if (c > 127 && !Isprint(c)) {
2706 if (c < ' ' || c == 127) {
2710 for (; (co % tabstop) != (tabstop - 1); co++) {
2718 c += '@'; // make it visible
2721 // the co++ is done here so that the column will
2722 // not be overwritten when we blank-out the rest of line
2729 //----- Refresh the changed screen lines -----------------------
2730 // Copy the source line from text[] into the buffer and note
2731 // if the current screenline is different from the new buffer.
2732 // If they differ then that line needs redrawing on the terminal.
2734 static void refresh(int full_screen)
2736 static int old_offset;
2738 Byte buf[MAX_SCR_COLS];
2739 Byte *tp, *sp; // pointer into text[] and screen[]
2740 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2741 int last_li= -2; // last line that changed- for optimizing cursor movement
2744 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2745 get_terminal_width_height(0, &columns, &rows);
2746 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2747 tp = screenbegin; // index into text[] of top line
2749 // compare text[] to screen[] and mark screen[] lines that need updating
2750 for (li = 0; li < rows - 1; li++) {
2751 int cs, ce; // column start & end
2752 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2753 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2754 // format current text line into buf
2755 format_line(buf, tp, li);
2757 // skip to the end of the current text[] line
2758 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2760 // see if there are any changes between vitual screen and buf
2761 changed = FALSE; // assume no change
2764 sp = &screen[li * columns]; // start of screen line
2766 // force re-draw of every single column from 0 - columns-1
2769 // compare newly formatted buffer with virtual screen
2770 // look forward for first difference between buf and screen
2771 for ( ; cs <= ce; cs++) {
2772 if (buf[cs + offset] != sp[cs]) {
2773 changed = TRUE; // mark for redraw
2778 // look backward for last difference between buf and screen
2779 for ( ; ce >= cs; ce--) {
2780 if (buf[ce + offset] != sp[ce]) {
2781 changed = TRUE; // mark for redraw
2785 // now, cs is index of first diff, and ce is index of last diff
2787 // if horz offset has changed, force a redraw
2788 if (offset != old_offset) {
2793 // make a sanity check of columns indexes
2795 if (ce > columns-1) ce= columns-1;
2796 if (cs > ce) { cs= 0; ce= columns-1; }
2797 // is there a change between vitual screen and buf
2799 // copy changed part of buffer to virtual screen
2800 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2802 // move cursor to column of first change
2803 if (offset != old_offset) {
2804 // opti_cur_move is still too stupid
2805 // to handle offsets correctly
2806 place_cursor(li, cs, FALSE);
2808 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2809 // if this just the next line
2810 // try to optimize cursor movement
2811 // otherwise, use standard ESC sequence
2812 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2815 place_cursor(li, cs, FALSE); // use standard ESC sequence
2816 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2819 // write line out to terminal
2822 char *out = (char*)sp+cs;
2829 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2835 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2836 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2839 place_cursor(crow, ccol, FALSE);
2842 if (offset != old_offset)
2843 old_offset = offset;
2846 //---------------------------------------------------------------------
2847 //----- the Ascii Chart -----------------------------------------------
2849 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2850 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2851 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2852 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2853 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2854 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2855 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2856 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2857 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2858 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2859 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2860 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2861 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2862 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2863 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2864 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2865 //---------------------------------------------------------------------
2867 //----- Execute a Vi Command -----------------------------------
2868 static void do_cmd(Byte c)
2870 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2871 int cnt, i, j, dir, yf;
2873 c1 = c; // quiet the compiler
2874 cnt = yf = dir = 0; // quiet the compiler
2875 p = q = save_dot = msg = buf; // quiet the compiler
2876 memset(buf, '\0', 9); // clear buf
2880 /* if this is a cursor key, skip these checks */
2893 if (cmd_mode == 2) {
2894 // flip-flop Insert/Replace mode
2895 if (c == VI_K_INSERT) goto dc_i;
2896 // we are 'R'eplacing the current *dot with new char
2898 // don't Replace past E-o-l
2899 cmd_mode = 1; // convert to insert
2901 if (1 <= c || Isprint(c)) {
2903 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2904 dot = char_insert(dot, c); // insert new char
2909 if (cmd_mode == 1) {
2910 // hitting "Insert" twice means "R" replace mode
2911 if (c == VI_K_INSERT) goto dc5;
2912 // insert the char c at "dot"
2913 if (1 <= c || Isprint(c)) {
2914 dot = char_insert(dot, c);
2929 #if ENABLE_FEATURE_VI_CRASHME
2930 case 0x14: // dc4 ctrl-T
2931 crashme = (crashme == 0) ? 1 : 0;
2962 //case 'u': // u- FIXME- there is no undo
2964 default: // unrecognised command
2973 end_cmd_q(); // stop adding to q
2974 case 0x00: // nul- ignore
2976 case 2: // ctrl-B scroll up full screen
2977 case VI_K_PAGEUP: // Cursor Key Page Up
2978 dot_scroll(rows - 2, -1);
2980 #if ENABLE_FEATURE_VI_USE_SIGNALS
2981 case 0x03: // ctrl-C interrupt
2982 longjmp(restart, 1);
2984 case 26: // ctrl-Z suspend
2985 suspend_sig(SIGTSTP);
2988 case 4: // ctrl-D scroll down half screen
2989 dot_scroll((rows - 2) / 2, 1);
2991 case 5: // ctrl-E scroll down one line
2994 case 6: // ctrl-F scroll down full screen
2995 case VI_K_PAGEDOWN: // Cursor Key Page Down
2996 dot_scroll(rows - 2, 1);
2998 case 7: // ctrl-G show current status
2999 last_status_cksum = 0; // force status update
3001 case 'h': // h- move left
3002 case VI_K_LEFT: // cursor key Left
3003 case 8: // ctrl-H- move left (This may be ERASE char)
3004 case 127: // DEL- move left (This may be ERASE char)
3010 case 10: // Newline ^J
3011 case 'j': // j- goto next line, same col
3012 case VI_K_DOWN: // cursor key Down
3016 dot_next(); // go to next B-o-l
3017 dot = move_to_col(dot, ccol + offset); // try stay in same col
3019 case 12: // ctrl-L force redraw whole screen
3020 case 18: // ctrl-R force redraw
3021 place_cursor(0, 0, FALSE); // put cursor in correct place
3022 clear_to_eos(); // tel terminal to erase display
3024 screen_erase(); // erase the internal screen buffer
3025 last_status_cksum = 0; // force status update
3026 refresh(TRUE); // this will redraw the entire display
3028 case 13: // Carriage Return ^M
3029 case '+': // +- goto next line
3036 case 21: // ctrl-U scroll up half screen
3037 dot_scroll((rows - 2) / 2, -1);
3039 case 25: // ctrl-Y scroll up one line
3045 cmd_mode = 0; // stop insrting
3047 last_status_cksum = 0; // force status update
3049 case ' ': // move right
3050 case 'l': // move right
3051 case VI_K_RIGHT: // Cursor Key Right
3057 #if ENABLE_FEATURE_VI_YANKMARK
3058 case '"': // "- name a register to use for Delete/Yank
3059 c1 = get_one_char();
3067 case '\'': // '- goto a specific mark
3068 c1 = get_one_char();
3074 if (text <= q && q < end) {
3076 dot_begin(); // go to B-o-l
3079 } else if (c1 == '\'') { // goto previous context
3080 dot = swap_context(dot); // swap current and previous context
3081 dot_begin(); // go to B-o-l
3087 case 'm': // m- Mark a line
3088 // this is really stupid. If there are any inserts or deletes
3089 // between text[0] and dot then this mark will not point to the
3090 // correct location! It could be off by many lines!
3091 // Well..., at least its quick and dirty.
3092 c1 = get_one_char();
3096 // remember the line
3097 mark[(int) c1] = dot;
3102 case 'P': // P- Put register before
3103 case 'p': // p- put register after
3106 psbs("Nothing in register %c", what_reg());
3109 // are we putting whole lines or strings
3110 if (strchr((char *) p, '\n') != NULL) {
3112 dot_begin(); // putting lines- Put above
3115 // are we putting after very last line?
3116 if (end_line(dot) == (end - 1)) {
3117 dot = end; // force dot to end of text[]
3119 dot_next(); // next line, then put before
3124 dot_right(); // move to right, can move to NL
3126 dot = string_insert(dot, p); // insert the string
3127 end_cmd_q(); // stop adding to q
3129 case 'U': // U- Undo; replace current line with original version
3130 if (reg[Ureg] != 0) {
3131 p = begin_line(dot);
3133 p = text_hole_delete(p, q); // delete cur line
3134 p = string_insert(p, reg[Ureg]); // insert orig line
3139 #endif /* FEATURE_VI_YANKMARK */
3140 case '$': // $- goto end of line
3141 case VI_K_END: // Cursor Key End
3145 dot = end_line(dot);
3147 case '%': // %- find matching char of pair () [] {}
3148 for (q = dot; q < end && *q != '\n'; q++) {
3149 if (strchr("()[]{}", *q) != NULL) {
3150 // we found half of a pair
3151 p = find_pair(q, *q);
3163 case 'f': // f- forward to a user specified char
3164 last_forward_char = get_one_char(); // get the search char
3166 // dont separate these two commands. 'f' depends on ';'
3168 //**** fall thru to ... ';'
3169 case ';': // ;- look at rest of line for last forward char
3173 if (last_forward_char == 0) break;
3175 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3178 if (*q == last_forward_char)
3181 case '-': // -- goto prev line
3188 #if ENABLE_FEATURE_VI_DOT_CMD
3189 case '.': // .- repeat the last modifying command
3190 // Stuff the last_modifying_cmd back into stdin
3191 // and let it be re-executed.
3192 if (last_modifying_cmd != 0) {
3193 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3197 #if ENABLE_FEATURE_VI_SEARCH
3198 case '?': // /- search for a pattern
3199 case '/': // /- search for a pattern
3202 q = get_input_line(buf); // get input line- use "status line"
3203 if (strlen((char *) q) == 1)
3204 goto dc3; // if no pat re-use old pat
3205 if (strlen((char *) q) > 1) { // new pat- save it and find
3206 // there is a new pat
3207 free(last_search_pattern);
3208 last_search_pattern = (Byte *) xstrdup((char *) q);
3209 goto dc3; // now find the pattern
3211 // user changed mind and erased the "/"- do nothing
3213 case 'N': // N- backward search for last pattern
3217 dir = BACK; // assume BACKWARD search
3219 if (last_search_pattern[0] == '?') {
3223 goto dc4; // now search for pattern
3225 case 'n': // n- repeat search for last pattern
3226 // search rest of text[] starting at next char
3227 // if search fails return orignal "p" not the "p+1" address
3232 if (last_search_pattern == 0) {
3233 msg = (Byte *) "No previous regular expression";
3236 if (last_search_pattern[0] == '/') {
3237 dir = FORWARD; // assume FORWARD search
3240 if (last_search_pattern[0] == '?') {
3245 q = char_search(p, last_search_pattern + 1, dir, FULL);
3247 dot = q; // good search, update "dot"
3251 // no pattern found between "dot" and "end"- continue at top
3256 q = char_search(p, last_search_pattern + 1, dir, FULL);
3257 if (q != NULL) { // found something
3258 dot = q; // found new pattern- goto it
3259 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3261 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3264 msg = (Byte *) "Pattern not found";
3267 if (*msg) psbs("%s", msg);
3269 case '{': // {- move backward paragraph
3270 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3271 if (q != NULL) { // found blank line
3272 dot = next_line(q); // move to next blank line
3275 case '}': // }- move forward paragraph
3276 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3277 if (q != NULL) { // found blank line
3278 dot = next_line(q); // move to next blank line
3281 #endif /* FEATURE_VI_SEARCH */
3282 case '0': // 0- goto begining of line
3292 if (c == '0' && cmdcnt < 1) {
3293 dot_begin(); // this was a standalone zero
3295 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3298 case ':': // :- the colon mode commands
3299 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3300 #if ENABLE_FEATURE_VI_COLON
3301 colon(p); // execute the command
3304 p++; // move past the ':'
3305 cnt = strlen((char *) p);
3308 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3309 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3310 if (file_modified && p[1] != '!') {
3311 psbs("No write since last change (:quit! overrides)");
3315 } else if (strncasecmp((char *) p, "write", cnt) == 0
3316 || strncasecmp((char *) p, "wq", cnt) == 0
3317 || strncasecmp((char *) p, "wn", cnt) == 0
3318 || strncasecmp((char *) p, "x", cnt) == 0) {
3319 cnt = file_write(cfn, text, end - 1);
3322 psbs("Write error: %s", strerror(errno));
3325 last_file_modified = -1;
3326 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3327 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3328 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3332 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3333 last_status_cksum = 0; // force status update
3334 } else if (sscanf((char *) p, "%d", &j) > 0) {
3335 dot = find_line(j); // go to line # j
3337 } else { // unrecognised cmd
3340 #endif /* !FEATURE_VI_COLON */
3342 case '<': // <- Left shift something
3343 case '>': // >- Right shift something
3344 cnt = count_lines(text, dot); // remember what line we are on
3345 c1 = get_one_char(); // get the type of thing to delete
3346 find_range(&p, &q, c1);
3347 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3350 i = count_lines(p, q); // # of lines we are shifting
3351 for ( ; i > 0; i--, p = next_line(p)) {
3353 // shift left- remove tab or 8 spaces
3355 // shrink buffer 1 char
3356 (void) text_hole_delete(p, p);
3357 } else if (*p == ' ') {
3358 // we should be calculating columns, not just SPACE
3359 for (j = 0; *p == ' ' && j < tabstop; j++) {
3360 (void) text_hole_delete(p, p);
3363 } else if (c == '>') {
3364 // shift right -- add tab or 8 spaces
3365 (void) char_insert(p, '\t');
3368 dot = find_line(cnt); // what line were we on
3370 end_cmd_q(); // stop adding to q
3372 case 'A': // A- append at e-o-l
3373 dot_end(); // go to e-o-l
3374 //**** fall thru to ... 'a'
3375 case 'a': // a- append after current char
3380 case 'B': // B- back a blank-delimited Word
3381 case 'E': // E- end of a blank-delimited word
3382 case 'W': // W- forward a blank-delimited word
3389 if (c == 'W' || isspace(dot[dir])) {
3390 dot = skip_thing(dot, 1, dir, S_TO_WS);
3391 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3394 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3396 case 'C': // C- Change to e-o-l
3397 case 'D': // D- delete to e-o-l
3399 dot = dollar_line(dot); // move to before NL
3400 // copy text into a register and delete
3401 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3403 goto dc_i; // start inserting
3404 #if ENABLE_FEATURE_VI_DOT_CMD
3406 end_cmd_q(); // stop adding to q
3409 case 'G': // G- goto to a line number (default= E-O-F)
3410 dot = end - 1; // assume E-O-F
3412 dot = find_line(cmdcnt); // what line is #cmdcnt
3416 case 'H': // H- goto top line on screen
3418 if (cmdcnt > (rows - 1)) {
3419 cmdcnt = (rows - 1);
3426 case 'I': // I- insert before first non-blank
3429 //**** fall thru to ... 'i'
3430 case 'i': // i- insert before current char
3431 case VI_K_INSERT: // Cursor Key Insert
3433 cmd_mode = 1; // start insrting
3435 case 'J': // J- join current and next lines together
3439 dot_end(); // move to NL
3440 if (dot < end - 1) { // make sure not last char in text[]
3441 *dot++ = ' '; // replace NL with space
3443 while (isblnk(*dot)) { // delete leading WS
3447 end_cmd_q(); // stop adding to q
3449 case 'L': // L- goto bottom line on screen
3451 if (cmdcnt > (rows - 1)) {
3452 cmdcnt = (rows - 1);
3460 case 'M': // M- goto middle line on screen
3462 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3463 dot = next_line(dot);
3465 case 'O': // O- open a empty line above
3467 p = begin_line(dot);
3468 if (p[-1] == '\n') {
3470 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3472 dot = char_insert(dot, '\n');
3475 dot = char_insert(dot, '\n'); // i\n ESC
3480 case 'R': // R- continuous Replace char
3484 case 'X': // X- delete char before dot
3485 case 'x': // x- delete the current char
3486 case 's': // s- substitute the current char
3493 if (dot[dir] != '\n') {
3495 dot--; // delete prev char
3496 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3499 goto dc_i; // start insrting
3500 end_cmd_q(); // stop adding to q
3502 case 'Z': // Z- if modified, {write}; exit
3503 // ZZ means to save file (if necessary), then exit
3504 c1 = get_one_char();
3509 if (file_modified) {
3510 #if ENABLE_FEATURE_VI_READONLY
3511 if (vi_readonly || readonly) {
3512 psbs("\"%s\" File is read only", cfn);
3516 cnt = file_write(cfn, text, end - 1);
3519 psbs("Write error: %s", strerror(errno));
3520 } else if (cnt == (end - 1 - text + 1)) {
3527 case '^': // ^- move to first non-blank on line
3531 case 'b': // b- back a word
3532 case 'e': // e- end of word
3539 if ((dot + dir) < text || (dot + dir) > end - 1)
3542 if (isspace(*dot)) {
3543 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3545 if (isalnum(*dot) || *dot == '_') {
3546 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3547 } else if (ispunct(*dot)) {
3548 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3551 case 'c': // c- change something
3552 case 'd': // d- delete something
3553 #if ENABLE_FEATURE_VI_YANKMARK
3554 case 'y': // y- yank something
3555 case 'Y': // Y- Yank a line
3557 yf = YANKDEL; // assume either "c" or "d"
3558 #if ENABLE_FEATURE_VI_YANKMARK
3559 if (c == 'y' || c == 'Y')
3564 c1 = get_one_char(); // get the type of thing to delete
3565 find_range(&p, &q, c1);
3566 if (c1 == 27) { // ESC- user changed mind and wants out
3567 c = c1 = 27; // Escape- do nothing
3568 } else if (strchr("wW", c1)) {
3570 // don't include trailing WS as part of word
3571 while (isblnk(*q)) {
3572 if (q <= text || q[-1] == '\n')
3577 dot = yank_delete(p, q, 0, yf); // delete word
3578 } else if (strchr("^0bBeEft$", c1)) {
3579 // single line copy text into a register and delete
3580 dot = yank_delete(p, q, 0, yf); // delete word
3581 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3582 // multiple line copy text into a register and delete
3583 dot = yank_delete(p, q, 1, yf); // delete lines
3585 dot = char_insert(dot, '\n');
3586 // on the last line of file don't move to prev line
3587 if (dot != (end-1)) {
3590 } else if (c == 'd') {
3595 // could not recognize object
3596 c = c1 = 27; // error-
3600 // if CHANGING, not deleting, start inserting after the delete
3602 strcpy((char *) buf, "Change");
3603 goto dc_i; // start inserting
3606 strcpy((char *) buf, "Delete");
3608 #if ENABLE_FEATURE_VI_YANKMARK
3609 if (c == 'y' || c == 'Y') {
3610 strcpy((char *) buf, "Yank");
3613 q = p + strlen((char *) p);
3614 for (cnt = 0; p <= q; p++) {
3618 psb("%s %d lines (%d chars) using [%c]",
3619 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3621 end_cmd_q(); // stop adding to q
3624 case 'k': // k- goto prev line, same col
3625 case VI_K_UP: // cursor key Up
3630 dot = move_to_col(dot, ccol + offset); // try stay in same col
3632 case 'r': // r- replace the current char with user input
3633 c1 = get_one_char(); // get the replacement char
3636 file_modified++; // has the file been modified
3638 end_cmd_q(); // stop adding to q
3640 case 't': // t- move to char prior to next x
3641 last_forward_char = get_one_char();
3643 if (*dot == last_forward_char)
3645 last_forward_char= 0;
3647 case 'w': // w- forward a word
3651 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3652 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3653 } else if (ispunct(*dot)) { // we are on PUNCT
3654 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3657 dot++; // move over word
3658 if (isspace(*dot)) {
3659 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3663 c1 = get_one_char(); // get the replacement char
3666 cnt = (rows - 2) / 2; // put dot at center
3668 cnt = rows - 2; // put dot at bottom
3669 screenbegin = begin_line(dot); // start dot at top
3670 dot_scroll(cnt, -1);
3672 case '|': // |- move to column "cmdcnt"
3673 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3675 case '~': // ~- flip the case of letters a-z -> A-Z
3679 if (islower(*dot)) {
3680 *dot = toupper(*dot);
3681 file_modified++; // has the file been modified
3682 } else if (isupper(*dot)) {
3683 *dot = tolower(*dot);
3684 file_modified++; // has the file been modified
3687 end_cmd_q(); // stop adding to q
3689 //----- The Cursor and Function Keys -----------------------------
3690 case VI_K_HOME: // Cursor Key Home
3693 // The Fn keys could point to do_macro which could translate them
3694 case VI_K_FUN1: // Function Key F1
3695 case VI_K_FUN2: // Function Key F2
3696 case VI_K_FUN3: // Function Key F3
3697 case VI_K_FUN4: // Function Key F4
3698 case VI_K_FUN5: // Function Key F5
3699 case VI_K_FUN6: // Function Key F6
3700 case VI_K_FUN7: // Function Key F7
3701 case VI_K_FUN8: // Function Key F8
3702 case VI_K_FUN9: // Function Key F9
3703 case VI_K_FUN10: // Function Key F10
3704 case VI_K_FUN11: // Function Key F11
3705 case VI_K_FUN12: // Function Key F12
3710 // if text[] just became empty, add back an empty line
3712 (void) char_insert(text, '\n'); // start empty buf with dummy line
3715 // it is OK for dot to exactly equal to end, otherwise check dot validity
3717 dot = bound_dot(dot); // make sure "dot" is valid
3719 #if ENABLE_FEATURE_VI_YANKMARK
3720 check_context(c); // update the current context
3724 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3725 cnt = dot - begin_line(dot);
3726 // Try to stay off of the Newline
3727 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3731 #if ENABLE_FEATURE_VI_CRASHME
3732 static int totalcmds = 0;
3733 static int Mp = 85; // Movement command Probability
3734 static int Np = 90; // Non-movement command Probability
3735 static int Dp = 96; // Delete command Probability
3736 static int Ip = 97; // Insert command Probability
3737 static int Yp = 98; // Yank command Probability
3738 static int Pp = 99; // Put command Probability
3739 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3740 char chars[20] = "\t012345 abcdABCD-=.$";
3741 char *words[20] = { "this", "is", "a", "test",
3742 "broadcast", "the", "emergency", "of",
3743 "system", "quick", "brown", "fox",
3744 "jumped", "over", "lazy", "dogs",
3745 "back", "January", "Febuary", "March"
3748 "You should have received a copy of the GNU General Public License\n",
3749 "char c, cm, *cmd, *cmd1;\n",
3750 "generate a command by percentages\n",
3751 "Numbers may be typed as a prefix to some commands.\n",
3752 "Quit, discarding changes!\n",
3753 "Forced write, if permission originally not valid.\n",
3754 "In general, any ex or ed command (such as substitute or delete).\n",
3755 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3756 "Please get w/ me and I will go over it with you.\n",
3757 "The following is a list of scheduled, committed changes.\n",
3758 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3759 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3760 "Any question about transactions please contact Sterling Huxley.\n",
3761 "I will try to get back to you by Friday, December 31.\n",
3762 "This Change will be implemented on Friday.\n",
3763 "Let me know if you have problems accessing this;\n",
3764 "Sterling Huxley recently added you to the access list.\n",
3765 "Would you like to go to lunch?\n",
3766 "The last command will be automatically run.\n",
3767 "This is too much english for a computer geek.\n",
3769 char *multilines[20] = {
3770 "You should have received a copy of the GNU General Public License\n",
3771 "char c, cm, *cmd, *cmd1;\n",
3772 "generate a command by percentages\n",
3773 "Numbers may be typed as a prefix to some commands.\n",
3774 "Quit, discarding changes!\n",
3775 "Forced write, if permission originally not valid.\n",
3776 "In general, any ex or ed command (such as substitute or delete).\n",
3777 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3778 "Please get w/ me and I will go over it with you.\n",
3779 "The following is a list of scheduled, committed changes.\n",
3780 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3781 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3782 "Any question about transactions please contact Sterling Huxley.\n",
3783 "I will try to get back to you by Friday, December 31.\n",
3784 "This Change will be implemented on Friday.\n",
3785 "Let me know if you have problems accessing this;\n",
3786 "Sterling Huxley recently added you to the access list.\n",
3787 "Would you like to go to lunch?\n",
3788 "The last command will be automatically run.\n",
3789 "This is too much english for a computer geek.\n",
3792 // create a random command to execute
3793 static void crash_dummy()
3795 static int sleeptime; // how long to pause between commands
3796 char c, cm, *cmd, *cmd1;
3797 int i, cnt, thing, rbi, startrbi, percent;
3799 // "dot" movement commands
3800 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3802 // is there already a command running?
3803 if (readed_for_parse > 0)
3807 sleeptime = 0; // how long to pause between commands
3808 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3809 // generate a command by percentages
3810 percent = (int) lrand48() % 100; // get a number from 0-99
3811 if (percent < Mp) { // Movement commands
3812 // available commands
3815 } else if (percent < Np) { // non-movement commands
3816 cmd = "mz<>\'\""; // available commands
3818 } else if (percent < Dp) { // Delete commands
3819 cmd = "dx"; // available commands
3821 } else if (percent < Ip) { // Inset commands
3822 cmd = "iIaAsrJ"; // available commands
3824 } else if (percent < Yp) { // Yank commands
3825 cmd = "yY"; // available commands
3827 } else if (percent < Pp) { // Put commands
3828 cmd = "pP"; // available commands
3831 // We do not know how to handle this command, try again
3835 // randomly pick one of the available cmds from "cmd[]"
3836 i = (int) lrand48() % strlen(cmd);
3838 if (strchr(":\024", cm))
3839 goto cd0; // dont allow colon or ctrl-T commands
3840 readbuffer[rbi++] = cm; // put cmd into input buffer
3842 // now we have the command-
3843 // there are 1, 2, and multi char commands
3844 // find out which and generate the rest of command as necessary
3845 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3846 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3847 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3848 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3850 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3852 readbuffer[rbi++] = c; // add movement to input buffer
3854 if (strchr("iIaAsc", cm)) { // multi-char commands
3856 // change some thing
3857 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3859 readbuffer[rbi++] = c; // add movement to input buffer
3861 thing = (int) lrand48() % 4; // what thing to insert
3862 cnt = (int) lrand48() % 10; // how many to insert
3863 for (i = 0; i < cnt; i++) {
3864 if (thing == 0) { // insert chars
3865 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3866 } else if (thing == 1) { // insert words
3867 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3868 strcat((char *) readbuffer, " ");
3869 sleeptime = 0; // how fast to type
3870 } else if (thing == 2) { // insert lines
3871 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3872 sleeptime = 0; // how fast to type
3873 } else { // insert multi-lines
3874 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3875 sleeptime = 0; // how fast to type
3878 strcat((char *) readbuffer, "\033");
3880 readed_for_parse = strlen(readbuffer);
3884 (void) mysleep(sleeptime); // sleep 1/100 sec
3887 // test to see if there are any errors
3888 static void crash_test()
3890 static time_t oldtim;
3892 char d[2], msg[BUFSIZ];
3896 strcat((char *) msg, "end<text ");
3898 if (end > textend) {
3899 strcat((char *) msg, "end>textend ");
3902 strcat((char *) msg, "dot<text ");
3905 strcat((char *) msg, "dot>end ");
3907 if (screenbegin < text) {
3908 strcat((char *) msg, "screenbegin<text ");
3910 if (screenbegin > end - 1) {
3911 strcat((char *) msg, "screenbegin>end-1 ");
3914 if (strlen(msg) > 0) {
3916 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3917 totalcmds, last_input_char, msg, SOs, SOn);
3919 while (read(0, d, 1) > 0) {
3920 if (d[0] == '\n' || d[0] == '\r')
3925 tim = (time_t) time((time_t *) 0);
3926 if (tim >= (oldtim + 3)) {
3927 sprintf((char *) status_buffer,
3928 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3929 totalcmds, M, N, I, D, Y, P, U, end - text + 1);