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 /* 0x9b is Meta-ESC */
32 #define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
35 #define MAX_SCR_COLS BUFSIZ
37 // Misc. non-Ascii keys that report an escape sequence
38 #define VI_K_UP (char)128 // cursor key Up
39 #define VI_K_DOWN (char)129 // cursor key Down
40 #define VI_K_RIGHT (char)130 // Cursor Key Right
41 #define VI_K_LEFT (char)131 // cursor key Left
42 #define VI_K_HOME (char)132 // Cursor Key Home
43 #define VI_K_END (char)133 // Cursor Key End
44 #define VI_K_INSERT (char)134 // Cursor Key Insert
45 #define VI_K_PAGEUP (char)135 // Cursor Key Page Up
46 #define VI_K_PAGEDOWN (char)136 // Cursor Key Page Down
47 #define VI_K_FUN1 (char)137 // Function Key F1
48 #define VI_K_FUN2 (char)138 // Function Key F2
49 #define VI_K_FUN3 (char)139 // Function Key F3
50 #define VI_K_FUN4 (char)140 // Function Key F4
51 #define VI_K_FUN5 (char)141 // Function Key F5
52 #define VI_K_FUN6 (char)142 // Function Key F6
53 #define VI_K_FUN7 (char)143 // Function Key F7
54 #define VI_K_FUN8 (char)144 // Function Key F8
55 #define VI_K_FUN9 (char)145 // Function Key F9
56 #define VI_K_FUN10 (char)146 // Function Key F10
57 #define VI_K_FUN11 (char)147 // Function Key F11
58 #define VI_K_FUN12 (char)148 // Function Key F12
60 /* vt102 typical ESC sequence */
61 /* terminal standout start/normal ESC sequence */
62 static const char SOs[] = "\033[7m";
63 static const char SOn[] = "\033[0m";
64 /* terminal bell sequence */
65 static const char bell[] = "\007";
66 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
67 static const char Ceol[] = "\033[0K";
68 static const char Ceos [] = "\033[0J";
69 /* Cursor motion arbitrary destination ESC sequence */
70 static const char CMrc[] = "\033[%d;%dH";
71 /* Cursor motion up and down ESC sequence */
72 static const char CMup[] = "\033[A";
73 static const char CMdown[] = "\n";
79 FORWARD = 1, // code depends on "1" for array index
80 BACK = -1, // code depends on "-1" for array index
81 LIMITED = 0, // how much of text[] in char_search
82 FULL = 1, // how much of text[] in char_search
84 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
85 S_TO_WS = 2, // used in skip_thing() for moving "dot"
86 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
87 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
88 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
91 /* vi.c expects chars to be unsigned. */
92 /* busybox build system provides that, but it's better */
93 /* to audit and fix the source */
96 #define VI_AUTOINDENT 1
97 #define VI_SHOWMATCH 2
98 #define VI_IGNORECASE 4
99 #define VI_ERR_METHOD 8
100 #define autoindent (vi_setops & VI_AUTOINDENT)
101 #define showmatch (vi_setops & VI_SHOWMATCH )
102 #define ignorecase (vi_setops & VI_IGNORECASE)
103 /* indicate error with beep or flash */
104 #define err_method (vi_setops & VI_ERR_METHOD)
107 static int editing; // >0 while we are editing a file
108 static int cmd_mode; // 0=command 1=insert 2=replace
109 static int file_modified; // buffer contents changed
110 static int last_file_modified = -1;
111 static int fn_start; // index of first cmd line file name
112 static int save_argc; // how many file names on cmd line
113 static int cmdcnt; // repetition count
114 static int rows, columns; // the terminal screen is this size
115 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
116 static char *status_buffer; // mesages to the user
117 #define STATUS_BUFFER_LEN 200
118 static int have_status_msg; // is default edit status needed?
119 static int last_status_cksum; // hash of current status line
120 static char *cfn; // previous, current, and next file name
121 static char *text, *end; // pointers to the user data in memory
122 static char *screen; // pointer to the virtual screen buffer
123 static int screensize; // and its size
124 static char *screenbegin; // index into text[], of top line on the screen
125 static char *dot; // where all the action takes place
127 static struct termios term_orig, term_vi; // remember what the cooked mode was
128 static char erase_char; // the users erase character
129 static char last_input_char; // last char read from user
130 static char last_forward_char; // last char searched for with 'f'
132 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
133 static int last_row; // where the cursor was last moved to
135 #if ENABLE_FEATURE_VI_USE_SIGNALS
136 static jmp_buf restart; // catch_sig()
138 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
141 #if ENABLE_FEATURE_VI_DOT_CMD
142 static int adding2q; // are we currently adding user input to q
143 static char *last_modifying_cmd; // last modifying cmd for "."
144 static char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
146 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
147 static char *modifying_cmds; // cmds that modify text[]
149 #if ENABLE_FEATURE_VI_READONLY
150 static int vi_readonly, readonly;
152 #if ENABLE_FEATURE_VI_YANKMARK
153 static char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
154 static int YDreg, Ureg; // default delete register and orig line for "U"
155 static char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
156 static char *context_start, *context_end;
158 #if ENABLE_FEATURE_VI_SEARCH
159 static char *last_search_pattern; // last pattern from a '/' or '?' search
163 static void edit_file(char *); // edit one file
164 static void do_cmd(char); // execute a command
165 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
166 static char *begin_line(char *); // return pointer to cur line B-o-l
167 static char *end_line(char *); // return pointer to cur line E-o-l
168 static char *prev_line(char *); // return pointer to prev line B-o-l
169 static char *next_line(char *); // return pointer to next line B-o-l
170 static char *end_screen(void); // get pointer to last char on screen
171 static int count_lines(char *, char *); // count line from start to stop
172 static char *find_line(int); // find begining of line #li
173 static char *move_to_col(char *, int); // move "p" to column l
174 static int isblnk(char); // is the char a blank or tab
175 static void dot_left(void); // move dot left- dont leave line
176 static void dot_right(void); // move dot right- dont leave line
177 static void dot_begin(void); // move dot to B-o-l
178 static void dot_end(void); // move dot to E-o-l
179 static void dot_next(void); // move dot to next line B-o-l
180 static void dot_prev(void); // move dot to prev line B-o-l
181 static void dot_scroll(int, int); // move the screen up or down
182 static void dot_skip_over_ws(void); // move dot pat WS
183 static void dot_delete(void); // delete the char at 'dot'
184 static char *bound_dot(char *); // make sure text[0] <= P < "end"
185 static char *new_screen(int, int); // malloc virtual screen memory
186 static char *new_text(int); // malloc memory for text[] buffer
187 static char *char_insert(char *, char); // insert the char c at 'p'
188 static char *stupid_insert(char *, char); // stupidly insert the char c at 'p'
189 static char find_range(char **, char **, char); // return pointers for an object
190 static int st_test(char *, int, int, char *); // helper for skip_thing()
191 static char *skip_thing(char *, int, int, int); // skip some object
192 static char *find_pair(char *, char); // find matching pair () [] {}
193 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
194 static char *text_hole_make(char *, int); // at "p", make a 'size' byte hole
195 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
196 static void show_help(void); // display some help info
197 static void rawmode(void); // set "raw" mode on tty
198 static void cookmode(void); // return to "cooked" mode on tty
199 static int mysleep(int); // sleep for 'h' 1/100 seconds
200 static char readit(void); // read (maybe cursor) key from stdin
201 static char get_one_char(void); // read 1 char from stdin
202 static int file_size(const char *); // what is the byte size of "fn"
203 static int file_insert(char *, char *, int);
204 static int file_write(char *, char *, char *);
205 static void place_cursor(int, int, int);
206 static void screen_erase(void);
207 static void clear_to_eol(void);
208 static void clear_to_eos(void);
209 static void standout_start(void); // send "start reverse video" sequence
210 static void standout_end(void); // send "end reverse video" sequence
211 static void flash(int); // flash the terminal screen
212 static void show_status_line(void); // put a message on the bottom line
213 static void psb(const char *, ...); // Print Status Buf
214 static void psbs(const char *, ...); // Print Status Buf in standout mode
215 static void ni(const char *); // display messages
216 static int format_edit_status(void); // format file status on status line
217 static void redraw(int); // force a full screen refresh
218 static void format_line(char*, char*, int);
219 static void refresh(int); // update the terminal from screen[]
221 static void Indicate_Error(void); // use flash or beep to indicate error
222 #define indicate_error(c) Indicate_Error()
223 static void Hit_Return(void);
225 #if ENABLE_FEATURE_VI_SEARCH
226 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
227 static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase"
229 #if ENABLE_FEATURE_VI_COLON
230 static char *get_one_address(char *, int *); // get colon addr, if present
231 static char *get_address(char *, int *, int *); // get two colon addrs, if present
232 static void colon(char *); // execute the "colon" mode cmds
234 #if ENABLE_FEATURE_VI_USE_SIGNALS
235 static void winch_sig(int); // catch window size changes
236 static void suspend_sig(int); // catch ctrl-Z
237 static void catch_sig(int); // catch ctrl-C and alarm time-outs
239 #if ENABLE_FEATURE_VI_DOT_CMD
240 static void start_new_cmd_q(char); // new queue for command
241 static void end_cmd_q(void); // stop saving input chars
243 #define end_cmd_q() ((void)0)
245 #if ENABLE_FEATURE_VI_SETOPTS
246 static void showmatching(char *); // show the matching pair () [] {}
248 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
249 static char *string_insert(char *, char *); // insert the string at 'p'
251 #if ENABLE_FEATURE_VI_YANKMARK
252 static char *text_yank(char *, char *, int); // save copy of "p" into a register
253 static char what_reg(void); // what is letter of current YDreg
254 static void check_context(char); // remember context for '' command
256 #if ENABLE_FEATURE_VI_CRASHME
257 static void crash_dummy();
258 static void crash_test();
259 static int crashme = 0;
261 #if ENABLE_FEATURE_VI_COLON
262 static char *initial_cmds[] = { NULL, NULL , NULL }; // currently 2 entries, NULL terminated
266 static void write1(const char *out)
271 int vi_main(int argc, char **argv);
272 int vi_main(int argc, char **argv)
275 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
277 #if ENABLE_FEATURE_VI_YANKMARK
280 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
283 #if ENABLE_FEATURE_VI_CRASHME
284 srand((long) my_pid);
287 status_buffer = STATUS_BUFFER;
288 last_status_cksum = 0;
290 #if ENABLE_FEATURE_VI_READONLY
291 vi_readonly = readonly = FALSE;
292 if (strncmp(argv[0], "view", 4) == 0) {
297 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
298 #if ENABLE_FEATURE_VI_YANKMARK
299 for (i = 0; i < 28; i++) {
301 } // init the yank regs
303 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
304 modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
307 // 1- process $HOME/.exrc file (not inplemented yet)
308 // 2- process EXINIT variable from environment
309 // 3- process command line args
310 #if ENABLE_FEATURE_VI_COLON
312 char *p = getenv("EXINIT");
314 initial_cmds[0] = xstrdup(p);
317 while ((c = getopt(argc, argv, "hCR" USE_FEATURE_VI_COLON("c:"))) != -1) {
319 #if ENABLE_FEATURE_VI_CRASHME
324 #if ENABLE_FEATURE_VI_READONLY
325 case 'R': // Read-only flag
330 //case 'r': // recover flag- ignore- we don't use tmp file
331 //case 'x': // encryption flag- ignore
332 //case 'c': // execute command first
333 #if ENABLE_FEATURE_VI_COLON
334 case 'c': // cmd line vi command
336 initial_cmds[initial_cmds[0] != 0] = xstrdup(optarg);
338 //case 'h': // help -- just use default
346 // The argv array can be used by the ":next" and ":rewind" commands
348 fn_start = optind; // remember first file name for :next and :rew
351 //----- This is the main file handling loop --------------
352 if (optind >= argc) {
353 editing = 1; // 0= exit, 1= one file, 2= multiple files
356 for (; optind < argc; optind++) {
357 editing = 1; // 0=exit, 1=one file, 2+ =many files
359 cfn = xstrdup(argv[optind]);
363 //-----------------------------------------------------------
368 static void edit_file(char * fn)
373 #if ENABLE_FEATURE_VI_USE_SIGNALS
376 #if ENABLE_FEATURE_VI_YANKMARK
377 static char *cur_line;
384 if (ENABLE_FEATURE_VI_WIN_RESIZE)
385 get_terminal_width_height(0, &columns, &rows);
386 new_screen(rows, columns); // get memory for virtual screen
388 cnt = file_size(fn); // file size
389 size = 2 * cnt; // 200% of file size
390 new_text(size); // get a text[] buffer
391 screenbegin = dot = end = text;
393 ch = file_insert(fn, text, cnt);
396 char_insert(text, '\n'); // start empty buf with dummy line
399 last_file_modified = -1;
400 #if ENABLE_FEATURE_VI_YANKMARK
401 YDreg = 26; // default Yank/Delete reg
402 Ureg = 27; // hold orig line for "U" cmd
403 for (cnt = 0; cnt < 28; cnt++) {
406 mark[26] = mark[27] = text; // init "previous context"
409 last_forward_char = last_input_char = '\0';
413 #if ENABLE_FEATURE_VI_USE_SIGNALS
415 signal(SIGWINCH, winch_sig);
416 signal(SIGTSTP, suspend_sig);
417 sig = setjmp(restart);
419 screenbegin = dot = text;
424 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
427 offset = 0; // no horizontal offset
429 #if ENABLE_FEATURE_VI_DOT_CMD
430 free(last_modifying_cmd);
432 ioq = ioq_start = last_modifying_cmd = NULL;
435 redraw(FALSE); // dont force every col re-draw
438 #if ENABLE_FEATURE_VI_COLON
443 while ((p = initial_cmds[n])) {
453 free(initial_cmds[n]);
454 initial_cmds[n] = NULL;
459 //------This is the main Vi cmd handling loop -----------------------
460 while (editing > 0) {
461 #if ENABLE_FEATURE_VI_CRASHME
463 if ((end - text) > 1) {
464 crash_dummy(); // generate a random command
467 dot = string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
472 last_input_char = c = get_one_char(); // get a cmd from user
473 #if ENABLE_FEATURE_VI_YANKMARK
474 // save a copy of the current line- for the 'U" command
475 if (begin_line(dot) != cur_line) {
476 cur_line = begin_line(dot);
477 text_yank(begin_line(dot), end_line(dot), Ureg);
480 #if ENABLE_FEATURE_VI_DOT_CMD
481 // These are commands that change text[].
482 // Remember the input for the "." command
483 if (!adding2q && ioq_start == 0
484 && strchr(modifying_cmds, c)
489 do_cmd(c); // execute the user command
491 // poll to see if there is input already waiting. if we are
492 // not able to display output fast enough to keep up, skip
493 // the display update until we catch up with input.
494 if (mysleep(0) == 0) {
495 // no input pending- so update output
499 #if ENABLE_FEATURE_VI_CRASHME
501 crash_test(); // test editor variables
504 //-------------------------------------------------------------------
506 place_cursor(rows, 0, FALSE); // go to bottom of screen
507 clear_to_eol(); // Erase to end of line
511 //----- The Colon commands -------------------------------------
512 #if ENABLE_FEATURE_VI_COLON
513 static char *get_one_address(char * p, int *addr) // get colon addr, if present
518 #if ENABLE_FEATURE_VI_YANKMARK
521 #if ENABLE_FEATURE_VI_SEARCH
522 char *pat, buf[BUFSIZ];
525 *addr = -1; // assume no addr
526 if (*p == '.') { // the current line
529 *addr = count_lines(text, q);
530 #if ENABLE_FEATURE_VI_YANKMARK
531 } else if (*p == '\'') { // is this a mark addr
535 if (c >= 'a' && c <= 'z') {
538 q = mark[(unsigned char) c];
539 if (q != NULL) { // is mark valid
540 *addr = count_lines(text, q); // count lines
544 #if ENABLE_FEATURE_VI_SEARCH
545 } else if (*p == '/') { // a search pattern
553 pat = xstrdup(buf); // save copy of pattern
556 q = char_search(dot, pat, FORWARD, FULL);
558 *addr = count_lines(text, q);
562 } else if (*p == '$') { // the last line in file
564 q = begin_line(end - 1);
565 *addr = count_lines(text, q);
566 } else if (isdigit(*p)) { // specific line number
567 sscanf(p, "%d%n", addr, &st);
569 } else { // I don't reconise this
570 // unrecognised address- assume -1
576 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
578 //----- get the address' i.e., 1,3 'a,'b -----
579 // get FIRST addr, if present
581 p++; // skip over leading spaces
582 if (*p == '%') { // alias for 1,$
585 *e = count_lines(text, end-1);
588 p = get_one_address(p, b);
591 if (*p == ',') { // is there a address separator
595 // get SECOND addr, if present
596 p = get_one_address(p, e);
600 p++; // skip over trailing spaces
604 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
605 static void setops(const char *args, const char *opname, int flg_no,
606 const char *short_opname, int opt)
608 const char *a = args + flg_no;
609 int l = strlen(opname) - 1; /* opname have + ' ' */
611 if (strncasecmp(a, opname, l) == 0
612 || strncasecmp(a, short_opname, 2) == 0
622 static void colon(char * buf)
624 char c, *orig_buf, *buf1, *q, *r;
625 char *fn, cmd[BUFSIZ], args[BUFSIZ];
626 int i, l, li, ch, b, e;
627 int useforce = FALSE, forced = FALSE;
630 // :3154 // if (-e line 3154) goto it else stay put
631 // :4,33w! foo // write a portion of buffer to file "foo"
632 // :w // write all of buffer to current file
634 // :q! // quit- dont care about modified file
635 // :'a,'z!sort -u // filter block through sort
636 // :'f // goto mark "f"
637 // :'fl // list literal the mark "f" line
638 // :.r bar // read file "bar" into buffer before dot
639 // :/123/,/abc/d // delete lines from "123" line to "abc" line
640 // :/xyz/ // goto the "xyz" line
641 // :s/find/replace/ // substitute pattern "find" with "replace"
642 // :!<cmd> // run <cmd> then return
648 buf++; // move past the ':'
652 q = text; // assume 1,$ for the range
654 li = count_lines(text, end - 1);
655 fn = cfn; // default to current file
656 memset(cmd, '\0', BUFSIZ); // clear cmd[]
657 memset(args, '\0', BUFSIZ); // clear args[]
659 // look for optional address(es) :. :1 :1,9 :'q,'a :%
660 buf = get_address(buf, &b, &e);
662 // remember orig command line
665 // get the COMMAND into cmd[]
667 while (*buf != '\0') {
676 buf1 = last_char_is(cmd, '!');
679 *buf1 = '\0'; // get rid of !
682 // if there is only one addr, then the addr
683 // is the line number of the single line the
684 // user wants. So, reset the end
685 // pointer to point at end of the "b" line
686 q = find_line(b); // what line is #b
691 // we were given two addrs. change the
692 // end pointer to the addr given by user.
693 r = find_line(e); // what line is #e
697 // ------------ now look for the command ------------
699 if (i == 0) { // :123CR goto line #123
701 dot = find_line(b); // what line is #b
705 #if ENABLE_FEATURE_ALLOW_EXEC
706 else if (strncmp(cmd, "!", 1) == 0) { // run a cmd
707 // :!ls run the <cmd>
708 alarm(0); // wait for input- no alarms
709 place_cursor(rows - 1, 0, FALSE); // go to Status line
710 clear_to_eol(); // clear the line
712 system(orig_buf + 1); // run the cmd
714 Hit_Return(); // let user see results
715 alarm(3); // done waiting for input
718 else if (strncmp(cmd, "=", i) == 0) { // where is the address
719 if (b < 0) { // no addr given- use defaults
720 b = e = count_lines(text, dot);
723 } else if (strncasecmp(cmd, "delete", i) == 0) { // delete lines
724 if (b < 0) { // no addr given- use defaults
725 q = begin_line(dot); // assume .,. for the range
728 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
730 } else if (strncasecmp(cmd, "edit", i) == 0) { // Edit a file
733 // don't edit, if the current file has been modified
734 if (file_modified && ! useforce) {
735 psbs("No write since last change (:edit! overrides)");
739 // the user supplied a file name
741 } else if (cfn && cfn[0]) {
742 // no user supplied name- use the current filename
746 // no user file name, no current name- punt
747 psbs("No current filename");
751 // see if file exists- if not, its just a new file request
752 sr = stat(fn, &st_buf);
754 // This is just a request for a new file creation.
755 // The file_insert below will fail but we get
756 // an empty buffer with a file name. Then the "write"
757 // command can do the create.
759 if ((st_buf.st_mode & S_IFREG) == 0) {
760 // This is not a regular file
761 psbs("\"%s\" is not a regular file", fn);
764 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
765 // dont have any read permissions
766 psbs("\"%s\" is not readable", fn);
771 // There is a read-able regular file
772 // make this the current file
773 q = xstrdup(fn); // save the cfn
774 free(cfn); // free the old name
775 cfn = q; // remember new cfn
778 // delete all the contents of text[]
779 new_text(2 * file_size(fn));
780 screenbegin = dot = end = text;
783 ch = file_insert(fn, text, file_size(fn));
786 // start empty buf with dummy line
787 char_insert(text, '\n');
791 last_file_modified = -1;
792 #if ENABLE_FEATURE_VI_YANKMARK
793 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
794 free(reg[Ureg]); // free orig line reg- for 'U'
797 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
798 free(reg[YDreg]); // free default yank/delete register
801 for (li = 0; li < 28; li++) {
805 // how many lines in text[]?
806 li = count_lines(text, end - 1);
808 #if ENABLE_FEATURE_VI_READONLY
812 (sr < 0 ? " [New file]" : ""),
813 #if ENABLE_FEATURE_VI_READONLY
814 ((vi_readonly || readonly) ? " [Read only]" : ""),
817 } else if (strncasecmp(cmd, "file", i) == 0) { // what File is this
818 if (b != -1 || e != -1) {
819 ni("No address allowed on this command");
823 // user wants a new filename
827 // user wants file status info
828 last_status_cksum = 0; // force status update
830 } else if (strncasecmp(cmd, "features", i) == 0) { // what features are available
831 // print out values of all features
832 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
833 clear_to_eol(); // clear the line
838 } else if (strncasecmp(cmd, "list", i) == 0) { // literal print line
839 if (b < 0) { // no addr given- use defaults
840 q = begin_line(dot); // assume .,. for the range
843 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
844 clear_to_eol(); // clear the line
846 for (; q <= r; q++) {
850 c_is_no_print = (c & 0x80) && !Isprint(c);
857 } else if (c < ' ' || c == 127) {
868 #if ENABLE_FEATURE_VI_SET
872 } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
873 || strncasecmp(cmd, "next", i) == 0 // edit next file
876 // force end of argv list
883 // don't exit if the file been modified
885 psbs("No write since last change (:%s! overrides)",
886 (*cmd == 'q' ? "quit" : "next"));
889 // are there other file to edit
890 if (*cmd == 'q' && optind < save_argc - 1) {
891 psbs("%d more file to edit", (save_argc - optind - 1));
894 if (*cmd == 'n' && optind >= save_argc - 1) {
895 psbs("No more files to edit");
899 } else if (strncasecmp(cmd, "read", i) == 0) { // read file into text[]
902 psbs("No filename given");
905 if (b < 0) { // no addr given- use defaults
906 q = begin_line(dot); // assume "dot"
908 // read after current line- unless user said ":0r foo"
911 #if ENABLE_FEATURE_VI_READONLY
912 l = readonly; // remember current files' status
914 ch = file_insert(fn, q, file_size(fn));
915 #if ENABLE_FEATURE_VI_READONLY
919 goto vc1; // nothing was inserted
920 // how many lines in text[]?
921 li = count_lines(q, q + ch - 1);
923 #if ENABLE_FEATURE_VI_READONLY
927 #if ENABLE_FEATURE_VI_READONLY
928 ((vi_readonly || readonly) ? " [Read only]" : ""),
932 // if the insert is before "dot" then we need to update
937 } else if (strncasecmp(cmd, "rewind", i) == 0) { // rewind cmd line args
938 if (file_modified && ! useforce) {
939 psbs("No write since last change (:rewind! overrides)");
941 // reset the filenames to edit
942 optind = fn_start - 1;
945 #if ENABLE_FEATURE_VI_SET
946 } else if (strncasecmp(cmd, "set", i) == 0) { // set or clear features
947 #if ENABLE_FEATURE_VI_SETOPTS
950 i = 0; // offset into args
951 // only blank is regarded as args delmiter. What about tab '\t' ?
952 if (!args[0] || strcasecmp(args, "all") == 0) {
953 // print out values of all options
954 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
955 clear_to_eol(); // clear the line
956 printf("----------------------------------------\r\n");
957 #if ENABLE_FEATURE_VI_SETOPTS
960 printf("autoindent ");
966 printf("ignorecase ");
969 printf("showmatch ");
970 printf("tabstop=%d ", tabstop);
975 #if ENABLE_FEATURE_VI_SETOPTS
978 if (strncasecmp(argp, "no", 2) == 0)
979 i = 2; // ":set noautoindent"
980 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
981 setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
982 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
983 setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
985 if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
986 sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
987 if (ch > 0 && ch < columns - 1)
990 while (*argp && *argp != ' ')
991 argp++; // skip to arg delimiter (i.e. blank)
992 while (*argp && *argp == ' ')
993 argp++; // skip all delimiting blanks
995 #endif /* FEATURE_VI_SETOPTS */
996 #endif /* FEATURE_VI_SET */
997 #if ENABLE_FEATURE_VI_SEARCH
998 } else if (strncasecmp(cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1002 // F points to the "find" pattern
1003 // R points to the "replace" pattern
1004 // replace the cmd line delimiters "/" with NULLs
1005 gflag = 0; // global replace flag
1006 c = orig_buf[1]; // what is the delimiter
1007 F = orig_buf + 2; // start of "find"
1008 R = strchr(F, c); // middle delimiter
1009 if (!R) goto colon_s_fail;
1010 *R++ = '\0'; // terminate "find"
1011 buf1 = strchr(R, c);
1012 if (!buf1) goto colon_s_fail;
1013 *buf1++ = '\0'; // terminate "replace"
1014 if (*buf1 == 'g') { // :s/foo/bar/g
1016 gflag++; // turn on gflag
1019 if (b < 0) { // maybe :s/foo/bar/
1020 q = begin_line(dot); // start with cur line
1021 b = count_lines(text, q); // cur line number
1024 e = b; // maybe :.s/foo/bar/
1025 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1026 ls = q; // orig line start
1028 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1030 // we found the "find" pattern - delete it
1031 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1032 // inset the "replace" patern
1033 string_insert(buf1, R); // insert the string
1034 // check for "global" :s/foo/bar/g
1036 if ((buf1 + strlen(R)) < end_line(ls)) {
1037 q = buf1 + strlen(R);
1038 goto vc4; // don't let q move past cur line
1044 #endif /* FEATURE_VI_SEARCH */
1045 } else if (strncasecmp(cmd, "version", i) == 0) { // show software version
1046 psb("%s", BB_VER " " BB_BT);
1047 } else if (strncasecmp(cmd, "write", i) == 0 // write text to file
1048 || strncasecmp(cmd, "wq", i) == 0
1049 || strncasecmp(cmd, "wn", i) == 0
1050 || strncasecmp(cmd, "x", i) == 0
1052 // is there a file name to write to?
1056 #if ENABLE_FEATURE_VI_READONLY
1057 if ((vi_readonly || readonly) && ! useforce) {
1058 psbs("\"%s\" File is read only", fn);
1062 // how many lines in text[]?
1063 li = count_lines(q, r);
1065 // see if file exists- if not, its just a new file request
1067 // if "fn" is not write-able, chmod u+w
1068 // sprintf(syscmd, "chmod u+w %s", fn);
1072 l = file_write(fn, q, r);
1073 if (useforce && forced) {
1075 // sprintf(syscmd, "chmod u-w %s", fn);
1081 psbs("Write error: %s", strerror(errno));
1083 psb("\"%s\" %dL, %dC", fn, li, l);
1084 if (q == text && r == end - 1 && l == ch) {
1086 last_file_modified = -1;
1088 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1089 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1094 #if ENABLE_FEATURE_VI_READONLY
1097 #if ENABLE_FEATURE_VI_YANKMARK
1098 } else if (strncasecmp(cmd, "yank", i) == 0) { // yank lines
1099 if (b < 0) { // no addr given- use defaults
1100 q = begin_line(dot); // assume .,. for the range
1103 text_yank(q, r, YDreg);
1104 li = count_lines(q, r);
1105 psb("Yank %d lines (%d chars) into [%c]",
1106 li, strlen(reg[YDreg]), what_reg());
1113 dot = bound_dot(dot); // make sure "dot" is valid
1115 #if ENABLE_FEATURE_VI_SEARCH
1117 psb(":s expression missing delimiters");
1121 #endif /* FEATURE_VI_COLON */
1123 static void Hit_Return(void)
1127 standout_start(); // start reverse video
1128 write1("[Hit return to continue]");
1129 standout_end(); // end reverse video
1130 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1132 redraw(TRUE); // force redraw all
1135 //----- Synchronize the cursor to Dot --------------------------
1136 static void sync_cursor(char * d, int *row, int *col)
1138 char *beg_cur; // begin and end of "d" line
1139 char *end_scr; // begin and end of screen
1143 beg_cur = begin_line(d); // first char of cur line
1145 end_scr = end_screen(); // last char of screen
1147 if (beg_cur < screenbegin) {
1148 // "d" is before top line on screen
1149 // how many lines do we have to move
1150 cnt = count_lines(beg_cur, screenbegin);
1152 screenbegin = beg_cur;
1153 if (cnt > (rows - 1) / 2) {
1154 // we moved too many lines. put "dot" in middle of screen
1155 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1156 screenbegin = prev_line(screenbegin);
1159 } else if (beg_cur > end_scr) {
1160 // "d" is after bottom line on screen
1161 // how many lines do we have to move
1162 cnt = count_lines(end_scr, beg_cur);
1163 if (cnt > (rows - 1) / 2)
1164 goto sc1; // too many lines
1165 for (ro = 0; ro < cnt - 1; ro++) {
1166 // move screen begin the same amount
1167 screenbegin = next_line(screenbegin);
1168 // now, move the end of screen
1169 end_scr = next_line(end_scr);
1170 end_scr = end_line(end_scr);
1173 // "d" is on screen- find out which row
1175 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1181 // find out what col "d" is on
1183 do { // drive "co" to correct column
1184 if (*tp == '\n' || *tp == '\0')
1188 co += ((tabstop - 1) - (co % tabstop));
1189 } else if (*tp < ' ' || *tp == 127) {
1190 co++; // display as ^X, use 2 columns
1192 } while (tp++ < d && ++co);
1194 // "co" is the column where "dot" is.
1195 // The screen has "columns" columns.
1196 // The currently displayed columns are 0+offset -- columns+ofset
1197 // |-------------------------------------------------------------|
1199 // offset | |------- columns ----------------|
1201 // If "co" is already in this range then we do not have to adjust offset
1202 // but, we do have to subtract the "offset" bias from "co".
1203 // If "co" is outside this range then we have to change "offset".
1204 // If the first char of a line is a tab the cursor will try to stay
1205 // in column 7, but we have to set offset to 0.
1207 if (co < 0 + offset) {
1210 if (co >= columns + offset) {
1211 offset = co - columns + 1;
1213 // if the first char of the line is a tab, and "dot" is sitting on it
1214 // force offset to 0.
1215 if (d == beg_cur && *d == '\t') {
1224 //----- Text Movement Routines ---------------------------------
1225 static char *begin_line(char * p) // return pointer to first char cur line
1227 while (p > text && p[-1] != '\n')
1228 p--; // go to cur line B-o-l
1232 static char *end_line(char * p) // return pointer to NL of cur line line
1234 while (p < end - 1 && *p != '\n')
1235 p++; // go to cur line E-o-l
1239 static inline char *dollar_line(char * p) // return pointer to just before NL line
1241 while (p < end - 1 && *p != '\n')
1242 p++; // go to cur line E-o-l
1243 // Try to stay off of the Newline
1244 if (*p == '\n' && (p - begin_line(p)) > 0)
1249 static char *prev_line(char * p) // return pointer first char prev line
1251 p = begin_line(p); // goto begining of cur line
1252 if (p[-1] == '\n' && p > text)
1253 p--; // step to prev line
1254 p = begin_line(p); // goto begining of prev line
1258 static char *next_line(char * p) // return pointer first char next line
1261 if (*p == '\n' && p < end - 1)
1262 p++; // step to next line
1266 //----- Text Information Routines ------------------------------
1267 static char *end_screen(void)
1272 // find new bottom line
1274 for (cnt = 0; cnt < rows - 2; cnt++)
1280 static int count_lines(char * start, char * stop) // count line from start to stop
1285 if (stop < start) { // start and stop are backwards- reverse them
1291 stop = end_line(stop); // get to end of this line
1292 for (q = start; q <= stop && q <= end - 1; q++) {
1299 static char *find_line(int li) // find begining of line #li
1303 for (q = text; li > 1; li--) {
1309 //----- Dot Movement Routines ----------------------------------
1310 static void dot_left(void)
1312 if (dot > text && dot[-1] != '\n')
1316 static void dot_right(void)
1318 if (dot < end - 1 && *dot != '\n')
1322 static void dot_begin(void)
1324 dot = begin_line(dot); // return pointer to first char cur line
1327 static void dot_end(void)
1329 dot = end_line(dot); // return pointer to last char cur line
1332 static char *move_to_col(char * p, int l)
1339 if (*p == '\n' || *p == '\0')
1343 co += ((tabstop - 1) - (co % tabstop));
1344 } else if (*p < ' ' || *p == 127) {
1345 co++; // display as ^X, use 2 columns
1347 } while (++co <= l && p++ < end);
1351 static void dot_next(void)
1353 dot = next_line(dot);
1356 static void dot_prev(void)
1358 dot = prev_line(dot);
1361 static void dot_scroll(int cnt, int dir)
1365 for (; cnt > 0; cnt--) {
1368 // ctrl-Y scroll up one line
1369 screenbegin = prev_line(screenbegin);
1372 // ctrl-E scroll down one line
1373 screenbegin = next_line(screenbegin);
1376 // make sure "dot" stays on the screen so we dont scroll off
1377 if (dot < screenbegin)
1379 q = end_screen(); // find new bottom line
1381 dot = begin_line(q); // is dot is below bottom line?
1385 static void dot_skip_over_ws(void)
1388 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1392 static void dot_delete(void) // delete the char at 'dot'
1394 text_hole_delete(dot, dot);
1397 static char *bound_dot(char * p) // make sure text[0] <= P < "end"
1399 if (p >= end && end > text) {
1401 indicate_error('1');
1405 indicate_error('2');
1410 //----- Helper Utility Routines --------------------------------
1412 //----------------------------------------------------------------
1413 //----- Char Routines --------------------------------------------
1414 /* Chars that are part of a word-
1415 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1416 * Chars that are Not part of a word (stoppers)
1417 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1418 * Chars that are WhiteSpace
1419 * TAB NEWLINE VT FF RETURN SPACE
1420 * DO NOT COUNT NEWLINE AS WHITESPACE
1423 static char *new_screen(int ro, int co)
1428 screensize = ro * co + 8;
1429 screen = xmalloc(screensize);
1430 // initialize the new screen. assume this will be a empty file.
1432 // non-existent text[] lines start with a tilde (~).
1433 for (li = 1; li < ro - 1; li++) {
1434 screen[(li * co) + 0] = '~';
1439 static char *new_text(int size)
1442 size = 10240; // have a minimum size for new files
1444 text = xmalloc(size + 8);
1445 memset(text, '\0', size); // clear new text[]
1446 //text += 4; // leave some room for "oops"
1450 #if ENABLE_FEATURE_VI_SEARCH
1451 static int mycmp(const char * s1, const char * s2, int len)
1455 i = strncmp(s1, s2, len);
1456 #if ENABLE_FEATURE_VI_SETOPTS
1458 i = strncasecmp(s1, s2, len);
1464 // search for pattern starting at p
1465 static char *char_search(char * p, const char * pat, int dir, int range)
1467 #ifndef REGEX_SEARCH
1472 if (dir == FORWARD) {
1473 stop = end - 1; // assume range is p - end-1
1474 if (range == LIMITED)
1475 stop = next_line(p); // range is to next line
1476 for (start = p; start < stop; start++) {
1477 if (mycmp(start, pat, len) == 0) {
1481 } else if (dir == BACK) {
1482 stop = text; // assume range is text - p
1483 if (range == LIMITED)
1484 stop = prev_line(p); // range is to prev line
1485 for (start = p - len; start >= stop; start--) {
1486 if (mycmp(start, pat, len) == 0) {
1491 // pattern not found
1493 #else /* REGEX_SEARCH */
1495 struct re_pattern_buffer preg;
1499 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1505 // assume a LIMITED forward search
1513 // count the number of chars to search over, forward or backward
1517 // RANGE could be negative if we are searching backwards
1520 q = re_compile_pattern(pat, strlen(pat), &preg);
1522 // The pattern was not compiled
1523 psbs("bad search pattern: \"%s\": %s", pat, q);
1524 i = 0; // return p if pattern not compiled
1534 // search for the compiled pattern, preg, in p[]
1535 // range < 0- search backward
1536 // range > 0- search forward
1538 // re_search() < 0 not found or error
1539 // re_search() > 0 index of found pattern
1540 // struct pattern char int int int struct reg
1541 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1542 i = re_search(&preg, q, size, 0, range, 0);
1545 i = 0; // return NULL if pattern not found
1548 if (dir == FORWARD) {
1554 #endif /* REGEX_SEARCH */
1556 #endif /* FEATURE_VI_SEARCH */
1558 static char *char_insert(char * p, char c) // insert the char c at 'p'
1560 if (c == 22) { // Is this an ctrl-V?
1561 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1562 p--; // backup onto ^
1563 refresh(FALSE); // show the ^
1567 file_modified++; // has the file been modified
1568 } else if (c == 27) { // Is this an ESC?
1571 end_cmd_q(); // stop adding to q
1572 last_status_cksum = 0; // force status update
1573 if ((p[-1] != '\n') && (dot > text)) {
1576 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1578 if ((p[-1] != '\n') && (dot>text)) {
1580 p = text_hole_delete(p, p); // shrink buffer 1 char
1583 // insert a char into text[]
1584 char *sp; // "save p"
1587 c = '\n'; // translate \r to \n
1588 sp = p; // remember addr of insert
1589 p = stupid_insert(p, c); // insert the char
1590 #if ENABLE_FEATURE_VI_SETOPTS
1591 if (showmatch && strchr(")]}", *sp) != NULL) {
1594 if (autoindent && c == '\n') { // auto indent the new line
1597 q = prev_line(p); // use prev line as templet
1598 for (; isblnk(*q); q++) {
1599 p = stupid_insert(p, *q); // insert the char
1607 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1609 p = text_hole_make(p, 1);
1612 file_modified++; // has the file been modified
1618 static char find_range(char ** start, char ** stop, char c)
1620 char *save_dot, *p, *q;
1626 if (strchr("cdy><", c)) {
1627 // these cmds operate on whole lines
1628 p = q = begin_line(p);
1629 for (cnt = 1; cnt < cmdcnt; cnt++) {
1633 } else if (strchr("^%$0bBeEft", c)) {
1634 // These cmds operate on char positions
1635 do_cmd(c); // execute movement cmd
1637 } else if (strchr("wW", c)) {
1638 do_cmd(c); // execute movement cmd
1639 // if we are at the next word's first char
1640 // step back one char
1641 // but check the possibilities when it is true
1642 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1643 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1644 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1645 dot--; // move back off of next word
1646 if (dot > text && *dot == '\n')
1647 dot--; // stay off NL
1649 } else if (strchr("H-k{", c)) {
1650 // these operate on multi-lines backwards
1651 q = end_line(dot); // find NL
1652 do_cmd(c); // execute movement cmd
1655 } else if (strchr("L+j}\r\n", c)) {
1656 // these operate on multi-lines forwards
1657 p = begin_line(dot);
1658 do_cmd(c); // execute movement cmd
1659 dot_end(); // find NL
1662 c = 27; // error- return an ESC char
1675 static int st_test(char * p, int type, int dir, char * tested)
1685 if (type == S_BEFORE_WS) {
1687 test = ((!isspace(c)) || c == '\n');
1689 if (type == S_TO_WS) {
1691 test = ((!isspace(c)) || c == '\n');
1693 if (type == S_OVER_WS) {
1695 test = ((isspace(c)));
1697 if (type == S_END_PUNCT) {
1699 test = ((ispunct(c)));
1701 if (type == S_END_ALNUM) {
1703 test = ((isalnum(c)) || c == '_');
1709 static char *skip_thing(char * p, int linecnt, int dir, int type)
1713 while (st_test(p, type, dir, &c)) {
1714 // make sure we limit search to correct number of lines
1715 if (c == '\n' && --linecnt < 1)
1717 if (dir >= 0 && p >= end - 1)
1719 if (dir < 0 && p <= text)
1721 p += dir; // move to next char
1726 // find matching char of pair () [] {}
1727 static char *find_pair(char * p, char c)
1734 dir = 1; // assume forward
1758 for (q = p + dir; text <= q && q < end; q += dir) {
1759 // look for match, count levels of pairs (( ))
1761 level++; // increase pair levels
1763 level--; // reduce pair level
1765 break; // found matching pair
1768 q = NULL; // indicate no match
1772 #if ENABLE_FEATURE_VI_SETOPTS
1773 // show the matching char of a pair, () [] {}
1774 static void showmatching(char * p)
1778 // we found half of a pair
1779 q = find_pair(p, *p); // get loc of matching char
1781 indicate_error('3'); // no matching char
1783 // "q" now points to matching pair
1784 save_dot = dot; // remember where we are
1785 dot = q; // go to new loc
1786 refresh(FALSE); // let the user see it
1787 mysleep(40); // give user some time
1788 dot = save_dot; // go back to old loc
1792 #endif /* FEATURE_VI_SETOPTS */
1794 // open a hole in text[]
1795 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1804 cnt = end - src; // the rest of buffer
1805 if (memmove(dest, src, cnt) != dest) {
1806 psbs("can't create room for new characters");
1808 memset(p, ' ', size); // clear new hole
1809 end = end + size; // adjust the new END
1810 file_modified++; // has the file been modified
1815 // close a hole in text[]
1816 static char *text_hole_delete(char * p, char * q) // delete "p" thru "q", inclusive
1821 // move forwards, from beginning
1825 if (q < p) { // they are backward- swap them
1829 hole_size = q - p + 1;
1831 if (src < text || src > end)
1833 if (dest < text || dest >= end)
1836 goto thd_atend; // just delete the end of the buffer
1837 if (memmove(dest, src, cnt) != dest) {
1838 psbs("can't delete the character");
1841 end = end - hole_size; // adjust the new END
1843 dest = end - 1; // make sure dest in below end-1
1845 dest = end = text; // keep pointers valid
1846 file_modified++; // has the file been modified
1851 // copy text into register, then delete text.
1852 // if dist <= 0, do not include, or go past, a NewLine
1854 static char *yank_delete(char * start, char * stop, int dist, int yf)
1858 // make sure start <= stop
1860 // they are backwards, reverse them
1866 // we cannot cross NL boundaries
1870 // dont go past a NewLine
1871 for (; p + 1 <= stop; p++) {
1873 stop = p; // "stop" just before NewLine
1879 #if ENABLE_FEATURE_VI_YANKMARK
1880 text_yank(start, stop, YDreg);
1882 if (yf == YANKDEL) {
1883 p = text_hole_delete(start, stop);
1888 static void show_help(void)
1890 puts("These features are available:"
1891 #if ENABLE_FEATURE_VI_SEARCH
1892 "\n\tPattern searches with / and ?"
1894 #if ENABLE_FEATURE_VI_DOT_CMD
1895 "\n\tLast command repeat with \'.\'"
1897 #if ENABLE_FEATURE_VI_YANKMARK
1898 "\n\tLine marking with 'x"
1899 "\n\tNamed buffers with \"x"
1901 #if ENABLE_FEATURE_VI_READONLY
1902 "\n\tReadonly if vi is called as \"view\""
1903 "\n\tReadonly with -R command line arg"
1905 #if ENABLE_FEATURE_VI_SET
1906 "\n\tSome colon mode commands with \':\'"
1908 #if ENABLE_FEATURE_VI_SETOPTS
1909 "\n\tSettable options with \":set\""
1911 #if ENABLE_FEATURE_VI_USE_SIGNALS
1912 "\n\tSignal catching- ^C"
1913 "\n\tJob suspend and resume with ^Z"
1915 #if ENABLE_FEATURE_VI_WIN_RESIZE
1916 "\n\tAdapt to window re-sizes"
1921 static inline void print_literal(char * buf, const char * s) // copy s to buf, convert unprintable
1934 c_is_no_print = (c & 0x80) && !Isprint(c);
1935 if (c_is_no_print) {
1939 if (c < ' ' || c == 127) {
1955 #if ENABLE_FEATURE_VI_DOT_CMD
1956 static void start_new_cmd_q(char c)
1959 free(last_modifying_cmd);
1960 // get buffer for new cmd
1961 last_modifying_cmd = xmalloc(BUFSIZ);
1962 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1963 // if there is a current cmd count put it in the buffer first
1965 sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1966 else // just save char c onto queue
1967 last_modifying_cmd[0] = c;
1971 static void end_cmd_q(void)
1973 #if ENABLE_FEATURE_VI_YANKMARK
1974 YDreg = 26; // go back to default Yank/Delete reg
1978 #endif /* FEATURE_VI_DOT_CMD */
1980 #if ENABLE_FEATURE_VI_YANKMARK \
1981 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1982 || ENABLE_FEATURE_VI_CRASHME
1983 static char *string_insert(char * p, char * s) // insert the string at 'p'
1988 p = text_hole_make(p, i);
1990 for (cnt = 0; *s != '\0'; s++) {
1994 #if ENABLE_FEATURE_VI_YANKMARK
1995 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2001 #if ENABLE_FEATURE_VI_YANKMARK
2002 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2007 if (q < p) { // they are backwards- reverse them
2014 free(t); // if already a yank register, free it
2015 t = xmalloc(cnt + 1); // get a new register
2016 memset(t, '\0', cnt + 1); // clear new text[]
2017 strncpy(t, p, cnt); // copy text[] into bufer
2022 static char what_reg(void)
2026 c = 'D'; // default to D-reg
2027 if (0 <= YDreg && YDreg <= 25)
2028 c = 'a' + (char) YDreg;
2036 static void check_context(char cmd)
2038 // A context is defined to be "modifying text"
2039 // Any modifying command establishes a new context.
2041 if (dot < context_start || dot > context_end) {
2042 if (strchr(modifying_cmds, cmd) != NULL) {
2043 // we are trying to modify text[]- make this the current context
2044 mark[27] = mark[26]; // move cur to prev
2045 mark[26] = dot; // move local to cur
2046 context_start = prev_line(prev_line(dot));
2047 context_end = next_line(next_line(dot));
2048 //loiter= start_loiter= now;
2053 static inline char *swap_context(char * p) // goto new context for '' command make this the current context
2057 // the current context is in mark[26]
2058 // the previous context is in mark[27]
2059 // only swap context if other context is valid
2060 if (text <= mark[27] && mark[27] <= end - 1) {
2062 mark[27] = mark[26];
2064 p = mark[26]; // where we are going- previous context
2065 context_start = prev_line(prev_line(prev_line(p)));
2066 context_end = next_line(next_line(next_line(p)));
2070 #endif /* FEATURE_VI_YANKMARK */
2072 static int isblnk(char c) // is the char a blank or tab
2074 return (c == ' ' || c == '\t');
2077 //----- Set terminal attributes --------------------------------
2078 static void rawmode(void)
2080 tcgetattr(0, &term_orig);
2081 term_vi = term_orig;
2082 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2083 term_vi.c_iflag &= (~IXON & ~ICRNL);
2084 term_vi.c_oflag &= (~ONLCR);
2085 term_vi.c_cc[VMIN] = 1;
2086 term_vi.c_cc[VTIME] = 0;
2087 erase_char = term_vi.c_cc[VERASE];
2088 tcsetattr(0, TCSANOW, &term_vi);
2091 static void cookmode(void)
2094 tcsetattr(0, TCSANOW, &term_orig);
2097 //----- Come here when we get a window resize signal ---------
2098 #if ENABLE_FEATURE_VI_USE_SIGNALS
2099 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2101 signal(SIGWINCH, winch_sig);
2102 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2103 get_terminal_width_height(0, &columns, &rows);
2104 new_screen(rows, columns); // get memory for virtual screen
2105 redraw(TRUE); // re-draw the screen
2108 //----- Come here when we get a continue signal -------------------
2109 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2111 rawmode(); // terminal to "raw"
2112 last_status_cksum = 0; // force status update
2113 redraw(TRUE); // re-draw the screen
2115 signal(SIGTSTP, suspend_sig);
2116 signal(SIGCONT, SIG_DFL);
2117 kill(my_pid, SIGCONT);
2120 //----- Come here when we get a Suspend signal -------------------
2121 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2123 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2124 clear_to_eol(); // Erase to end of line
2125 cookmode(); // terminal to "cooked"
2127 signal(SIGCONT, cont_sig);
2128 signal(SIGTSTP, SIG_DFL);
2129 kill(my_pid, SIGTSTP);
2132 //----- Come here when we get a signal ---------------------------
2133 static void catch_sig(int sig)
2135 signal(SIGINT, catch_sig);
2137 longjmp(restart, sig);
2139 #endif /* FEATURE_VI_USE_SIGNALS */
2141 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2146 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2151 tv.tv_usec = hund * 10000;
2152 select(1, &rfds, NULL, NULL, &tv);
2153 return FD_ISSET(0, &rfds);
2156 #define readbuffer bb_common_bufsiz1
2158 static int readed_for_parse;
2160 //----- IO Routines --------------------------------------------
2161 static char readit(void) // read (maybe cursor) key from stdin
2170 static const struct esc_cmds esccmds[] = {
2171 {"OA", VI_K_UP}, // cursor key Up
2172 {"OB", VI_K_DOWN}, // cursor key Down
2173 {"OC", VI_K_RIGHT}, // Cursor Key Right
2174 {"OD", VI_K_LEFT}, // cursor key Left
2175 {"OH", VI_K_HOME}, // Cursor Key Home
2176 {"OF", VI_K_END}, // Cursor Key End
2177 {"[A", VI_K_UP}, // cursor key Up
2178 {"[B", VI_K_DOWN}, // cursor key Down
2179 {"[C", VI_K_RIGHT}, // Cursor Key Right
2180 {"[D", VI_K_LEFT}, // cursor key Left
2181 {"[H", VI_K_HOME}, // Cursor Key Home
2182 {"[F", VI_K_END}, // Cursor Key End
2183 {"[1~", VI_K_HOME}, // Cursor Key Home
2184 {"[2~", VI_K_INSERT}, // Cursor Key Insert
2185 {"[4~", VI_K_END}, // Cursor Key End
2186 {"[5~", VI_K_PAGEUP}, // Cursor Key Page Up
2187 {"[6~", VI_K_PAGEDOWN},// Cursor Key Page Down
2188 {"OP", VI_K_FUN1}, // Function Key F1
2189 {"OQ", VI_K_FUN2}, // Function Key F2
2190 {"OR", VI_K_FUN3}, // Function Key F3
2191 {"OS", VI_K_FUN4}, // Function Key F4
2192 {"[15~", VI_K_FUN5}, // Function Key F5
2193 {"[17~", VI_K_FUN6}, // Function Key F6
2194 {"[18~", VI_K_FUN7}, // Function Key F7
2195 {"[19~", VI_K_FUN8}, // Function Key F8
2196 {"[20~", VI_K_FUN9}, // Function Key F9
2197 {"[21~", VI_K_FUN10}, // Function Key F10
2198 {"[23~", VI_K_FUN11}, // Function Key F11
2199 {"[24~", VI_K_FUN12}, // Function Key F12
2200 {"[11~", VI_K_FUN1}, // Function Key F1
2201 {"[12~", VI_K_FUN2}, // Function Key F2
2202 {"[13~", VI_K_FUN3}, // Function Key F3
2203 {"[14~", VI_K_FUN4}, // Function Key F4
2206 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2208 alarm(0); // turn alarm OFF while we wait for input
2210 n = readed_for_parse;
2211 // get input from User- are there already input chars in Q?
2214 // the Q is empty, wait for a typed char
2215 n = read(0, readbuffer, BUFSIZ - 1);
2218 goto ri0; // interrupted sys call
2221 if (errno == EFAULT)
2223 if (errno == EINVAL)
2231 if (readbuffer[0] == 27) {
2235 // This is an ESC char. Is this Esc sequence?
2236 // Could be bare Esc key. See if there are any
2237 // more chars to read after the ESC. This would
2238 // be a Function or Cursor Key sequence.
2242 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2244 // keep reading while there are input chars and room in buffer
2245 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2246 // read the rest of the ESC string
2247 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2253 readed_for_parse = n;
2256 if (c == 27 && n > 1) {
2257 // Maybe cursor or function key?
2258 const struct esc_cmds *eindex;
2260 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2261 int cnt = strlen(eindex->seq);
2265 if (strncmp(eindex->seq, readbuffer + 1, cnt))
2267 // is a Cursor key- put derived value back into Q
2269 // for squeeze out the ESC sequence
2273 if (eindex == &esccmds[ESCCMDS_COUNT]) {
2274 /* defined ESC sequence not found, set only one ESC */
2280 // remove key sequence from Q
2281 readed_for_parse -= n;
2282 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2283 alarm(3); // we are done waiting for input, turn alarm ON
2287 //----- IO Routines --------------------------------------------
2288 static char get_one_char(void)
2292 #if ENABLE_FEATURE_VI_DOT_CMD
2293 // ! adding2q && ioq == 0 read()
2294 // ! adding2q && ioq != 0 *ioq
2295 // adding2q *last_modifying_cmd= read()
2297 // we are not adding to the q.
2298 // but, we may be reading from a q
2300 // there is no current q, read from STDIN
2301 c = readit(); // get the users input
2303 // there is a queue to get chars from first
2306 // the end of the q, read from STDIN
2308 ioq_start = ioq = 0;
2309 c = readit(); // get the users input
2313 // adding STDIN chars to q
2314 c = readit(); // get the users input
2315 if (last_modifying_cmd != 0) {
2316 int len = strlen(last_modifying_cmd);
2317 if (len + 1 >= BUFSIZ) {
2318 psbs("last_modifying_cmd overrun");
2320 // add new char to q
2321 last_modifying_cmd[len] = c;
2326 c = readit(); // get the users input
2327 #endif /* FEATURE_VI_DOT_CMD */
2328 return c; // return the char, where ever it came from
2331 static char *get_input_line(const char * prompt) // get input line- use "status line"
2339 strcpy(buf, prompt);
2340 last_status_cksum = 0; // force status update
2341 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2342 clear_to_eol(); // clear the line
2343 write1(prompt); // write out the :, /, or ? prompt
2346 while (i < BUFSIZ) {
2347 c = get_one_char(); // read user input
2348 if (c == '\n' || c == '\r' || c == 27)
2349 break; // is this end of input
2350 if (c == erase_char || c == 8 || c == 127) {
2351 // user wants to erase prev char
2352 i--; // backup to prev char
2353 buf[i] = '\0'; // erase the char
2354 buf[i + 1] = '\0'; // null terminate buffer
2355 write1("\b \b"); // erase char on screen
2356 if (i <= 0) { // user backs up before b-o-l, exit
2360 buf[i] = c; // save char in buffer
2361 buf[i + 1] = '\0'; // make sure buffer is null terminated
2362 putchar(c); // echo the char back to user
2368 obufp = xstrdup(buf);
2372 static int file_size(const char * fn) // what is the byte size of "fn"
2380 sr = stat(fn, &st_buf); // see if file exists
2382 cnt = (int) st_buf.st_size;
2387 static int file_insert(char * fn, char * p, int size)
2392 #if ENABLE_FEATURE_VI_READONLY
2395 if (!fn || !fn[0]) {
2396 psbs("No filename given");
2400 // OK- this is just a no-op
2405 psbs("Trying to insert a negative number (%d) of characters", size);
2408 if (p < text || p > end) {
2409 psbs("Trying to insert file outside of memory");
2413 // see if we can open the file
2414 #if ENABLE_FEATURE_VI_READONLY
2415 if (vi_readonly) goto fi1; // do not try write-mode
2417 fd = open(fn, O_RDWR); // assume read & write
2419 // could not open for writing- maybe file is read only
2420 #if ENABLE_FEATURE_VI_READONLY
2423 fd = open(fn, O_RDONLY); // try read-only
2425 psbs("\"%s\" %s", fn, "cannot open file");
2428 #if ENABLE_FEATURE_VI_READONLY
2429 // got the file- read-only
2433 p = text_hole_make(p, size);
2434 cnt = read(fd, p, size);
2438 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2439 psbs("cannot read file \"%s\"", fn);
2440 } else if (cnt < size) {
2441 // There was a partial read, shrink unused space text[]
2442 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2443 psbs("cannot read all of file \"%s\"", fn);
2451 static int file_write(char * fn, char * first, char * last)
2453 int fd, cnt, charcnt;
2456 psbs("No current filename");
2460 // FIXIT- use the correct umask()
2461 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2464 cnt = last - first + 1;
2465 charcnt = write(fd, first, cnt);
2466 if (charcnt == cnt) {
2468 //file_modified = FALSE; // the file has not been modified
2476 //----- Terminal Drawing ---------------------------------------
2477 // The terminal is made up of 'rows' line of 'columns' columns.
2478 // classically this would be 24 x 80.
2479 // screen coordinates
2485 // 23,0 ... 23,79 status line
2488 //----- Move the cursor to row x col (count from 0, not 1) -------
2489 static void place_cursor(int row, int col, int opti)
2493 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2496 // char cm3[BUFSIZ];
2497 int Rrow = last_row;
2500 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2502 if (row < 0) row = 0;
2503 if (row >= rows) row = rows - 1;
2504 if (col < 0) col = 0;
2505 if (col >= columns) col = columns - 1;
2507 //----- 1. Try the standard terminal ESC sequence
2508 sprintf(cm1, CMrc, row + 1, col + 1);
2513 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2514 //----- find the minimum # of chars to move cursor -------------
2515 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2516 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2518 // move to the correct row
2519 while (row < Rrow) {
2520 // the cursor has to move up
2524 while (row > Rrow) {
2525 // the cursor has to move down
2526 strcat(cm2, CMdown);
2530 // now move to the correct column
2531 strcat(cm2, "\r"); // start at col 0
2532 // just send out orignal source char to get to correct place
2533 screenp = &screen[row * columns]; // start of screen line
2534 strncat(cm2, screenp, col);
2536 //----- 3. Try some other way of moving cursor
2537 //---------------------------------------------
2539 // pick the shortest cursor motion to send out
2541 if (strlen(cm2) < strlen(cm)) {
2543 } /* else if (strlen(cm3) < strlen(cm)) {
2546 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2548 write1(cm); // move the cursor
2551 //----- Erase from cursor to end of line -----------------------
2552 static void clear_to_eol(void)
2554 write1(Ceol); // Erase from cursor to end of line
2557 //----- Erase from cursor to end of screen -----------------------
2558 static void clear_to_eos(void)
2560 write1(Ceos); // Erase from cursor to end of screen
2563 //----- Start standout mode ------------------------------------
2564 static void standout_start(void) // send "start reverse video" sequence
2566 write1(SOs); // Start reverse video mode
2569 //----- End standout mode --------------------------------------
2570 static void standout_end(void) // send "end reverse video" sequence
2572 write1(SOn); // End reverse video mode
2575 //----- Flash the screen --------------------------------------
2576 static void flash(int h)
2578 standout_start(); // send "start reverse video" sequence
2581 standout_end(); // send "end reverse video" sequence
2585 static void Indicate_Error(void)
2587 #if ENABLE_FEATURE_VI_CRASHME
2589 return; // generate a random command
2592 write1(bell); // send out a bell character
2598 //----- Screen[] Routines --------------------------------------
2599 //----- Erase the Screen[] memory ------------------------------
2600 static void screen_erase(void)
2602 memset(screen, ' ', screensize); // clear new screen
2605 static int bufsum(char *buf, int count)
2608 char *e = buf + count;
2611 sum += (unsigned char) *buf++;
2615 //----- Draw the status line at bottom of the screen -------------
2616 static void show_status_line(void)
2618 int cnt = 0, cksum = 0;
2620 // either we already have an error or status message, or we
2622 if (!have_status_msg) {
2623 cnt = format_edit_status();
2624 cksum = bufsum(status_buffer, cnt);
2626 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2627 last_status_cksum= cksum; // remember if we have seen this line
2628 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2629 write1(status_buffer);
2631 if (have_status_msg) {
2632 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2634 have_status_msg = 0;
2637 have_status_msg = 0;
2639 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2644 //----- format the status buffer, the bottom line of screen ------
2645 // format status buffer, with STANDOUT mode
2646 static void psbs(const char *format, ...)
2650 va_start(args, format);
2651 strcpy(status_buffer, SOs); // Terminal standout mode on
2652 vsprintf(status_buffer + strlen(status_buffer), format, args);
2653 strcat(status_buffer, SOn); // Terminal standout mode off
2656 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2659 // format status buffer
2660 static void psb(const char *format, ...)
2664 va_start(args, format);
2665 vsprintf(status_buffer, format, args);
2668 have_status_msg = 1;
2671 static void ni(const char * s) // display messages
2675 print_literal(buf, s);
2676 psbs("\'%s\' is not implemented", buf);
2679 static int format_edit_status(void) // show file status on status line
2683 int cur, percent, ret, trunc_at;
2685 // file_modified is now a counter rather than a flag. this
2686 // helps reduce the amount of line counting we need to do.
2687 // (this will cause a mis-reporting of modified status
2688 // once every MAXINT editing operations.)
2690 // it would be nice to do a similar optimization here -- if
2691 // we haven't done a motion that could have changed which line
2692 // we're on, then we shouldn't have to do this count_lines()
2693 cur = count_lines(text, dot);
2695 // reduce counting -- the total lines can't have
2696 // changed if we haven't done any edits.
2697 if (file_modified != last_file_modified) {
2698 tot = cur + count_lines(dot, end - 1) - 1;
2699 last_file_modified = file_modified;
2702 // current line percent
2703 // ------------- ~~ ----------
2706 percent = (100 * cur) / tot;
2712 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2713 columns : STATUS_BUFFER_LEN-1;
2715 ret = snprintf(status_buffer, trunc_at+1,
2716 #if ENABLE_FEATURE_VI_READONLY
2717 "%c %s%s%s %d/%d %d%%",
2719 "%c %s%s %d/%d %d%%",
2721 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2722 (cfn != 0 ? cfn : "No file"),
2723 #if ENABLE_FEATURE_VI_READONLY
2724 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2726 (file_modified ? " [modified]" : ""),
2729 if (ret >= 0 && ret < trunc_at)
2730 return ret; /* it all fit */
2732 return trunc_at; /* had to truncate */
2735 //----- Force refresh of all Lines -----------------------------
2736 static void redraw(int full_screen)
2738 place_cursor(0, 0, FALSE); // put cursor in correct place
2739 clear_to_eos(); // tel terminal to erase display
2740 screen_erase(); // erase the internal screen buffer
2741 last_status_cksum = 0; // force status update
2742 refresh(full_screen); // this will redraw the entire display
2746 //----- Format a text[] line into a buffer ---------------------
2747 static void format_line(char *dest, char *src, int li)
2752 for (co = 0; co < MAX_SCR_COLS; co++) {
2753 c = ' '; // assume blank
2754 if (li > 0 && co == 0) {
2755 c = '~'; // not first line, assume Tilde
2757 // are there chars in text[] and have we gone past the end
2758 if (text < end && src < end) {
2763 if ((c & 0x80) && !Isprint(c)) {
2766 if ((unsigned char)(c) < ' ' || c == 0x7f) {
2770 for (; (co % tabstop) != (tabstop - 1); co++) {
2778 c += '@'; // make it visible
2781 // the co++ is done here so that the column will
2782 // not be overwritten when we blank-out the rest of line
2789 //----- Refresh the changed screen lines -----------------------
2790 // Copy the source line from text[] into the buffer and note
2791 // if the current screenline is different from the new buffer.
2792 // If they differ then that line needs redrawing on the terminal.
2794 static void refresh(int full_screen)
2796 static int old_offset;
2799 char buf[MAX_SCR_COLS];
2800 char *tp, *sp; // pointer into text[] and screen[]
2801 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2802 int last_li = -2; // last line that changed- for optimizing cursor movement
2805 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2806 get_terminal_width_height(0, &columns, &rows);
2807 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2808 tp = screenbegin; // index into text[] of top line
2810 // compare text[] to screen[] and mark screen[] lines that need updating
2811 for (li = 0; li < rows - 1; li++) {
2812 int cs, ce; // column start & end
2813 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2814 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2815 // format current text line into buf
2816 format_line(buf, tp, li);
2818 // skip to the end of the current text[] line
2819 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2821 // see if there are any changes between vitual screen and buf
2822 changed = FALSE; // assume no change
2825 sp = &screen[li * columns]; // start of screen line
2827 // force re-draw of every single column from 0 - columns-1
2830 // compare newly formatted buffer with virtual screen
2831 // look forward for first difference between buf and screen
2832 for (; cs <= ce; cs++) {
2833 if (buf[cs + offset] != sp[cs]) {
2834 changed = TRUE; // mark for redraw
2839 // look backward for last difference between buf and screen
2840 for ( ; ce >= cs; ce--) {
2841 if (buf[ce + offset] != sp[ce]) {
2842 changed = TRUE; // mark for redraw
2846 // now, cs is index of first diff, and ce is index of last diff
2848 // if horz offset has changed, force a redraw
2849 if (offset != old_offset) {
2854 // make a sanity check of columns indexes
2856 if (ce > columns-1) ce= columns-1;
2857 if (cs > ce) { cs= 0; ce= columns-1; }
2858 // is there a change between vitual screen and buf
2860 // copy changed part of buffer to virtual screen
2861 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2863 // move cursor to column of first change
2864 if (offset != old_offset) {
2865 // opti_cur_move is still too stupid
2866 // to handle offsets correctly
2867 place_cursor(li, cs, FALSE);
2869 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2870 // if this just the next line
2871 // try to optimize cursor movement
2872 // otherwise, use standard ESC sequence
2873 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2876 place_cursor(li, cs, FALSE); // use standard ESC sequence
2877 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2880 // write line out to terminal
2882 int nic = ce - cs + 1;
2883 char *out = sp + cs;
2890 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2896 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2897 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2900 place_cursor(crow, ccol, FALSE);
2903 if (offset != old_offset)
2904 old_offset = offset;
2907 //---------------------------------------------------------------------
2908 //----- the Ascii Chart -----------------------------------------------
2910 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2911 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2912 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2913 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2914 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2915 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2916 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2917 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2918 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2919 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2920 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2921 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2922 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2923 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2924 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2925 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2926 //---------------------------------------------------------------------
2928 //----- Execute a Vi Command -----------------------------------
2929 static void do_cmd(char c)
2932 char c1, *p, *q, buf[9], *save_dot;
2933 int cnt, i, j, dir, yf;
2935 c1 = c; // quiet the compiler
2936 cnt = yf = dir = 0; // quiet the compiler
2937 msg = p = q = save_dot = buf; // quiet the compiler
2938 memset(buf, '\0', 9); // clear buf
2942 /* if this is a cursor key, skip these checks */
2955 if (cmd_mode == 2) {
2956 // flip-flop Insert/Replace mode
2957 if (c == VI_K_INSERT)
2959 // we are 'R'eplacing the current *dot with new char
2961 // don't Replace past E-o-l
2962 cmd_mode = 1; // convert to insert
2964 if (1 <= c || Isprint(c)) {
2966 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2967 dot = char_insert(dot, c); // insert new char
2972 if (cmd_mode == 1) {
2973 // hitting "Insert" twice means "R" replace mode
2974 if (c == VI_K_INSERT) goto dc5;
2975 // insert the char c at "dot"
2976 if (1 <= c || Isprint(c)) {
2977 dot = char_insert(dot, c);
2992 #if ENABLE_FEATURE_VI_CRASHME
2993 case 0x14: // dc4 ctrl-T
2994 crashme = (crashme == 0) ? 1 : 0;
3025 //case 'u': // u- FIXME- there is no undo
3027 default: // unrecognised command
3036 end_cmd_q(); // stop adding to q
3037 case 0x00: // nul- ignore
3039 case 2: // ctrl-B scroll up full screen
3040 case VI_K_PAGEUP: // Cursor Key Page Up
3041 dot_scroll(rows - 2, -1);
3043 #if ENABLE_FEATURE_VI_USE_SIGNALS
3044 case 0x03: // ctrl-C interrupt
3045 longjmp(restart, 1);
3047 case 26: // ctrl-Z suspend
3048 suspend_sig(SIGTSTP);
3051 case 4: // ctrl-D scroll down half screen
3052 dot_scroll((rows - 2) / 2, 1);
3054 case 5: // ctrl-E scroll down one line
3057 case 6: // ctrl-F scroll down full screen
3058 case VI_K_PAGEDOWN: // Cursor Key Page Down
3059 dot_scroll(rows - 2, 1);
3061 case 7: // ctrl-G show current status
3062 last_status_cksum = 0; // force status update
3064 case 'h': // h- move left
3065 case VI_K_LEFT: // cursor key Left
3066 case 8: // ctrl-H- move left (This may be ERASE char)
3067 case 0x7f: // DEL- move left (This may be ERASE char)
3073 case 10: // Newline ^J
3074 case 'j': // j- goto next line, same col
3075 case VI_K_DOWN: // cursor key Down
3079 dot_next(); // go to next B-o-l
3080 dot = move_to_col(dot, ccol + offset); // try stay in same col
3082 case 12: // ctrl-L force redraw whole screen
3083 case 18: // ctrl-R force redraw
3084 place_cursor(0, 0, FALSE); // put cursor in correct place
3085 clear_to_eos(); // tel terminal to erase display
3087 screen_erase(); // erase the internal screen buffer
3088 last_status_cksum = 0; // force status update
3089 refresh(TRUE); // this will redraw the entire display
3091 case 13: // Carriage Return ^M
3092 case '+': // +- goto next line
3099 case 21: // ctrl-U scroll up half screen
3100 dot_scroll((rows - 2) / 2, -1);
3102 case 25: // ctrl-Y scroll up one line
3108 cmd_mode = 0; // stop insrting
3110 last_status_cksum = 0; // force status update
3112 case ' ': // move right
3113 case 'l': // move right
3114 case VI_K_RIGHT: // Cursor Key Right
3120 #if ENABLE_FEATURE_VI_YANKMARK
3121 case '"': // "- name a register to use for Delete/Yank
3122 c1 = get_one_char();
3130 case '\'': // '- goto a specific mark
3131 c1 = get_one_char();
3136 q = mark[(unsigned char) c1];
3137 if (text <= q && q < end) {
3139 dot_begin(); // go to B-o-l
3142 } else if (c1 == '\'') { // goto previous context
3143 dot = swap_context(dot); // swap current and previous context
3144 dot_begin(); // go to B-o-l
3150 case 'm': // m- Mark a line
3151 // this is really stupid. If there are any inserts or deletes
3152 // between text[0] and dot then this mark will not point to the
3153 // correct location! It could be off by many lines!
3154 // Well..., at least its quick and dirty.
3155 c1 = get_one_char();
3159 // remember the line
3160 mark[(int) c1] = dot;
3165 case 'P': // P- Put register before
3166 case 'p': // p- put register after
3169 psbs("Nothing in register %c", what_reg());
3172 // are we putting whole lines or strings
3173 if (strchr(p, '\n') != NULL) {
3175 dot_begin(); // putting lines- Put above
3178 // are we putting after very last line?
3179 if (end_line(dot) == (end - 1)) {
3180 dot = end; // force dot to end of text[]
3182 dot_next(); // next line, then put before
3187 dot_right(); // move to right, can move to NL
3189 dot = string_insert(dot, p); // insert the string
3190 end_cmd_q(); // stop adding to q
3192 case 'U': // U- Undo; replace current line with original version
3193 if (reg[Ureg] != 0) {
3194 p = begin_line(dot);
3196 p = text_hole_delete(p, q); // delete cur line
3197 p = string_insert(p, reg[Ureg]); // insert orig line
3202 #endif /* FEATURE_VI_YANKMARK */
3203 case '$': // $- goto end of line
3204 case VI_K_END: // Cursor Key End
3208 dot = end_line(dot);
3210 case '%': // %- find matching char of pair () [] {}
3211 for (q = dot; q < end && *q != '\n'; q++) {
3212 if (strchr("()[]{}", *q) != NULL) {
3213 // we found half of a pair
3214 p = find_pair(q, *q);
3226 case 'f': // f- forward to a user specified char
3227 last_forward_char = get_one_char(); // get the search char
3229 // dont separate these two commands. 'f' depends on ';'
3231 //**** fall thru to ... ';'
3232 case ';': // ;- look at rest of line for last forward char
3236 if (last_forward_char == 0)
3239 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3242 if (*q == last_forward_char)
3245 case '-': // -- goto prev line
3252 #if ENABLE_FEATURE_VI_DOT_CMD
3253 case '.': // .- repeat the last modifying command
3254 // Stuff the last_modifying_cmd back into stdin
3255 // and let it be re-executed.
3256 if (last_modifying_cmd != 0) {
3257 ioq = ioq_start = xstrdup(last_modifying_cmd);
3261 #if ENABLE_FEATURE_VI_SEARCH
3262 case '?': // /- search for a pattern
3263 case '/': // /- search for a pattern
3266 q = get_input_line(buf); // get input line- use "status line"
3268 goto dc3; // if no pat re-use old pat
3269 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3270 // there is a new pat
3271 free(last_search_pattern);
3272 last_search_pattern = xstrdup(q);
3273 goto dc3; // now find the pattern
3275 // user changed mind and erased the "/"- do nothing
3277 case 'N': // N- backward search for last pattern
3281 dir = BACK; // assume BACKWARD search
3283 if (last_search_pattern[0] == '?') {
3287 goto dc4; // now search for pattern
3289 case 'n': // n- repeat search for last pattern
3290 // search rest of text[] starting at next char
3291 // if search fails return orignal "p" not the "p+1" address
3296 if (last_search_pattern == 0) {
3297 msg = "No previous regular expression";
3300 if (last_search_pattern[0] == '/') {
3301 dir = FORWARD; // assume FORWARD search
3304 if (last_search_pattern[0] == '?') {
3309 q = char_search(p, last_search_pattern + 1, dir, FULL);
3311 dot = q; // good search, update "dot"
3315 // no pattern found between "dot" and "end"- continue at top
3320 q = char_search(p, last_search_pattern + 1, dir, FULL);
3321 if (q != NULL) { // found something
3322 dot = q; // found new pattern- goto it
3323 msg = "search hit BOTTOM, continuing at TOP";
3325 msg = "search hit TOP, continuing at BOTTOM";
3328 msg = "Pattern not found";
3334 case '{': // {- move backward paragraph
3335 q = char_search(dot, "\n\n", BACK, FULL);
3336 if (q != NULL) { // found blank line
3337 dot = next_line(q); // move to next blank line
3340 case '}': // }- move forward paragraph
3341 q = char_search(dot, "\n\n", FORWARD, FULL);
3342 if (q != NULL) { // found blank line
3343 dot = next_line(q); // move to next blank line
3346 #endif /* FEATURE_VI_SEARCH */
3347 case '0': // 0- goto begining of line
3357 if (c == '0' && cmdcnt < 1) {
3358 dot_begin(); // this was a standalone zero
3360 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3363 case ':': // :- the colon mode commands
3364 p = get_input_line(":"); // get input line- use "status line"
3365 #if ENABLE_FEATURE_VI_COLON
3366 colon(p); // execute the command
3369 p++; // move past the ':'
3373 if (strncasecmp(p, "quit", cnt) == 0
3374 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3376 if (file_modified && p[1] != '!') {
3377 psbs("No write since last change (:quit! overrides)");
3381 } else if (strncasecmp(p, "write", cnt) == 0
3382 || strncasecmp(p, "wq", cnt) == 0
3383 || strncasecmp(p, "wn", cnt) == 0
3384 || strncasecmp(p, "x", cnt) == 0
3386 cnt = file_write(cfn, text, end - 1);
3389 psbs("Write error: %s", strerror(errno));
3392 last_file_modified = -1;
3393 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3394 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3395 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3400 } else if (strncasecmp(p, "file", cnt) == 0) {
3401 last_status_cksum = 0; // force status update
3402 } else if (sscanf(p, "%d", &j) > 0) {
3403 dot = find_line(j); // go to line # j
3405 } else { // unrecognised cmd
3408 #endif /* !FEATURE_VI_COLON */
3410 case '<': // <- Left shift something
3411 case '>': // >- Right shift something
3412 cnt = count_lines(text, dot); // remember what line we are on
3413 c1 = get_one_char(); // get the type of thing to delete
3414 find_range(&p, &q, c1);
3415 yank_delete(p, q, 1, YANKONLY); // save copy before change
3418 i = count_lines(p, q); // # of lines we are shifting
3419 for ( ; i > 0; i--, p = next_line(p)) {
3421 // shift left- remove tab or 8 spaces
3423 // shrink buffer 1 char
3424 text_hole_delete(p, p);
3425 } else if (*p == ' ') {
3426 // we should be calculating columns, not just SPACE
3427 for (j = 0; *p == ' ' && j < tabstop; j++) {
3428 text_hole_delete(p, p);
3431 } else if (c == '>') {
3432 // shift right -- add tab or 8 spaces
3433 char_insert(p, '\t');
3436 dot = find_line(cnt); // what line were we on
3438 end_cmd_q(); // stop adding to q
3440 case 'A': // A- append at e-o-l
3441 dot_end(); // go to e-o-l
3442 //**** fall thru to ... 'a'
3443 case 'a': // a- append after current char
3448 case 'B': // B- back a blank-delimited Word
3449 case 'E': // E- end of a blank-delimited word
3450 case 'W': // W- forward a blank-delimited word
3457 if (c == 'W' || isspace(dot[dir])) {
3458 dot = skip_thing(dot, 1, dir, S_TO_WS);
3459 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3462 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3464 case 'C': // C- Change to e-o-l
3465 case 'D': // D- delete to e-o-l
3467 dot = dollar_line(dot); // move to before NL
3468 // copy text into a register and delete
3469 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3471 goto dc_i; // start inserting
3472 #if ENABLE_FEATURE_VI_DOT_CMD
3474 end_cmd_q(); // stop adding to q
3477 case 'G': // G- goto to a line number (default= E-O-F)
3478 dot = end - 1; // assume E-O-F
3480 dot = find_line(cmdcnt); // what line is #cmdcnt
3484 case 'H': // H- goto top line on screen
3486 if (cmdcnt > (rows - 1)) {
3487 cmdcnt = (rows - 1);
3494 case 'I': // I- insert before first non-blank
3497 //**** fall thru to ... 'i'
3498 case 'i': // i- insert before current char
3499 case VI_K_INSERT: // Cursor Key Insert
3501 cmd_mode = 1; // start insrting
3503 case 'J': // J- join current and next lines together
3507 dot_end(); // move to NL
3508 if (dot < end - 1) { // make sure not last char in text[]
3509 *dot++ = ' '; // replace NL with space
3511 while (isblnk(*dot)) { // delete leading WS
3515 end_cmd_q(); // stop adding to q
3517 case 'L': // L- goto bottom line on screen
3519 if (cmdcnt > (rows - 1)) {
3520 cmdcnt = (rows - 1);
3528 case 'M': // M- goto middle line on screen
3530 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3531 dot = next_line(dot);
3533 case 'O': // O- open a empty line above
3535 p = begin_line(dot);
3536 if (p[-1] == '\n') {
3538 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3540 dot = char_insert(dot, '\n');
3543 dot = char_insert(dot, '\n'); // i\n ESC
3548 case 'R': // R- continuous Replace char
3552 case 'X': // X- delete char before dot
3553 case 'x': // x- delete the current char
3554 case 's': // s- substitute the current char
3561 if (dot[dir] != '\n') {
3563 dot--; // delete prev char
3564 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3567 goto dc_i; // start insrting
3568 end_cmd_q(); // stop adding to q
3570 case 'Z': // Z- if modified, {write}; exit
3571 // ZZ means to save file (if necessary), then exit
3572 c1 = get_one_char();
3577 if (file_modified) {
3578 #if ENABLE_FEATURE_VI_READONLY
3579 if (vi_readonly || readonly) {
3580 psbs("\"%s\" File is read only", cfn);
3584 cnt = file_write(cfn, text, end - 1);
3587 psbs("Write error: %s", strerror(errno));
3588 } else if (cnt == (end - 1 - text + 1)) {
3595 case '^': // ^- move to first non-blank on line
3599 case 'b': // b- back a word
3600 case 'e': // e- end of word
3607 if ((dot + dir) < text || (dot + dir) > end - 1)
3610 if (isspace(*dot)) {
3611 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3613 if (isalnum(*dot) || *dot == '_') {
3614 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3615 } else if (ispunct(*dot)) {
3616 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3619 case 'c': // c- change something
3620 case 'd': // d- delete something
3621 #if ENABLE_FEATURE_VI_YANKMARK
3622 case 'y': // y- yank something
3623 case 'Y': // Y- Yank a line
3625 yf = YANKDEL; // assume either "c" or "d"
3626 #if ENABLE_FEATURE_VI_YANKMARK
3627 if (c == 'y' || c == 'Y')
3632 c1 = get_one_char(); // get the type of thing to delete
3633 find_range(&p, &q, c1);
3634 if (c1 == 27) { // ESC- user changed mind and wants out
3635 c = c1 = 27; // Escape- do nothing
3636 } else if (strchr("wW", c1)) {
3638 // don't include trailing WS as part of word
3639 while (isblnk(*q)) {
3640 if (q <= text || q[-1] == '\n')
3645 dot = yank_delete(p, q, 0, yf); // delete word
3646 } else if (strchr("^0bBeEft$", c1)) {
3647 // single line copy text into a register and delete
3648 dot = yank_delete(p, q, 0, yf); // delete word
3649 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3650 // multiple line copy text into a register and delete
3651 dot = yank_delete(p, q, 1, yf); // delete lines
3653 dot = char_insert(dot, '\n');
3654 // on the last line of file don't move to prev line
3655 if (dot != (end-1)) {
3658 } else if (c == 'd') {
3663 // could not recognize object
3664 c = c1 = 27; // error-
3668 // if CHANGING, not deleting, start inserting after the delete
3670 strcpy(buf, "Change");
3671 goto dc_i; // start inserting
3674 strcpy(buf, "Delete");
3676 #if ENABLE_FEATURE_VI_YANKMARK
3677 if (c == 'y' || c == 'Y') {
3678 strcpy(buf, "Yank");
3682 for (cnt = 0; p <= q; p++) {
3686 psb("%s %d lines (%d chars) using [%c]",
3687 buf, cnt, strlen(reg[YDreg]), what_reg());
3689 end_cmd_q(); // stop adding to q
3692 case 'k': // k- goto prev line, same col
3693 case VI_K_UP: // cursor key Up
3698 dot = move_to_col(dot, ccol + offset); // try stay in same col
3700 case 'r': // r- replace the current char with user input
3701 c1 = get_one_char(); // get the replacement char
3704 file_modified++; // has the file been modified
3706 end_cmd_q(); // stop adding to q
3708 case 't': // t- move to char prior to next x
3709 last_forward_char = get_one_char();
3711 if (*dot == last_forward_char)
3713 last_forward_char= 0;
3715 case 'w': // w- forward a word
3719 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3720 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3721 } else if (ispunct(*dot)) { // we are on PUNCT
3722 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3725 dot++; // move over word
3726 if (isspace(*dot)) {
3727 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3731 c1 = get_one_char(); // get the replacement char
3734 cnt = (rows - 2) / 2; // put dot at center
3736 cnt = rows - 2; // put dot at bottom
3737 screenbegin = begin_line(dot); // start dot at top
3738 dot_scroll(cnt, -1);
3740 case '|': // |- move to column "cmdcnt"
3741 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3743 case '~': // ~- flip the case of letters a-z -> A-Z
3747 if (islower(*dot)) {
3748 *dot = toupper(*dot);
3749 file_modified++; // has the file been modified
3750 } else if (isupper(*dot)) {
3751 *dot = tolower(*dot);
3752 file_modified++; // has the file been modified
3755 end_cmd_q(); // stop adding to q
3757 //----- The Cursor and Function Keys -----------------------------
3758 case VI_K_HOME: // Cursor Key Home
3761 // The Fn keys could point to do_macro which could translate them
3762 case VI_K_FUN1: // Function Key F1
3763 case VI_K_FUN2: // Function Key F2
3764 case VI_K_FUN3: // Function Key F3
3765 case VI_K_FUN4: // Function Key F4
3766 case VI_K_FUN5: // Function Key F5
3767 case VI_K_FUN6: // Function Key F6
3768 case VI_K_FUN7: // Function Key F7
3769 case VI_K_FUN8: // Function Key F8
3770 case VI_K_FUN9: // Function Key F9
3771 case VI_K_FUN10: // Function Key F10
3772 case VI_K_FUN11: // Function Key F11
3773 case VI_K_FUN12: // Function Key F12
3778 // if text[] just became empty, add back an empty line
3780 char_insert(text, '\n'); // start empty buf with dummy line
3783 // it is OK for dot to exactly equal to end, otherwise check dot validity
3785 dot = bound_dot(dot); // make sure "dot" is valid
3787 #if ENABLE_FEATURE_VI_YANKMARK
3788 check_context(c); // update the current context
3792 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3793 cnt = dot - begin_line(dot);
3794 // Try to stay off of the Newline
3795 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3799 #if ENABLE_FEATURE_VI_CRASHME
3800 static int totalcmds = 0;
3801 static int Mp = 85; // Movement command Probability
3802 static int Np = 90; // Non-movement command Probability
3803 static int Dp = 96; // Delete command Probability
3804 static int Ip = 97; // Insert command Probability
3805 static int Yp = 98; // Yank command Probability
3806 static int Pp = 99; // Put command Probability
3807 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3808 const char chars[20] = "\t012345 abcdABCD-=.$";
3809 const char *const words[20] = {
3810 "this", "is", "a", "test",
3811 "broadcast", "the", "emergency", "of",
3812 "system", "quick", "brown", "fox",
3813 "jumped", "over", "lazy", "dogs",
3814 "back", "January", "Febuary", "March"
3816 const char *const lines[20] = {
3817 "You should have received a copy of the GNU General Public License\n",
3818 "char c, cm, *cmd, *cmd1;\n",
3819 "generate a command by percentages\n",
3820 "Numbers may be typed as a prefix to some commands.\n",
3821 "Quit, discarding changes!\n",
3822 "Forced write, if permission originally not valid.\n",
3823 "In general, any ex or ed command (such as substitute or delete).\n",
3824 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3825 "Please get w/ me and I will go over it with you.\n",
3826 "The following is a list of scheduled, committed changes.\n",
3827 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3828 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3829 "Any question about transactions please contact Sterling Huxley.\n",
3830 "I will try to get back to you by Friday, December 31.\n",
3831 "This Change will be implemented on Friday.\n",
3832 "Let me know if you have problems accessing this;\n",
3833 "Sterling Huxley recently added you to the access list.\n",
3834 "Would you like to go to lunch?\n",
3835 "The last command will be automatically run.\n",
3836 "This is too much english for a computer geek.\n",
3838 char *multilines[20] = {
3839 "You should have received a copy of the GNU General Public License\n",
3840 "char c, cm, *cmd, *cmd1;\n",
3841 "generate a command by percentages\n",
3842 "Numbers may be typed as a prefix to some commands.\n",
3843 "Quit, discarding changes!\n",
3844 "Forced write, if permission originally not valid.\n",
3845 "In general, any ex or ed command (such as substitute or delete).\n",
3846 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3847 "Please get w/ me and I will go over it with you.\n",
3848 "The following is a list of scheduled, committed changes.\n",
3849 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3850 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3851 "Any question about transactions please contact Sterling Huxley.\n",
3852 "I will try to get back to you by Friday, December 31.\n",
3853 "This Change will be implemented on Friday.\n",
3854 "Let me know if you have problems accessing this;\n",
3855 "Sterling Huxley recently added you to the access list.\n",
3856 "Would you like to go to lunch?\n",
3857 "The last command will be automatically run.\n",
3858 "This is too much english for a computer geek.\n",
3861 // create a random command to execute
3862 static void crash_dummy()
3864 static int sleeptime; // how long to pause between commands
3865 char c, cm, *cmd, *cmd1;
3866 int i, cnt, thing, rbi, startrbi, percent;
3868 // "dot" movement commands
3869 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3871 // is there already a command running?
3872 if (readed_for_parse > 0)
3876 sleeptime = 0; // how long to pause between commands
3877 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3878 // generate a command by percentages
3879 percent = (int) lrand48() % 100; // get a number from 0-99
3880 if (percent < Mp) { // Movement commands
3881 // available commands
3884 } else if (percent < Np) { // non-movement commands
3885 cmd = "mz<>\'\""; // available commands
3887 } else if (percent < Dp) { // Delete commands
3888 cmd = "dx"; // available commands
3890 } else if (percent < Ip) { // Inset commands
3891 cmd = "iIaAsrJ"; // available commands
3893 } else if (percent < Yp) { // Yank commands
3894 cmd = "yY"; // available commands
3896 } else if (percent < Pp) { // Put commands
3897 cmd = "pP"; // available commands
3900 // We do not know how to handle this command, try again
3904 // randomly pick one of the available cmds from "cmd[]"
3905 i = (int) lrand48() % strlen(cmd);
3907 if (strchr(":\024", cm))
3908 goto cd0; // dont allow colon or ctrl-T commands
3909 readbuffer[rbi++] = cm; // put cmd into input buffer
3911 // now we have the command-
3912 // there are 1, 2, and multi char commands
3913 // find out which and generate the rest of command as necessary
3914 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3915 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3916 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3917 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3919 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3921 readbuffer[rbi++] = c; // add movement to input buffer
3923 if (strchr("iIaAsc", cm)) { // multi-char commands
3925 // change some thing
3926 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3928 readbuffer[rbi++] = c; // add movement to input buffer
3930 thing = (int) lrand48() % 4; // what thing to insert
3931 cnt = (int) lrand48() % 10; // how many to insert
3932 for (i = 0; i < cnt; i++) {
3933 if (thing == 0) { // insert chars
3934 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3935 } else if (thing == 1) { // insert words
3936 strcat(readbuffer, words[(int) lrand48() % 20]);
3937 strcat(readbuffer, " ");
3938 sleeptime = 0; // how fast to type
3939 } else if (thing == 2) { // insert lines
3940 strcat(readbuffer, lines[(int) lrand48() % 20]);
3941 sleeptime = 0; // how fast to type
3942 } else { // insert multi-lines
3943 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3944 sleeptime = 0; // how fast to type
3947 strcat(readbuffer, "\033");
3949 readed_for_parse = strlen(readbuffer);
3953 mysleep(sleeptime); // sleep 1/100 sec
3956 // test to see if there are any errors
3957 static void crash_test()
3959 static time_t oldtim;
3962 char d[2], msg[BUFSIZ];
3966 strcat(msg, "end<text ");
3968 if (end > textend) {
3969 strcat(msg, "end>textend ");
3972 strcat(msg, "dot<text ");
3975 strcat(msg, "dot>end ");
3977 if (screenbegin < text) {
3978 strcat(msg, "screenbegin<text ");
3980 if (screenbegin > end - 1) {
3981 strcat(msg, "screenbegin>end-1 ");
3986 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3987 totalcmds, last_input_char, msg, SOs, SOn);
3989 while (read(0, d, 1) > 0) {
3990 if (d[0] == '\n' || d[0] == '\r')
3995 tim = (time_t) time((time_t *) 0);
3996 if (tim >= (oldtim + 3)) {
3997 sprintf(status_buffer,
3998 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3999 totalcmds, M, N, I, D, Y, P, U, end - text + 1);