1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
26 #define ENABLE_FEATURE_VI_CRASHME 0
28 #if ENABLE_LOCALE_SUPPORT
29 #define Isprint(c) isprint((c))
31 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
34 #define MAX_SCR_COLS BUFSIZ
36 // Misc. non-Ascii keys that report an escape sequence
37 #define VI_K_UP 128 // cursor key Up
38 #define VI_K_DOWN 129 // cursor key Down
39 #define VI_K_RIGHT 130 // Cursor Key Right
40 #define VI_K_LEFT 131 // cursor key Left
41 #define VI_K_HOME 132 // Cursor Key Home
42 #define VI_K_END 133 // Cursor Key End
43 #define VI_K_INSERT 134 // Cursor Key Insert
44 #define VI_K_PAGEUP 135 // Cursor Key Page Up
45 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
46 #define VI_K_FUN1 137 // Function Key F1
47 #define VI_K_FUN2 138 // Function Key F2
48 #define VI_K_FUN3 139 // Function Key F3
49 #define VI_K_FUN4 140 // Function Key F4
50 #define VI_K_FUN5 141 // Function Key F5
51 #define VI_K_FUN6 142 // Function Key F6
52 #define VI_K_FUN7 143 // Function Key F7
53 #define VI_K_FUN8 144 // Function Key F8
54 #define VI_K_FUN9 145 // Function Key F9
55 #define VI_K_FUN10 146 // Function Key F10
56 #define VI_K_FUN11 147 // Function Key F11
57 #define VI_K_FUN12 148 // Function Key F12
59 /* vt102 typical ESC sequence */
60 /* terminal standout start/normal ESC sequence */
61 static const char SOs[] = "\033[7m";
62 static const char SOn[] = "\033[0m";
63 /* terminal bell sequence */
64 static const char bell[] = "\007";
65 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
66 static const char Ceol[] = "\033[0K";
67 static const char Ceos [] = "\033[0J";
68 /* Cursor motion arbitrary destination ESC sequence */
69 static const char CMrc[] = "\033[%d;%dH";
70 /* Cursor motion up and down ESC sequence */
71 static const char CMup[] = "\033[A";
72 static const char CMdown[] = "\n";
78 FORWARD = 1, // code depends on "1" for array index
79 BACK = -1, // code depends on "-1" for array index
80 LIMITED = 0, // how much of text[] in char_search
81 FULL = 1, // how much of text[] in char_search
83 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
84 S_TO_WS = 2, // used in skip_thing() for moving "dot"
85 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
86 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
87 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
90 typedef unsigned char Byte;
93 #define VI_AUTOINDENT 1
94 #define VI_SHOWMATCH 2
95 #define VI_IGNORECASE 4
96 #define VI_ERR_METHOD 8
97 #define autoindent (vi_setops & VI_AUTOINDENT)
98 #define showmatch (vi_setops & VI_SHOWMATCH )
99 #define ignorecase (vi_setops & VI_IGNORECASE)
100 /* indicate error with beep or flash */
101 #define err_method (vi_setops & VI_ERR_METHOD)
104 static int editing; // >0 while we are editing a file
105 static int cmd_mode; // 0=command 1=insert 2=replace
106 static int file_modified; // buffer contents changed
107 static int last_file_modified = -1;
108 static int fn_start; // index of first cmd line file name
109 static int save_argc; // how many file names on cmd line
110 static int cmdcnt; // repetition count
111 static fd_set rfds; // use select() for small sleeps
112 static struct timeval tv; // use select() for small sleeps
113 static int rows, columns; // the terminal screen is this size
114 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
115 static Byte *status_buffer; // mesages to the user
116 #define STATUS_BUFFER_LEN 200
117 static int have_status_msg; // is default edit status needed?
118 static int last_status_cksum; // hash of current status line
119 static Byte *cfn; // previous, current, and next file name
120 static Byte *text, *end; // pointers to the user data in memory
121 static Byte *screen; // pointer to the virtual screen buffer
122 static int screensize; // and its size
123 static Byte *screenbegin; // index into text[], of top line on the screen
124 static Byte *dot; // where all the action takes place
126 static struct termios term_orig, term_vi; // remember what the cooked mode was
127 static Byte erase_char; // the users erase character
128 static Byte last_input_char; // last char read from user
129 static Byte last_forward_char; // last char searched for with 'f'
131 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
132 static int last_row; // where the cursor was last moved to
134 #if ENABLE_FEATURE_VI_USE_SIGNALS
135 static jmp_buf restart; // catch_sig()
137 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
140 #if ENABLE_FEATURE_VI_DOT_CMD
141 static int adding2q; // are we currently adding user input to q
142 static Byte *last_modifying_cmd; // last modifying cmd for "."
143 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
145 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
146 static Byte *modifying_cmds; // cmds that modify text[]
148 #if ENABLE_FEATURE_VI_READONLY
149 static int vi_readonly, readonly;
151 #if ENABLE_FEATURE_VI_YANKMARK
152 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
153 static int YDreg, Ureg; // default delete register and orig line for "U"
154 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
155 static Byte *context_start, *context_end;
157 #if ENABLE_FEATURE_VI_SEARCH
158 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
162 static void edit_file(Byte *); // edit one file
163 static void do_cmd(Byte); // execute a command
164 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
165 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
166 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
167 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
168 static Byte *next_line(Byte *); // return pointer to next line B-o-l
169 static Byte *end_screen(void); // get pointer to last char on screen
170 static int count_lines(Byte *, Byte *); // count line from start to stop
171 static Byte *find_line(int); // find begining of line #li
172 static Byte *move_to_col(Byte *, int); // move "p" to column l
173 static int isblnk(Byte); // is the char a blank or tab
174 static void dot_left(void); // move dot left- dont leave line
175 static void dot_right(void); // move dot right- dont leave line
176 static void dot_begin(void); // move dot to B-o-l
177 static void dot_end(void); // move dot to E-o-l
178 static void dot_next(void); // move dot to next line B-o-l
179 static void dot_prev(void); // move dot to prev line B-o-l
180 static void dot_scroll(int, int); // move the screen up or down
181 static void dot_skip_over_ws(void); // move dot pat WS
182 static void dot_delete(void); // delete the char at 'dot'
183 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
184 static Byte *new_screen(int, int); // malloc virtual screen memory
185 static Byte *new_text(int); // malloc memory for text[] buffer
186 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
187 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
188 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
189 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
190 static Byte *skip_thing(Byte *, int, int, int); // skip some object
191 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
192 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
193 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
194 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
195 static void show_help(void); // display some help info
196 static void rawmode(void); // set "raw" mode on tty
197 static void cookmode(void); // return to "cooked" mode on tty
198 static int mysleep(int); // sleep for 'h' 1/100 seconds
199 static Byte readit(void); // read (maybe cursor) key from stdin
200 static Byte get_one_char(void); // read 1 char from stdin
201 static int file_size(const Byte *); // what is the byte size of "fn"
202 static int file_insert(Byte *, Byte *, int);
203 static int file_write(Byte *, Byte *, Byte *);
204 static void place_cursor(int, int, int);
205 static void screen_erase(void);
206 static void clear_to_eol(void);
207 static void clear_to_eos(void);
208 static void standout_start(void); // send "start reverse video" sequence
209 static void standout_end(void); // send "end reverse video" sequence
210 static void flash(int); // flash the terminal screen
211 static void show_status_line(void); // put a message on the bottom line
212 static void psb(const char *, ...); // Print Status Buf
213 static void psbs(const char *, ...); // Print Status Buf in standout mode
214 static void ni(Byte *); // display messages
215 static int format_edit_status(void); // format file status on status line
216 static void redraw(int); // force a full screen refresh
217 static void format_line(Byte*, Byte*, int);
218 static void refresh(int); // update the terminal from screen[]
220 static void Indicate_Error(void); // use flash or beep to indicate error
221 #define indicate_error(c) Indicate_Error()
222 static void Hit_Return(void);
224 #if ENABLE_FEATURE_VI_SEARCH
225 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
226 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
228 #if ENABLE_FEATURE_VI_COLON
229 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
230 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
231 static void colon(Byte *); // execute the "colon" mode cmds
233 #if ENABLE_FEATURE_VI_USE_SIGNALS
234 static void winch_sig(int); // catch window size changes
235 static void suspend_sig(int); // catch ctrl-Z
236 static void catch_sig(int); // catch ctrl-C and alarm time-outs
238 #if ENABLE_FEATURE_VI_DOT_CMD
239 static void start_new_cmd_q(Byte); // new queue for command
240 static void end_cmd_q(void); // stop saving input chars
242 #define end_cmd_q() ((void)0)
244 #if ENABLE_FEATURE_VI_SETOPTS
245 static void showmatching(Byte *); // show the matching pair () [] {}
247 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
248 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
250 #if ENABLE_FEATURE_VI_YANKMARK
251 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
252 static Byte what_reg(void); // what is letter of current YDreg
253 static void check_context(Byte); // remember context for '' command
255 #if ENABLE_FEATURE_VI_CRASHME
256 static void crash_dummy();
257 static void crash_test();
258 static int crashme = 0;
262 static void write1(const char *out)
267 int vi_main(int argc, char **argv)
270 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
272 #if ENABLE_FEATURE_VI_YANKMARK
275 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
278 #if ENABLE_FEATURE_VI_CRASHME
279 (void) srand((long) my_pid);
282 status_buffer = (Byte *)STATUS_BUFFER;
283 last_status_cksum = 0;
285 #if ENABLE_FEATURE_VI_READONLY
286 vi_readonly = readonly = FALSE;
287 if (strncmp(argv[0], "view", 4) == 0) {
292 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
293 #if ENABLE_FEATURE_VI_YANKMARK
294 for (i = 0; i < 28; i++) {
296 } // init the yank regs
298 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
299 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
302 // 1- process $HOME/.exrc file
303 // 2- process EXINIT variable from environment
304 // 3- process command line args
305 while ((c = getopt(argc, argv, "hCR")) != -1) {
307 #if ENABLE_FEATURE_VI_CRASHME
312 #if ENABLE_FEATURE_VI_READONLY
313 case 'R': // Read-only flag
318 //case 'r': // recover flag- ignore- we don't use tmp file
319 //case 'x': // encryption flag- ignore
320 //case 'c': // execute command first
321 //case 'h': // help -- just use default
328 // The argv array can be used by the ":next" and ":rewind" commands
330 fn_start = optind; // remember first file name for :next and :rew
333 //----- This is the main file handling loop --------------
334 if (optind >= argc) {
335 editing = 1; // 0= exit, 1= one file, 2= multiple files
338 for (; optind < argc; optind++) {
339 editing = 1; // 0=exit, 1=one file, 2+ =many files
341 cfn = (Byte *) xstrdup(argv[optind]);
345 //-----------------------------------------------------------
350 static void edit_file(Byte * fn)
355 #if ENABLE_FEATURE_VI_USE_SIGNALS
358 #if ENABLE_FEATURE_VI_YANKMARK
359 static Byte *cur_line;
366 if (ENABLE_FEATURE_VI_WIN_RESIZE)
367 get_terminal_width_height(0, &columns, &rows);
368 new_screen(rows, columns); // get memory for virtual screen
370 cnt = file_size(fn); // file size
371 size = 2 * cnt; // 200% of file size
372 new_text(size); // get a text[] buffer
373 screenbegin = dot = end = text;
375 ch= file_insert(fn, text, cnt);
378 (void) char_insert(text, '\n'); // start empty buf with dummy line
381 last_file_modified = -1;
382 #if ENABLE_FEATURE_VI_YANKMARK
383 YDreg = 26; // default Yank/Delete reg
384 Ureg = 27; // hold orig line for "U" cmd
385 for (cnt = 0; cnt < 28; cnt++) {
388 mark[26] = mark[27] = text; // init "previous context"
391 last_forward_char = last_input_char = '\0';
395 #if ENABLE_FEATURE_VI_USE_SIGNALS
397 signal(SIGWINCH, winch_sig);
398 signal(SIGTSTP, suspend_sig);
399 sig = setjmp(restart);
401 screenbegin = dot = text;
406 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
409 offset = 0; // no horizontal offset
411 #if ENABLE_FEATURE_VI_DOT_CMD
412 free(last_modifying_cmd);
414 ioq = ioq_start = last_modifying_cmd = 0;
417 redraw(FALSE); // dont force every col re-draw
420 //------This is the main Vi cmd handling loop -----------------------
421 while (editing > 0) {
422 #if ENABLE_FEATURE_VI_CRASHME
424 if ((end - text) > 1) {
425 crash_dummy(); // generate a random command
429 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
434 last_input_char = c = get_one_char(); // get a cmd from user
435 #if ENABLE_FEATURE_VI_YANKMARK
436 // save a copy of the current line- for the 'U" command
437 if (begin_line(dot) != cur_line) {
438 cur_line = begin_line(dot);
439 text_yank(begin_line(dot), end_line(dot), Ureg);
442 #if ENABLE_FEATURE_VI_DOT_CMD
443 // These are commands that change text[].
444 // Remember the input for the "." command
445 if (!adding2q && ioq_start == 0
446 && strchr((char *) modifying_cmds, c) != NULL) {
450 do_cmd(c); // execute the user command
452 // poll to see if there is input already waiting. if we are
453 // not able to display output fast enough to keep up, skip
454 // the display update until we catch up with input.
455 if (mysleep(0) == 0) {
456 // no input pending- so update output
460 #if ENABLE_FEATURE_VI_CRASHME
462 crash_test(); // test editor variables
465 //-------------------------------------------------------------------
467 place_cursor(rows, 0, FALSE); // go to bottom of screen
468 clear_to_eol(); // Erase to end of line
472 //----- The Colon commands -------------------------------------
473 #if ENABLE_FEATURE_VI_COLON
474 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
479 #if ENABLE_FEATURE_VI_YANKMARK
482 #if ENABLE_FEATURE_VI_SEARCH
483 Byte *pat, buf[BUFSIZ];
486 *addr = -1; // assume no addr
487 if (*p == '.') { // the current line
490 *addr = count_lines(text, q);
491 #if ENABLE_FEATURE_VI_YANKMARK
492 } else if (*p == '\'') { // is this a mark addr
496 if (c >= 'a' && c <= 'z') {
500 if (q != NULL) { // is mark valid
501 *addr = count_lines(text, q); // count lines
505 #if ENABLE_FEATURE_VI_SEARCH
506 } else if (*p == '/') { // a search pattern
514 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
517 q = char_search(dot, pat, FORWARD, FULL);
519 *addr = count_lines(text, q);
523 } else if (*p == '$') { // the last line in file
525 q = begin_line(end - 1);
526 *addr = count_lines(text, q);
527 } else if (isdigit(*p)) { // specific line number
528 sscanf((char *) p, "%d%n", addr, &st);
530 } else { // I don't reconise this
531 // unrecognised address- assume -1
537 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
539 //----- get the address' i.e., 1,3 'a,'b -----
540 // get FIRST addr, if present
542 p++; // skip over leading spaces
543 if (*p == '%') { // alias for 1,$
546 *e = count_lines(text, end-1);
549 p = get_one_address(p, b);
552 if (*p == ',') { // is there a address separator
556 // get SECOND addr, if present
557 p = get_one_address(p, e);
561 p++; // skip over trailing spaces
565 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
566 static void setops(const Byte *args, const char *opname, int flg_no,
567 const char *short_opname, int opt)
569 const char *a = (char *) args + flg_no;
570 int l = strlen(opname) - 1; /* opname have + ' ' */
572 if (strncasecmp(a, opname, l) == 0 ||
573 strncasecmp(a, short_opname, 2) == 0) {
582 static void colon(Byte * buf)
584 Byte c, *orig_buf, *buf1, *q, *r;
585 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
586 int i, l, li, ch, b, e;
587 int useforce = FALSE, forced = FALSE;
590 // :3154 // if (-e line 3154) goto it else stay put
591 // :4,33w! foo // write a portion of buffer to file "foo"
592 // :w // write all of buffer to current file
594 // :q! // quit- dont care about modified file
595 // :'a,'z!sort -u // filter block through sort
596 // :'f // goto mark "f"
597 // :'fl // list literal the mark "f" line
598 // :.r bar // read file "bar" into buffer before dot
599 // :/123/,/abc/d // delete lines from "123" line to "abc" line
600 // :/xyz/ // goto the "xyz" line
601 // :s/find/replace/ // substitute pattern "find" with "replace"
602 // :!<cmd> // run <cmd> then return
605 if (strlen((char *) buf) <= 0)
608 buf++; // move past the ':'
612 q = text; // assume 1,$ for the range
614 li = count_lines(text, end - 1);
615 fn = cfn; // default to current file
616 memset(cmd, '\0', BUFSIZ); // clear cmd[]
617 memset(args, '\0', BUFSIZ); // clear args[]
619 // look for optional address(es) :. :1 :1,9 :'q,'a :%
620 buf = get_address(buf, &b, &e);
622 // remember orig command line
625 // get the COMMAND into cmd[]
627 while (*buf != '\0') {
635 strcpy((char *) args, (char *) buf);
636 buf1 = (Byte*)last_char_is((char *)cmd, '!');
639 *buf1 = '\0'; // get rid of !
642 // if there is only one addr, then the addr
643 // is the line number of the single line the
644 // user wants. So, reset the end
645 // pointer to point at end of the "b" line
646 q = find_line(b); // what line is #b
651 // we were given two addrs. change the
652 // end pointer to the addr given by user.
653 r = find_line(e); // what line is #e
657 // ------------ now look for the command ------------
658 i = strlen((char *) cmd);
659 if (i == 0) { // :123CR goto line #123
661 dot = find_line(b); // what line is #b
665 #if ENABLE_FEATURE_ALLOW_EXEC
666 else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
667 // :!ls run the <cmd>
668 (void) alarm(0); // wait for input- no alarms
669 place_cursor(rows - 1, 0, FALSE); // go to Status line
670 clear_to_eol(); // clear the line
672 system((char*)(orig_buf+1)); // run the cmd
674 Hit_Return(); // let user see results
675 (void) alarm(3); // done waiting for input
678 else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
679 if (b < 0) { // no addr given- use defaults
680 b = e = count_lines(text, dot);
683 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
684 if (b < 0) { // no addr given- use defaults
685 q = begin_line(dot); // assume .,. for the range
688 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
690 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
693 // don't edit, if the current file has been modified
694 if (file_modified && ! useforce) {
695 psbs("No write since last change (:edit! overrides)");
698 if (strlen((char*)args) > 0) {
699 // the user supplied a file name
701 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
702 // no user supplied name- use the current filename
706 // no user file name, no current name- punt
707 psbs("No current filename");
711 // see if file exists- if not, its just a new file request
712 if ((sr=stat((char*)fn, &st_buf)) < 0) {
713 // This is just a request for a new file creation.
714 // The file_insert below will fail but we get
715 // an empty buffer with a file name. Then the "write"
716 // command can do the create.
718 if ((st_buf.st_mode & (S_IFREG)) == 0) {
719 // This is not a regular file
720 psbs("\"%s\" is not a regular file", fn);
723 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
724 // dont have any read permissions
725 psbs("\"%s\" is not readable", fn);
730 // There is a read-able regular file
731 // make this the current file
732 q = (Byte *) xstrdup((char *) fn); // save the cfn
733 free(cfn); // free the old name
734 cfn = q; // remember new cfn
737 // delete all the contents of text[]
738 new_text(2 * file_size(fn));
739 screenbegin = dot = end = text;
742 ch = file_insert(fn, text, file_size(fn));
745 // start empty buf with dummy line
746 (void) char_insert(text, '\n');
750 last_file_modified = -1;
751 #if ENABLE_FEATURE_VI_YANKMARK
752 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
753 free(reg[Ureg]); // free orig line reg- for 'U'
756 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
757 free(reg[YDreg]); // free default yank/delete register
760 for (li = 0; li < 28; li++) {
764 // how many lines in text[]?
765 li = count_lines(text, end - 1);
767 #if ENABLE_FEATURE_VI_READONLY
771 (sr < 0 ? " [New file]" : ""),
772 #if ENABLE_FEATURE_VI_READONLY
773 ((vi_readonly || readonly) ? " [Read only]" : ""),
776 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
777 if (b != -1 || e != -1) {
778 ni((Byte *) "No address allowed on this command");
781 if (strlen((char *) args) > 0) {
782 // user wants a new filename
784 cfn = (Byte *) xstrdup((char *) args);
786 // user wants file status info
787 last_status_cksum = 0; // force status update
789 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
790 // print out values of all features
791 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
792 clear_to_eol(); // clear the line
797 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
798 if (b < 0) { // no addr given- use defaults
799 q = begin_line(dot); // assume .,. for the range
802 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
803 clear_to_eol(); // clear the line
805 for (; q <= r; q++) {
809 c_is_no_print = c > 127 && !Isprint(c);
816 } else if (c < ' ' || c == 127) {
827 #if ENABLE_FEATURE_VI_SET
831 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
832 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
834 // force end of argv list
841 // don't exit if the file been modified
843 psbs("No write since last change (:%s! overrides)",
844 (*cmd == 'q' ? "quit" : "next"));
847 // are there other file to edit
848 if (*cmd == 'q' && optind < save_argc - 1) {
849 psbs("%d more file to edit", (save_argc - optind - 1));
852 if (*cmd == 'n' && optind >= save_argc - 1) {
853 psbs("No more files to edit");
857 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
859 if (strlen((char *) fn) <= 0) {
860 psbs("No filename given");
863 if (b < 0) { // no addr given- use defaults
864 q = begin_line(dot); // assume "dot"
866 // read after current line- unless user said ":0r foo"
869 #if ENABLE_FEATURE_VI_READONLY
870 l= readonly; // remember current files' status
872 ch = file_insert(fn, q, file_size(fn));
873 #if ENABLE_FEATURE_VI_READONLY
877 goto vc1; // nothing was inserted
878 // how many lines in text[]?
879 li = count_lines(q, q + ch - 1);
881 #if ENABLE_FEATURE_VI_READONLY
885 #if ENABLE_FEATURE_VI_READONLY
886 ((vi_readonly || readonly) ? " [Read only]" : ""),
890 // if the insert is before "dot" then we need to update
895 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
896 if (file_modified && ! useforce) {
897 psbs("No write since last change (:rewind! overrides)");
899 // reset the filenames to edit
900 optind = fn_start - 1;
903 #if ENABLE_FEATURE_VI_SET
904 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
905 i = 0; // offset into args
906 if (strlen((char *) args) == 0) {
907 // print out values of all options
908 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
909 clear_to_eol(); // clear the line
910 printf("----------------------------------------\r\n");
911 #if ENABLE_FEATURE_VI_SETOPTS
914 printf("autoindent ");
920 printf("ignorecase ");
923 printf("showmatch ");
924 printf("tabstop=%d ", tabstop);
929 if (strncasecmp((char *) args, "no", 2) == 0)
930 i = 2; // ":set noautoindent"
931 #if ENABLE_FEATURE_VI_SETOPTS
932 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
933 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
934 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
935 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
936 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
937 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
938 if (ch > 0 && ch < columns - 1)
941 #endif /* FEATURE_VI_SETOPTS */
942 #endif /* FEATURE_VI_SET */
943 #if ENABLE_FEATURE_VI_SEARCH
944 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
948 // F points to the "find" pattern
949 // R points to the "replace" pattern
950 // replace the cmd line delimiters "/" with NULLs
951 gflag = 0; // global replace flag
952 c = orig_buf[1]; // what is the delimiter
953 F = orig_buf + 2; // start of "find"
954 R = (Byte *) strchr((char *) F, c); // middle delimiter
955 if (!R) goto colon_s_fail;
956 *R++ = '\0'; // terminate "find"
957 buf1 = (Byte *) strchr((char *) R, c);
958 if (!buf1) goto colon_s_fail;
959 *buf1++ = '\0'; // terminate "replace"
960 if (*buf1 == 'g') { // :s/foo/bar/g
962 gflag++; // turn on gflag
965 if (b < 0) { // maybe :s/foo/bar/
966 q = begin_line(dot); // start with cur line
967 b = count_lines(text, q); // cur line number
970 e = b; // maybe :.s/foo/bar/
971 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
972 ls = q; // orig line start
974 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
976 // we found the "find" pattern- delete it
977 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
978 // inset the "replace" patern
979 (void) string_insert(buf1, R); // insert the string
980 // check for "global" :s/foo/bar/g
982 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
983 q = buf1 + strlen((char *) R);
984 goto vc4; // don't let q move past cur line
990 #endif /* FEATURE_VI_SEARCH */
991 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
992 psb("%s", BB_VER " " BB_BT);
993 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
994 || strncasecmp((char *) cmd, "wq", i) == 0
995 || strncasecmp((char *) cmd, "wn", i) == 0
996 || strncasecmp((char *) cmd, "x", i) == 0) {
997 // is there a file name to write to?
998 if (strlen((char *) args) > 0) {
1001 #if ENABLE_FEATURE_VI_READONLY
1002 if ((vi_readonly || readonly) && ! useforce) {
1003 psbs("\"%s\" File is read only", fn);
1007 // how many lines in text[]?
1008 li = count_lines(q, r);
1010 // see if file exists- if not, its just a new file request
1012 // if "fn" is not write-able, chmod u+w
1013 // sprintf(syscmd, "chmod u+w %s", fn);
1017 l = file_write(fn, q, r);
1018 if (useforce && forced) {
1020 // sprintf(syscmd, "chmod u-w %s", fn);
1026 psbs("Write error: %s", strerror(errno));
1028 psb("\"%s\" %dL, %dC", fn, li, l);
1029 if (q == text && r == end - 1 && l == ch) {
1031 last_file_modified = -1;
1033 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1034 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1039 #if ENABLE_FEATURE_VI_READONLY
1042 #if ENABLE_FEATURE_VI_YANKMARK
1043 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1044 if (b < 0) { // no addr given- use defaults
1045 q = begin_line(dot); // assume .,. for the range
1048 text_yank(q, r, YDreg);
1049 li = count_lines(q, r);
1050 psb("Yank %d lines (%d chars) into [%c]",
1051 li, strlen((char *) reg[YDreg]), what_reg());
1058 dot = bound_dot(dot); // make sure "dot" is valid
1060 #if ENABLE_FEATURE_VI_SEARCH
1062 psb(":s expression missing delimiters");
1066 #endif /* FEATURE_VI_COLON */
1068 static void Hit_Return(void)
1072 standout_start(); // start reverse video
1073 write1("[Hit return to continue]");
1074 standout_end(); // end reverse video
1075 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1077 redraw(TRUE); // force redraw all
1080 //----- Synchronize the cursor to Dot --------------------------
1081 static void sync_cursor(Byte * d, int *row, int *col)
1083 Byte *beg_cur; // begin and end of "d" line
1084 Byte *end_scr; // begin and end of screen
1088 beg_cur = begin_line(d); // first char of cur line
1090 end_scr = end_screen(); // last char of screen
1092 if (beg_cur < screenbegin) {
1093 // "d" is before top line on screen
1094 // how many lines do we have to move
1095 cnt = count_lines(beg_cur, screenbegin);
1097 screenbegin = beg_cur;
1098 if (cnt > (rows - 1) / 2) {
1099 // we moved too many lines. put "dot" in middle of screen
1100 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1101 screenbegin = prev_line(screenbegin);
1104 } else if (beg_cur > end_scr) {
1105 // "d" is after bottom line on screen
1106 // how many lines do we have to move
1107 cnt = count_lines(end_scr, beg_cur);
1108 if (cnt > (rows - 1) / 2)
1109 goto sc1; // too many lines
1110 for (ro = 0; ro < cnt - 1; ro++) {
1111 // move screen begin the same amount
1112 screenbegin = next_line(screenbegin);
1113 // now, move the end of screen
1114 end_scr = next_line(end_scr);
1115 end_scr = end_line(end_scr);
1118 // "d" is on screen- find out which row
1120 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1126 // find out what col "d" is on
1128 do { // drive "co" to correct column
1129 if (*tp == '\n' || *tp == '\0')
1133 co += ((tabstop - 1) - (co % tabstop));
1134 } else if (*tp < ' ' || *tp == 127) {
1135 co++; // display as ^X, use 2 columns
1137 } while (tp++ < d && ++co);
1139 // "co" is the column where "dot" is.
1140 // The screen has "columns" columns.
1141 // The currently displayed columns are 0+offset -- columns+ofset
1142 // |-------------------------------------------------------------|
1144 // offset | |------- columns ----------------|
1146 // If "co" is already in this range then we do not have to adjust offset
1147 // but, we do have to subtract the "offset" bias from "co".
1148 // If "co" is outside this range then we have to change "offset".
1149 // If the first char of a line is a tab the cursor will try to stay
1150 // in column 7, but we have to set offset to 0.
1152 if (co < 0 + offset) {
1155 if (co >= columns + offset) {
1156 offset = co - columns + 1;
1158 // if the first char of the line is a tab, and "dot" is sitting on it
1159 // force offset to 0.
1160 if (d == beg_cur && *d == '\t') {
1169 //----- Text Movement Routines ---------------------------------
1170 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1172 while (p > text && p[-1] != '\n')
1173 p--; // go to cur line B-o-l
1177 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1179 while (p < end - 1 && *p != '\n')
1180 p++; // go to cur line E-o-l
1184 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1186 while (p < end - 1 && *p != '\n')
1187 p++; // go to cur line E-o-l
1188 // Try to stay off of the Newline
1189 if (*p == '\n' && (p - begin_line(p)) > 0)
1194 static Byte *prev_line(Byte * p) // return pointer first char prev line
1196 p = begin_line(p); // goto begining of cur line
1197 if (p[-1] == '\n' && p > text)
1198 p--; // step to prev line
1199 p = begin_line(p); // goto begining of prev line
1203 static Byte *next_line(Byte * p) // return pointer first char next line
1206 if (*p == '\n' && p < end - 1)
1207 p++; // step to next line
1211 //----- Text Information Routines ------------------------------
1212 static Byte *end_screen(void)
1217 // find new bottom line
1219 for (cnt = 0; cnt < rows - 2; cnt++)
1225 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1230 if (stop < start) { // start and stop are backwards- reverse them
1236 stop = end_line(stop); // get to end of this line
1237 for (q = start; q <= stop && q <= end - 1; q++) {
1244 static Byte *find_line(int li) // find begining of line #li
1248 for (q = text; li > 1; li--) {
1254 //----- Dot Movement Routines ----------------------------------
1255 static void dot_left(void)
1257 if (dot > text && dot[-1] != '\n')
1261 static void dot_right(void)
1263 if (dot < end - 1 && *dot != '\n')
1267 static void dot_begin(void)
1269 dot = begin_line(dot); // return pointer to first char cur line
1272 static void dot_end(void)
1274 dot = end_line(dot); // return pointer to last char cur line
1277 static Byte *move_to_col(Byte * p, int l)
1284 if (*p == '\n' || *p == '\0')
1288 co += ((tabstop - 1) - (co % tabstop));
1289 } else if (*p < ' ' || *p == 127) {
1290 co++; // display as ^X, use 2 columns
1292 } while (++co <= l && p++ < end);
1296 static void dot_next(void)
1298 dot = next_line(dot);
1301 static void dot_prev(void)
1303 dot = prev_line(dot);
1306 static void dot_scroll(int cnt, int dir)
1310 for (; cnt > 0; cnt--) {
1313 // ctrl-Y scroll up one line
1314 screenbegin = prev_line(screenbegin);
1317 // ctrl-E scroll down one line
1318 screenbegin = next_line(screenbegin);
1321 // make sure "dot" stays on the screen so we dont scroll off
1322 if (dot < screenbegin)
1324 q = end_screen(); // find new bottom line
1326 dot = begin_line(q); // is dot is below bottom line?
1330 static void dot_skip_over_ws(void)
1333 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1337 static void dot_delete(void) // delete the char at 'dot'
1339 (void) text_hole_delete(dot, dot);
1342 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1344 if (p >= end && end > text) {
1346 indicate_error('1');
1350 indicate_error('2');
1355 //----- Helper Utility Routines --------------------------------
1357 //----------------------------------------------------------------
1358 //----- Char Routines --------------------------------------------
1359 /* Chars that are part of a word-
1360 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1361 * Chars that are Not part of a word (stoppers)
1362 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1363 * Chars that are WhiteSpace
1364 * TAB NEWLINE VT FF RETURN SPACE
1365 * DO NOT COUNT NEWLINE AS WHITESPACE
1368 static Byte *new_screen(int ro, int co)
1373 screensize = ro * co + 8;
1374 screen = xmalloc(screensize);
1375 // initialize the new screen. assume this will be a empty file.
1377 // non-existent text[] lines start with a tilde (~).
1378 for (li = 1; li < ro - 1; li++) {
1379 screen[(li * co) + 0] = '~';
1384 static Byte *new_text(int size)
1387 size = 10240; // have a minimum size for new files
1389 text = xmalloc(size + 8);
1390 memset(text, '\0', size); // clear new text[]
1391 //text += 4; // leave some room for "oops"
1395 #if ENABLE_FEATURE_VI_SEARCH
1396 static int mycmp(Byte * s1, Byte * s2, int len)
1400 i = strncmp((char *) s1, (char *) s2, len);
1401 #if ENABLE_FEATURE_VI_SETOPTS
1403 i = strncasecmp((char *) s1, (char *) s2, len);
1409 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1411 #ifndef REGEX_SEARCH
1415 len = strlen((char *) pat);
1416 if (dir == FORWARD) {
1417 stop = end - 1; // assume range is p - end-1
1418 if (range == LIMITED)
1419 stop = next_line(p); // range is to next line
1420 for (start = p; start < stop; start++) {
1421 if (mycmp(start, pat, len) == 0) {
1425 } else if (dir == BACK) {
1426 stop = text; // assume range is text - p
1427 if (range == LIMITED)
1428 stop = prev_line(p); // range is to prev line
1429 for (start = p - len; start >= stop; start--) {
1430 if (mycmp(start, pat, len) == 0) {
1435 // pattern not found
1437 #else /*REGEX_SEARCH */
1439 struct re_pattern_buffer preg;
1443 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1449 // assume a LIMITED forward search
1457 // count the number of chars to search over, forward or backward
1461 // RANGE could be negative if we are searching backwards
1464 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1466 // The pattern was not compiled
1467 psbs("bad search pattern: \"%s\": %s", pat, q);
1468 i = 0; // return p if pattern not compiled
1478 // search for the compiled pattern, preg, in p[]
1479 // range < 0- search backward
1480 // range > 0- search forward
1482 // re_search() < 0 not found or error
1483 // re_search() > 0 index of found pattern
1484 // struct pattern char int int int struct reg
1485 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1486 i = re_search(&preg, q, size, 0, range, 0);
1489 i = 0; // return NULL if pattern not found
1492 if (dir == FORWARD) {
1498 #endif /* REGEX_SEARCH */
1500 #endif /* FEATURE_VI_SEARCH */
1502 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1504 if (c == 22) { // Is this an ctrl-V?
1505 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1506 p--; // backup onto ^
1507 refresh(FALSE); // show the ^
1511 file_modified++; // has the file been modified
1512 } else if (c == 27) { // Is this an ESC?
1515 end_cmd_q(); // stop adding to q
1516 last_status_cksum = 0; // force status update
1517 if ((p[-1] != '\n') && (dot>text)) {
1520 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1522 if ((p[-1] != '\n') && (dot>text)) {
1524 p = text_hole_delete(p, p); // shrink buffer 1 char
1527 // insert a char into text[]
1528 Byte *sp; // "save p"
1531 c = '\n'; // translate \r to \n
1532 sp = p; // remember addr of insert
1533 p = stupid_insert(p, c); // insert the char
1534 #if ENABLE_FEATURE_VI_SETOPTS
1535 if (showmatch && strchr(")]}", *sp) != NULL) {
1538 if (autoindent && c == '\n') { // auto indent the new line
1541 q = prev_line(p); // use prev line as templet
1542 for (; isblnk(*q); q++) {
1543 p = stupid_insert(p, *q); // insert the char
1551 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1553 p = text_hole_make(p, 1);
1556 file_modified++; // has the file been modified
1562 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1564 Byte *save_dot, *p, *q;
1570 if (strchr("cdy><", c)) {
1571 // these cmds operate on whole lines
1572 p = q = begin_line(p);
1573 for (cnt = 1; cnt < cmdcnt; cnt++) {
1577 } else if (strchr("^%$0bBeEft", c)) {
1578 // These cmds operate on char positions
1579 do_cmd(c); // execute movement cmd
1581 } else if (strchr("wW", c)) {
1582 do_cmd(c); // execute movement cmd
1583 // if we are at the next word's first char
1584 // step back one char
1585 // but check the possibilities when it is true
1586 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1587 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1588 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1589 dot--; // move back off of next word
1590 if (dot > text && *dot == '\n')
1591 dot--; // stay off NL
1593 } else if (strchr("H-k{", c)) {
1594 // these operate on multi-lines backwards
1595 q = end_line(dot); // find NL
1596 do_cmd(c); // execute movement cmd
1599 } else if (strchr("L+j}\r\n", c)) {
1600 // these operate on multi-lines forwards
1601 p = begin_line(dot);
1602 do_cmd(c); // execute movement cmd
1603 dot_end(); // find NL
1606 c = 27; // error- return an ESC char
1619 static int st_test(Byte * p, int type, int dir, Byte * tested)
1629 if (type == S_BEFORE_WS) {
1631 test = ((!isspace(c)) || c == '\n');
1633 if (type == S_TO_WS) {
1635 test = ((!isspace(c)) || c == '\n');
1637 if (type == S_OVER_WS) {
1639 test = ((isspace(c)));
1641 if (type == S_END_PUNCT) {
1643 test = ((ispunct(c)));
1645 if (type == S_END_ALNUM) {
1647 test = ((isalnum(c)) || c == '_');
1653 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1657 while (st_test(p, type, dir, &c)) {
1658 // make sure we limit search to correct number of lines
1659 if (c == '\n' && --linecnt < 1)
1661 if (dir >= 0 && p >= end - 1)
1663 if (dir < 0 && p <= text)
1665 p += dir; // move to next char
1670 // find matching char of pair () [] {}
1671 static Byte *find_pair(Byte * p, Byte c)
1678 dir = 1; // assume forward
1702 for (q = p + dir; text <= q && q < end; q += dir) {
1703 // look for match, count levels of pairs (( ))
1705 level++; // increase pair levels
1707 level--; // reduce pair level
1709 break; // found matching pair
1712 q = NULL; // indicate no match
1716 #if ENABLE_FEATURE_VI_SETOPTS
1717 // show the matching char of a pair, () [] {}
1718 static void showmatching(Byte * p)
1722 // we found half of a pair
1723 q = find_pair(p, *p); // get loc of matching char
1725 indicate_error('3'); // no matching char
1727 // "q" now points to matching pair
1728 save_dot = dot; // remember where we are
1729 dot = q; // go to new loc
1730 refresh(FALSE); // let the user see it
1731 (void) mysleep(40); // give user some time
1732 dot = save_dot; // go back to old loc
1736 #endif /* FEATURE_VI_SETOPTS */
1738 // open a hole in text[]
1739 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1748 cnt = end - src; // the rest of buffer
1749 if (memmove(dest, src, cnt) != dest) {
1750 psbs("can't create room for new characters");
1752 memset(p, ' ', size); // clear new hole
1753 end = end + size; // adjust the new END
1754 file_modified++; // has the file been modified
1759 // close a hole in text[]
1760 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1765 // move forwards, from beginning
1769 if (q < p) { // they are backward- swap them
1773 hole_size = q - p + 1;
1775 if (src < text || src > end)
1777 if (dest < text || dest >= end)
1780 goto thd_atend; // just delete the end of the buffer
1781 if (memmove(dest, src, cnt) != dest) {
1782 psbs("can't delete the character");
1785 end = end - hole_size; // adjust the new END
1787 dest = end - 1; // make sure dest in below end-1
1789 dest = end = text; // keep pointers valid
1790 file_modified++; // has the file been modified
1795 // copy text into register, then delete text.
1796 // if dist <= 0, do not include, or go past, a NewLine
1798 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1802 // make sure start <= stop
1804 // they are backwards, reverse them
1810 // we cannot cross NL boundaries
1814 // dont go past a NewLine
1815 for (; p + 1 <= stop; p++) {
1817 stop = p; // "stop" just before NewLine
1823 #if ENABLE_FEATURE_VI_YANKMARK
1824 text_yank(start, stop, YDreg);
1826 if (yf == YANKDEL) {
1827 p = text_hole_delete(start, stop);
1832 static void show_help(void)
1834 puts("These features are available:"
1835 #if ENABLE_FEATURE_VI_SEARCH
1836 "\n\tPattern searches with / and ?"
1838 #if ENABLE_FEATURE_VI_DOT_CMD
1839 "\n\tLast command repeat with \'.\'"
1841 #if ENABLE_FEATURE_VI_YANKMARK
1842 "\n\tLine marking with 'x"
1843 "\n\tNamed buffers with \"x"
1845 #if ENABLE_FEATURE_VI_READONLY
1846 "\n\tReadonly if vi is called as \"view\""
1847 "\n\tReadonly with -R command line arg"
1849 #if ENABLE_FEATURE_VI_SET
1850 "\n\tSome colon mode commands with \':\'"
1852 #if ENABLE_FEATURE_VI_SETOPTS
1853 "\n\tSettable options with \":set\""
1855 #if ENABLE_FEATURE_VI_USE_SIGNALS
1856 "\n\tSignal catching- ^C"
1857 "\n\tJob suspend and resume with ^Z"
1859 #if ENABLE_FEATURE_VI_WIN_RESIZE
1860 "\n\tAdapt to window re-sizes"
1865 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1870 strcpy((char *) buf, ""); // init buf
1871 if (strlen((char *) s) <= 0)
1872 s = (Byte *) "(NULL)";
1873 for (; *s > '\0'; s++) {
1877 c_is_no_print = c > 127 && !Isprint(c);
1878 if (c_is_no_print) {
1879 strcat((char *) buf, SOn);
1882 if (c < ' ' || c == 127) {
1883 strcat((char *) buf, "^");
1890 strcat((char *) buf, (char *) b);
1892 strcat((char *) buf, SOs);
1894 strcat((char *) buf, "$");
1899 #if ENABLE_FEATURE_VI_DOT_CMD
1900 static void start_new_cmd_q(Byte c)
1903 free(last_modifying_cmd);
1904 // get buffer for new cmd
1905 last_modifying_cmd = xmalloc(BUFSIZ);
1906 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1907 // if there is a current cmd count put it in the buffer first
1909 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1910 else // just save char c onto queue
1911 last_modifying_cmd[0] = c;
1915 static void end_cmd_q(void)
1917 #if ENABLE_FEATURE_VI_YANKMARK
1918 YDreg = 26; // go back to default Yank/Delete reg
1923 #endif /* FEATURE_VI_DOT_CMD */
1925 #if ENABLE_FEATURE_VI_YANKMARK \
1926 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1927 || ENABLE_FEATURE_VI_CRASHME
1928 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1932 i = strlen((char *) s);
1933 p = text_hole_make(p, i);
1934 strncpy((char *) p, (char *) s, i);
1935 for (cnt = 0; *s != '\0'; s++) {
1939 #if ENABLE_FEATURE_VI_YANKMARK
1940 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1946 #if ENABLE_FEATURE_VI_YANKMARK
1947 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1952 if (q < p) { // they are backwards- reverse them
1959 free(t); // if already a yank register, free it
1960 t = xmalloc(cnt + 1); // get a new register
1961 memset(t, '\0', cnt + 1); // clear new text[]
1962 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1967 static Byte what_reg(void)
1971 c = 'D'; // default to D-reg
1972 if (0 <= YDreg && YDreg <= 25)
1973 c = 'a' + (Byte) YDreg;
1981 static void check_context(Byte cmd)
1983 // A context is defined to be "modifying text"
1984 // Any modifying command establishes a new context.
1986 if (dot < context_start || dot > context_end) {
1987 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1988 // we are trying to modify text[]- make this the current context
1989 mark[27] = mark[26]; // move cur to prev
1990 mark[26] = dot; // move local to cur
1991 context_start = prev_line(prev_line(dot));
1992 context_end = next_line(next_line(dot));
1993 //loiter= start_loiter= now;
1999 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2003 // the current context is in mark[26]
2004 // the previous context is in mark[27]
2005 // only swap context if other context is valid
2006 if (text <= mark[27] && mark[27] <= end - 1) {
2008 mark[27] = mark[26];
2010 p = mark[26]; // where we are going- previous context
2011 context_start = prev_line(prev_line(prev_line(p)));
2012 context_end = next_line(next_line(next_line(p)));
2016 #endif /* FEATURE_VI_YANKMARK */
2018 static int isblnk(Byte c) // is the char a blank or tab
2020 return (c == ' ' || c == '\t');
2023 //----- Set terminal attributes --------------------------------
2024 static void rawmode(void)
2026 tcgetattr(0, &term_orig);
2027 term_vi = term_orig;
2028 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2029 term_vi.c_iflag &= (~IXON & ~ICRNL);
2030 term_vi.c_oflag &= (~ONLCR);
2031 term_vi.c_cc[VMIN] = 1;
2032 term_vi.c_cc[VTIME] = 0;
2033 erase_char = term_vi.c_cc[VERASE];
2034 tcsetattr(0, TCSANOW, &term_vi);
2037 static void cookmode(void)
2040 tcsetattr(0, TCSANOW, &term_orig);
2043 //----- Come here when we get a window resize signal ---------
2044 #if ENABLE_FEATURE_VI_USE_SIGNALS
2045 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2047 signal(SIGWINCH, winch_sig);
2048 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2049 get_terminal_width_height(0, &columns, &rows);
2050 new_screen(rows, columns); // get memory for virtual screen
2051 redraw(TRUE); // re-draw the screen
2054 //----- Come here when we get a continue signal -------------------
2055 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2057 rawmode(); // terminal to "raw"
2058 last_status_cksum = 0; // force status update
2059 redraw(TRUE); // re-draw the screen
2061 signal(SIGTSTP, suspend_sig);
2062 signal(SIGCONT, SIG_DFL);
2063 kill(my_pid, SIGCONT);
2066 //----- Come here when we get a Suspend signal -------------------
2067 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2069 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2070 clear_to_eol(); // Erase to end of line
2071 cookmode(); // terminal to "cooked"
2073 signal(SIGCONT, cont_sig);
2074 signal(SIGTSTP, SIG_DFL);
2075 kill(my_pid, SIGTSTP);
2078 //----- Come here when we get a signal ---------------------------
2079 static void catch_sig(int sig)
2081 signal(SIGINT, catch_sig);
2083 longjmp(restart, sig);
2085 #endif /* FEATURE_VI_USE_SIGNALS */
2087 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2089 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2094 tv.tv_usec = hund * 10000;
2095 select(1, &rfds, NULL, NULL, &tv);
2096 return FD_ISSET(0, &rfds);
2099 #define readbuffer bb_common_bufsiz1
2101 static int readed_for_parse;
2103 //----- IO Routines --------------------------------------------
2104 static Byte readit(void) // read (maybe cursor) key from stdin
2113 static const struct esc_cmds esccmds[] = {
2114 {"OA", (Byte) VI_K_UP}, // cursor key Up
2115 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2116 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2117 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2118 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2119 {"OF", (Byte) VI_K_END}, // Cursor Key End
2120 {"[A", (Byte) VI_K_UP}, // cursor key Up
2121 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2122 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2123 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2124 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2125 {"[F", (Byte) VI_K_END}, // Cursor Key End
2126 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2127 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2128 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2129 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2130 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2131 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2132 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2133 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2134 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2135 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2136 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2137 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2138 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2139 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2140 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2141 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2142 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2143 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2144 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2145 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2146 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2149 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2151 (void) alarm(0); // turn alarm OFF while we wait for input
2153 n = readed_for_parse;
2154 // get input from User- are there already input chars in Q?
2157 // the Q is empty, wait for a typed char
2158 n = read(0, readbuffer, BUFSIZ - 1);
2161 goto ri0; // interrupted sys call
2164 if (errno == EFAULT)
2166 if (errno == EINVAL)
2174 if (readbuffer[0] == 27) {
2175 // This is an ESC char. Is this Esc sequence?
2176 // Could be bare Esc key. See if there are any
2177 // more chars to read after the ESC. This would
2178 // be a Function or Cursor Key sequence.
2182 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2184 // keep reading while there are input chars and room in buffer
2185 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2186 // read the rest of the ESC string
2187 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2193 readed_for_parse = n;
2196 if(c == 27 && n > 1) {
2197 // Maybe cursor or function key?
2198 const struct esc_cmds *eindex;
2200 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2201 int cnt = strlen(eindex->seq);
2205 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2207 // is a Cursor key- put derived value back into Q
2209 // for squeeze out the ESC sequence
2213 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2214 /* defined ESC sequence not found, set only one ESC */
2220 // remove key sequence from Q
2221 readed_for_parse -= n;
2222 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2223 (void) alarm(3); // we are done waiting for input, turn alarm ON
2227 //----- IO Routines --------------------------------------------
2228 static Byte get_one_char(void)
2232 #if ENABLE_FEATURE_VI_DOT_CMD
2233 // ! adding2q && ioq == 0 read()
2234 // ! adding2q && ioq != 0 *ioq
2235 // adding2q *last_modifying_cmd= read()
2237 // we are not adding to the q.
2238 // but, we may be reading from a q
2240 // there is no current q, read from STDIN
2241 c = readit(); // get the users input
2243 // there is a queue to get chars from first
2246 // the end of the q, read from STDIN
2248 ioq_start = ioq = 0;
2249 c = readit(); // get the users input
2253 // adding STDIN chars to q
2254 c = readit(); // get the users input
2255 if (last_modifying_cmd != 0) {
2256 int len = strlen((char *) last_modifying_cmd);
2257 if (len + 1 >= BUFSIZ) {
2258 psbs("last_modifying_cmd overrun");
2260 // add new char to q
2261 last_modifying_cmd[len] = c;
2266 c = readit(); // get the users input
2267 #endif /* FEATURE_VI_DOT_CMD */
2268 return c; // return the char, where ever it came from
2271 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2276 static Byte *obufp = NULL;
2278 strcpy((char *) buf, (char *) prompt);
2279 last_status_cksum = 0; // force status update
2280 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2281 clear_to_eol(); // clear the line
2282 write1((char *) prompt); // write out the :, /, or ? prompt
2284 for (i = strlen((char *) buf); i < BUFSIZ;) {
2285 c = get_one_char(); // read user input
2286 if (c == '\n' || c == '\r' || c == 27)
2287 break; // is this end of input
2288 if (c == erase_char || c == 8 || c == 127) {
2289 // user wants to erase prev char
2290 i--; // backup to prev char
2291 buf[i] = '\0'; // erase the char
2292 buf[i + 1] = '\0'; // null terminate buffer
2293 write1("\b \b"); // erase char on screen
2294 if (i <= 0) { // user backs up before b-o-l, exit
2298 buf[i] = c; // save char in buffer
2299 buf[i + 1] = '\0'; // make sure buffer is null terminated
2300 putchar(c); // echo the char back to user
2306 obufp = (Byte *) xstrdup((char *) buf);
2310 static int file_size(const Byte * fn) // what is the byte size of "fn"
2315 if (fn == 0 || strlen((char *)fn) <= 0)
2318 sr = stat((char *) fn, &st_buf); // see if file exists
2320 cnt = (int) st_buf.st_size;
2325 static int file_insert(Byte * fn, Byte * p, int size)
2330 #if ENABLE_FEATURE_VI_READONLY
2333 if (fn == 0 || strlen((char*) fn) <= 0) {
2334 psbs("No filename given");
2338 // OK- this is just a no-op
2343 psbs("Trying to insert a negative number (%d) of characters", size);
2346 if (p < text || p > end) {
2347 psbs("Trying to insert file outside of memory");
2351 // see if we can open the file
2352 #if ENABLE_FEATURE_VI_READONLY
2353 if (vi_readonly) goto fi1; // do not try write-mode
2355 fd = open((char *) fn, O_RDWR); // assume read & write
2357 // could not open for writing- maybe file is read only
2358 #if ENABLE_FEATURE_VI_READONLY
2361 fd = open((char *) fn, O_RDONLY); // try read-only
2363 psbs("\"%s\" %s", fn, "cannot open file");
2366 #if ENABLE_FEATURE_VI_READONLY
2367 // got the file- read-only
2371 p = text_hole_make(p, size);
2372 cnt = read(fd, p, size);
2376 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2377 psbs("cannot read file \"%s\"", fn);
2378 } else if (cnt < size) {
2379 // There was a partial read, shrink unused space text[]
2380 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2381 psbs("cannot read all of file \"%s\"", fn);
2389 static int file_write(Byte * fn, Byte * first, Byte * last)
2391 int fd, cnt, charcnt;
2394 psbs("No current filename");
2398 // FIXIT- use the correct umask()
2399 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2402 cnt = last - first + 1;
2403 charcnt = write(fd, first, cnt);
2404 if (charcnt == cnt) {
2406 //file_modified= FALSE; // the file has not been modified
2414 //----- Terminal Drawing ---------------------------------------
2415 // The terminal is made up of 'rows' line of 'columns' columns.
2416 // classically this would be 24 x 80.
2417 // screen coordinates
2423 // 23,0 ... 23,79 status line
2426 //----- Move the cursor to row x col (count from 0, not 1) -------
2427 static void place_cursor(int row, int col, int opti)
2431 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2434 // char cm3[BUFSIZ];
2438 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2440 if (row < 0) row = 0;
2441 if (row >= rows) row = rows - 1;
2442 if (col < 0) col = 0;
2443 if (col >= columns) col = columns - 1;
2445 //----- 1. Try the standard terminal ESC sequence
2446 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2448 if (! opti) goto pc0;
2450 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2451 //----- find the minimum # of chars to move cursor -------------
2452 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2453 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2455 // move to the correct row
2456 while (row < Rrow) {
2457 // the cursor has to move up
2461 while (row > Rrow) {
2462 // the cursor has to move down
2463 strcat(cm2, CMdown);
2467 // now move to the correct column
2468 strcat(cm2, "\r"); // start at col 0
2469 // just send out orignal source char to get to correct place
2470 screenp = &screen[row * columns]; // start of screen line
2471 strncat(cm2, (char* )screenp, col);
2473 //----- 3. Try some other way of moving cursor
2474 //---------------------------------------------
2476 // pick the shortest cursor motion to send out
2478 if (strlen(cm2) < strlen(cm)) {
2480 } /* else if (strlen(cm3) < strlen(cm)) {
2483 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2485 write1(cm); // move the cursor
2488 //----- Erase from cursor to end of line -----------------------
2489 static void clear_to_eol(void)
2491 write1(Ceol); // Erase from cursor to end of line
2494 //----- Erase from cursor to end of screen -----------------------
2495 static void clear_to_eos(void)
2497 write1(Ceos); // Erase from cursor to end of screen
2500 //----- Start standout mode ------------------------------------
2501 static void standout_start(void) // send "start reverse video" sequence
2503 write1(SOs); // Start reverse video mode
2506 //----- End standout mode --------------------------------------
2507 static void standout_end(void) // send "end reverse video" sequence
2509 write1(SOn); // End reverse video mode
2512 //----- Flash the screen --------------------------------------
2513 static void flash(int h)
2515 standout_start(); // send "start reverse video" sequence
2518 standout_end(); // send "end reverse video" sequence
2522 static void Indicate_Error(void)
2524 #if ENABLE_FEATURE_VI_CRASHME
2526 return; // generate a random command
2529 write1(bell); // send out a bell character
2535 //----- Screen[] Routines --------------------------------------
2536 //----- Erase the Screen[] memory ------------------------------
2537 static void screen_erase(void)
2539 memset(screen, ' ', screensize); // clear new screen
2542 static int bufsum(unsigned char *buf, int count)
2545 unsigned char *e = buf + count;
2551 //----- Draw the status line at bottom of the screen -------------
2552 static void show_status_line(void)
2554 int cnt = 0, cksum = 0;
2556 // either we already have an error or status message, or we
2558 if (!have_status_msg) {
2559 cnt = format_edit_status();
2560 cksum = bufsum(status_buffer, cnt);
2562 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2563 last_status_cksum= cksum; // remember if we have seen this line
2564 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2565 write1((char*)status_buffer);
2567 if (have_status_msg) {
2568 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2570 have_status_msg = 0;
2573 have_status_msg = 0;
2575 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2580 //----- format the status buffer, the bottom line of screen ------
2581 // format status buffer, with STANDOUT mode
2582 static void psbs(const char *format, ...)
2586 va_start(args, format);
2587 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2588 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2589 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2592 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2597 // format status buffer
2598 static void psb(const char *format, ...)
2602 va_start(args, format);
2603 vsprintf((char *) status_buffer, format, args);
2606 have_status_msg = 1;
2611 static void ni(Byte * s) // display messages
2615 print_literal(buf, s);
2616 psbs("\'%s\' is not implemented", buf);
2619 static int format_edit_status(void) // show file status on status line
2621 int cur, percent, ret, trunc_at;
2624 // file_modified is now a counter rather than a flag. this
2625 // helps reduce the amount of line counting we need to do.
2626 // (this will cause a mis-reporting of modified status
2627 // once every MAXINT editing operations.)
2629 // it would be nice to do a similar optimization here -- if
2630 // we haven't done a motion that could have changed which line
2631 // we're on, then we shouldn't have to do this count_lines()
2632 cur = count_lines(text, dot);
2634 // reduce counting -- the total lines can't have
2635 // changed if we haven't done any edits.
2636 if (file_modified != last_file_modified) {
2637 tot = cur + count_lines(dot, end - 1) - 1;
2638 last_file_modified = file_modified;
2641 // current line percent
2642 // ------------- ~~ ----------
2645 percent = (100 * cur) / tot;
2651 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2652 columns : STATUS_BUFFER_LEN-1;
2654 ret = snprintf((char *) status_buffer, trunc_at+1,
2655 #if ENABLE_FEATURE_VI_READONLY
2656 "%c %s%s%s %d/%d %d%%",
2658 "%c %s%s %d/%d %d%%",
2660 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2661 (cfn != 0 ? (char *) cfn : "No file"),
2662 #if ENABLE_FEATURE_VI_READONLY
2663 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2665 (file_modified ? " [modified]" : ""),
2668 if (ret >= 0 && ret < trunc_at)
2669 return ret; /* it all fit */
2671 return trunc_at; /* had to truncate */
2674 //----- Force refresh of all Lines -----------------------------
2675 static void redraw(int full_screen)
2677 place_cursor(0, 0, FALSE); // put cursor in correct place
2678 clear_to_eos(); // tel terminal to erase display
2679 screen_erase(); // erase the internal screen buffer
2680 last_status_cksum = 0; // force status update
2681 refresh(full_screen); // this will redraw the entire display
2685 //----- Format a text[] line into a buffer ---------------------
2686 static void format_line(Byte *dest, Byte *src, int li)
2691 for (co= 0; co < MAX_SCR_COLS; co++) {
2692 c= ' '; // assume blank
2693 if (li > 0 && co == 0) {
2694 c = '~'; // not first line, assume Tilde
2696 // are there chars in text[] and have we gone past the end
2697 if (text < end && src < end) {
2702 if (c > 127 && !Isprint(c)) {
2705 if (c < ' ' || c == 127) {
2709 for (; (co % tabstop) != (tabstop - 1); co++) {
2717 c += '@'; // make it visible
2720 // the co++ is done here so that the column will
2721 // not be overwritten when we blank-out the rest of line
2728 //----- Refresh the changed screen lines -----------------------
2729 // Copy the source line from text[] into the buffer and note
2730 // if the current screenline is different from the new buffer.
2731 // If they differ then that line needs redrawing on the terminal.
2733 static void refresh(int full_screen)
2735 static int old_offset;
2737 Byte buf[MAX_SCR_COLS];
2738 Byte *tp, *sp; // pointer into text[] and screen[]
2739 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2740 int last_li= -2; // last line that changed- for optimizing cursor movement
2743 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2744 get_terminal_width_height(0, &columns, &rows);
2745 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2746 tp = screenbegin; // index into text[] of top line
2748 // compare text[] to screen[] and mark screen[] lines that need updating
2749 for (li = 0; li < rows - 1; li++) {
2750 int cs, ce; // column start & end
2751 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2752 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2753 // format current text line into buf
2754 format_line(buf, tp, li);
2756 // skip to the end of the current text[] line
2757 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2759 // see if there are any changes between vitual screen and buf
2760 changed = FALSE; // assume no change
2763 sp = &screen[li * columns]; // start of screen line
2765 // force re-draw of every single column from 0 - columns-1
2768 // compare newly formatted buffer with virtual screen
2769 // look forward for first difference between buf and screen
2770 for ( ; cs <= ce; cs++) {
2771 if (buf[cs + offset] != sp[cs]) {
2772 changed = TRUE; // mark for redraw
2777 // look backward for last difference between buf and screen
2778 for ( ; ce >= cs; ce--) {
2779 if (buf[ce + offset] != sp[ce]) {
2780 changed = TRUE; // mark for redraw
2784 // now, cs is index of first diff, and ce is index of last diff
2786 // if horz offset has changed, force a redraw
2787 if (offset != old_offset) {
2792 // make a sanity check of columns indexes
2794 if (ce > columns-1) ce= columns-1;
2795 if (cs > ce) { cs= 0; ce= columns-1; }
2796 // is there a change between vitual screen and buf
2798 // copy changed part of buffer to virtual screen
2799 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2801 // move cursor to column of first change
2802 if (offset != old_offset) {
2803 // opti_cur_move is still too stupid
2804 // to handle offsets correctly
2805 place_cursor(li, cs, FALSE);
2807 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2808 // if this just the next line
2809 // try to optimize cursor movement
2810 // otherwise, use standard ESC sequence
2811 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2814 place_cursor(li, cs, FALSE); // use standard ESC sequence
2815 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2818 // write line out to terminal
2821 char *out = (char*)sp+cs;
2828 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2834 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2835 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2838 place_cursor(crow, ccol, FALSE);
2841 if (offset != old_offset)
2842 old_offset = offset;
2845 //---------------------------------------------------------------------
2846 //----- the Ascii Chart -----------------------------------------------
2848 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2849 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2850 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2851 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2852 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2853 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2854 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2855 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2856 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2857 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2858 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2859 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2860 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2861 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2862 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2863 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2864 //---------------------------------------------------------------------
2866 //----- Execute a Vi Command -----------------------------------
2867 static void do_cmd(Byte c)
2869 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2870 int cnt, i, j, dir, yf;
2872 c1 = c; // quiet the compiler
2873 cnt = yf = dir = 0; // quiet the compiler
2874 p = q = save_dot = msg = buf; // quiet the compiler
2875 memset(buf, '\0', 9); // clear buf
2879 /* if this is a cursor key, skip these checks */
2892 if (cmd_mode == 2) {
2893 // flip-flop Insert/Replace mode
2894 if (c == VI_K_INSERT) goto dc_i;
2895 // we are 'R'eplacing the current *dot with new char
2897 // don't Replace past E-o-l
2898 cmd_mode = 1; // convert to insert
2900 if (1 <= c || Isprint(c)) {
2902 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2903 dot = char_insert(dot, c); // insert new char
2908 if (cmd_mode == 1) {
2909 // hitting "Insert" twice means "R" replace mode
2910 if (c == VI_K_INSERT) goto dc5;
2911 // insert the char c at "dot"
2912 if (1 <= c || Isprint(c)) {
2913 dot = char_insert(dot, c);
2928 #if ENABLE_FEATURE_VI_CRASHME
2929 case 0x14: // dc4 ctrl-T
2930 crashme = (crashme == 0) ? 1 : 0;
2961 //case 'u': // u- FIXME- there is no undo
2963 default: // unrecognised command
2972 end_cmd_q(); // stop adding to q
2973 case 0x00: // nul- ignore
2975 case 2: // ctrl-B scroll up full screen
2976 case VI_K_PAGEUP: // Cursor Key Page Up
2977 dot_scroll(rows - 2, -1);
2979 #if ENABLE_FEATURE_VI_USE_SIGNALS
2980 case 0x03: // ctrl-C interrupt
2981 longjmp(restart, 1);
2983 case 26: // ctrl-Z suspend
2984 suspend_sig(SIGTSTP);
2987 case 4: // ctrl-D scroll down half screen
2988 dot_scroll((rows - 2) / 2, 1);
2990 case 5: // ctrl-E scroll down one line
2993 case 6: // ctrl-F scroll down full screen
2994 case VI_K_PAGEDOWN: // Cursor Key Page Down
2995 dot_scroll(rows - 2, 1);
2997 case 7: // ctrl-G show current status
2998 last_status_cksum = 0; // force status update
3000 case 'h': // h- move left
3001 case VI_K_LEFT: // cursor key Left
3002 case 8: // ctrl-H- move left (This may be ERASE char)
3003 case 127: // DEL- move left (This may be ERASE char)
3009 case 10: // Newline ^J
3010 case 'j': // j- goto next line, same col
3011 case VI_K_DOWN: // cursor key Down
3015 dot_next(); // go to next B-o-l
3016 dot = move_to_col(dot, ccol + offset); // try stay in same col
3018 case 12: // ctrl-L force redraw whole screen
3019 case 18: // ctrl-R force redraw
3020 place_cursor(0, 0, FALSE); // put cursor in correct place
3021 clear_to_eos(); // tel terminal to erase display
3023 screen_erase(); // erase the internal screen buffer
3024 last_status_cksum = 0; // force status update
3025 refresh(TRUE); // this will redraw the entire display
3027 case 13: // Carriage Return ^M
3028 case '+': // +- goto next line
3035 case 21: // ctrl-U scroll up half screen
3036 dot_scroll((rows - 2) / 2, -1);
3038 case 25: // ctrl-Y scroll up one line
3044 cmd_mode = 0; // stop insrting
3046 last_status_cksum = 0; // force status update
3048 case ' ': // move right
3049 case 'l': // move right
3050 case VI_K_RIGHT: // Cursor Key Right
3056 #if ENABLE_FEATURE_VI_YANKMARK
3057 case '"': // "- name a register to use for Delete/Yank
3058 c1 = get_one_char();
3066 case '\'': // '- goto a specific mark
3067 c1 = get_one_char();
3073 if (text <= q && q < end) {
3075 dot_begin(); // go to B-o-l
3078 } else if (c1 == '\'') { // goto previous context
3079 dot = swap_context(dot); // swap current and previous context
3080 dot_begin(); // go to B-o-l
3086 case 'm': // m- Mark a line
3087 // this is really stupid. If there are any inserts or deletes
3088 // between text[0] and dot then this mark will not point to the
3089 // correct location! It could be off by many lines!
3090 // Well..., at least its quick and dirty.
3091 c1 = get_one_char();
3095 // remember the line
3096 mark[(int) c1] = dot;
3101 case 'P': // P- Put register before
3102 case 'p': // p- put register after
3105 psbs("Nothing in register %c", what_reg());
3108 // are we putting whole lines or strings
3109 if (strchr((char *) p, '\n') != NULL) {
3111 dot_begin(); // putting lines- Put above
3114 // are we putting after very last line?
3115 if (end_line(dot) == (end - 1)) {
3116 dot = end; // force dot to end of text[]
3118 dot_next(); // next line, then put before
3123 dot_right(); // move to right, can move to NL
3125 dot = string_insert(dot, p); // insert the string
3126 end_cmd_q(); // stop adding to q
3128 case 'U': // U- Undo; replace current line with original version
3129 if (reg[Ureg] != 0) {
3130 p = begin_line(dot);
3132 p = text_hole_delete(p, q); // delete cur line
3133 p = string_insert(p, reg[Ureg]); // insert orig line
3138 #endif /* FEATURE_VI_YANKMARK */
3139 case '$': // $- goto end of line
3140 case VI_K_END: // Cursor Key End
3144 dot = end_line(dot);
3146 case '%': // %- find matching char of pair () [] {}
3147 for (q = dot; q < end && *q != '\n'; q++) {
3148 if (strchr("()[]{}", *q) != NULL) {
3149 // we found half of a pair
3150 p = find_pair(q, *q);
3162 case 'f': // f- forward to a user specified char
3163 last_forward_char = get_one_char(); // get the search char
3165 // dont separate these two commands. 'f' depends on ';'
3167 //**** fall thru to ... ';'
3168 case ';': // ;- look at rest of line for last forward char
3172 if (last_forward_char == 0) break;
3174 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3177 if (*q == last_forward_char)
3180 case '-': // -- goto prev line
3187 #if ENABLE_FEATURE_VI_DOT_CMD
3188 case '.': // .- repeat the last modifying command
3189 // Stuff the last_modifying_cmd back into stdin
3190 // and let it be re-executed.
3191 if (last_modifying_cmd != 0) {
3192 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3196 #if ENABLE_FEATURE_VI_SEARCH
3197 case '?': // /- search for a pattern
3198 case '/': // /- search for a pattern
3201 q = get_input_line(buf); // get input line- use "status line"
3202 if (strlen((char *) q) == 1)
3203 goto dc3; // if no pat re-use old pat
3204 if (strlen((char *) q) > 1) { // new pat- save it and find
3205 // there is a new pat
3206 free(last_search_pattern);
3207 last_search_pattern = (Byte *) xstrdup((char *) q);
3208 goto dc3; // now find the pattern
3210 // user changed mind and erased the "/"- do nothing
3212 case 'N': // N- backward search for last pattern
3216 dir = BACK; // assume BACKWARD search
3218 if (last_search_pattern[0] == '?') {
3222 goto dc4; // now search for pattern
3224 case 'n': // n- repeat search for last pattern
3225 // search rest of text[] starting at next char
3226 // if search fails return orignal "p" not the "p+1" address
3231 if (last_search_pattern == 0) {
3232 msg = (Byte *) "No previous regular expression";
3235 if (last_search_pattern[0] == '/') {
3236 dir = FORWARD; // assume FORWARD search
3239 if (last_search_pattern[0] == '?') {
3244 q = char_search(p, last_search_pattern + 1, dir, FULL);
3246 dot = q; // good search, update "dot"
3250 // no pattern found between "dot" and "end"- continue at top
3255 q = char_search(p, last_search_pattern + 1, dir, FULL);
3256 if (q != NULL) { // found something
3257 dot = q; // found new pattern- goto it
3258 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3260 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3263 msg = (Byte *) "Pattern not found";
3266 if (*msg) psbs("%s", msg);
3268 case '{': // {- move backward paragraph
3269 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3270 if (q != NULL) { // found blank line
3271 dot = next_line(q); // move to next blank line
3274 case '}': // }- move forward paragraph
3275 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3276 if (q != NULL) { // found blank line
3277 dot = next_line(q); // move to next blank line
3280 #endif /* FEATURE_VI_SEARCH */
3281 case '0': // 0- goto begining of line
3291 if (c == '0' && cmdcnt < 1) {
3292 dot_begin(); // this was a standalone zero
3294 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3297 case ':': // :- the colon mode commands
3298 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3299 #if ENABLE_FEATURE_VI_COLON
3300 colon(p); // execute the command
3303 p++; // move past the ':'
3304 cnt = strlen((char *) p);
3307 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3308 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3309 if (file_modified && p[1] != '!') {
3310 psbs("No write since last change (:quit! overrides)");
3314 } else if (strncasecmp((char *) p, "write", cnt) == 0
3315 || strncasecmp((char *) p, "wq", cnt) == 0
3316 || strncasecmp((char *) p, "wn", cnt) == 0
3317 || strncasecmp((char *) p, "x", cnt) == 0) {
3318 cnt = file_write(cfn, text, end - 1);
3321 psbs("Write error: %s", strerror(errno));
3324 last_file_modified = -1;
3325 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3326 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3327 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3331 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3332 last_status_cksum = 0; // force status update
3333 } else if (sscanf((char *) p, "%d", &j) > 0) {
3334 dot = find_line(j); // go to line # j
3336 } else { // unrecognised cmd
3339 #endif /* !FEATURE_VI_COLON */
3341 case '<': // <- Left shift something
3342 case '>': // >- Right shift something
3343 cnt = count_lines(text, dot); // remember what line we are on
3344 c1 = get_one_char(); // get the type of thing to delete
3345 find_range(&p, &q, c1);
3346 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3349 i = count_lines(p, q); // # of lines we are shifting
3350 for ( ; i > 0; i--, p = next_line(p)) {
3352 // shift left- remove tab or 8 spaces
3354 // shrink buffer 1 char
3355 (void) text_hole_delete(p, p);
3356 } else if (*p == ' ') {
3357 // we should be calculating columns, not just SPACE
3358 for (j = 0; *p == ' ' && j < tabstop; j++) {
3359 (void) text_hole_delete(p, p);
3362 } else if (c == '>') {
3363 // shift right -- add tab or 8 spaces
3364 (void) char_insert(p, '\t');
3367 dot = find_line(cnt); // what line were we on
3369 end_cmd_q(); // stop adding to q
3371 case 'A': // A- append at e-o-l
3372 dot_end(); // go to e-o-l
3373 //**** fall thru to ... 'a'
3374 case 'a': // a- append after current char
3379 case 'B': // B- back a blank-delimited Word
3380 case 'E': // E- end of a blank-delimited word
3381 case 'W': // W- forward a blank-delimited word
3388 if (c == 'W' || isspace(dot[dir])) {
3389 dot = skip_thing(dot, 1, dir, S_TO_WS);
3390 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3393 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3395 case 'C': // C- Change to e-o-l
3396 case 'D': // D- delete to e-o-l
3398 dot = dollar_line(dot); // move to before NL
3399 // copy text into a register and delete
3400 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3402 goto dc_i; // start inserting
3403 #if ENABLE_FEATURE_VI_DOT_CMD
3405 end_cmd_q(); // stop adding to q
3408 case 'G': // G- goto to a line number (default= E-O-F)
3409 dot = end - 1; // assume E-O-F
3411 dot = find_line(cmdcnt); // what line is #cmdcnt
3415 case 'H': // H- goto top line on screen
3417 if (cmdcnt > (rows - 1)) {
3418 cmdcnt = (rows - 1);
3425 case 'I': // I- insert before first non-blank
3428 //**** fall thru to ... 'i'
3429 case 'i': // i- insert before current char
3430 case VI_K_INSERT: // Cursor Key Insert
3432 cmd_mode = 1; // start insrting
3434 case 'J': // J- join current and next lines together
3438 dot_end(); // move to NL
3439 if (dot < end - 1) { // make sure not last char in text[]
3440 *dot++ = ' '; // replace NL with space
3442 while (isblnk(*dot)) { // delete leading WS
3446 end_cmd_q(); // stop adding to q
3448 case 'L': // L- goto bottom line on screen
3450 if (cmdcnt > (rows - 1)) {
3451 cmdcnt = (rows - 1);
3459 case 'M': // M- goto middle line on screen
3461 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3462 dot = next_line(dot);
3464 case 'O': // O- open a empty line above
3466 p = begin_line(dot);
3467 if (p[-1] == '\n') {
3469 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3471 dot = char_insert(dot, '\n');
3474 dot = char_insert(dot, '\n'); // i\n ESC
3479 case 'R': // R- continuous Replace char
3483 case 'X': // X- delete char before dot
3484 case 'x': // x- delete the current char
3485 case 's': // s- substitute the current char
3492 if (dot[dir] != '\n') {
3494 dot--; // delete prev char
3495 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3498 goto dc_i; // start insrting
3499 end_cmd_q(); // stop adding to q
3501 case 'Z': // Z- if modified, {write}; exit
3502 // ZZ means to save file (if necessary), then exit
3503 c1 = get_one_char();
3508 if (file_modified) {
3509 #if ENABLE_FEATURE_VI_READONLY
3510 if (vi_readonly || readonly) {
3511 psbs("\"%s\" File is read only", cfn);
3515 cnt = file_write(cfn, text, end - 1);
3518 psbs("Write error: %s", strerror(errno));
3519 } else if (cnt == (end - 1 - text + 1)) {
3526 case '^': // ^- move to first non-blank on line
3530 case 'b': // b- back a word
3531 case 'e': // e- end of word
3538 if ((dot + dir) < text || (dot + dir) > end - 1)
3541 if (isspace(*dot)) {
3542 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3544 if (isalnum(*dot) || *dot == '_') {
3545 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3546 } else if (ispunct(*dot)) {
3547 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3550 case 'c': // c- change something
3551 case 'd': // d- delete something
3552 #if ENABLE_FEATURE_VI_YANKMARK
3553 case 'y': // y- yank something
3554 case 'Y': // Y- Yank a line
3556 yf = YANKDEL; // assume either "c" or "d"
3557 #if ENABLE_FEATURE_VI_YANKMARK
3558 if (c == 'y' || c == 'Y')
3563 c1 = get_one_char(); // get the type of thing to delete
3564 find_range(&p, &q, c1);
3565 if (c1 == 27) { // ESC- user changed mind and wants out
3566 c = c1 = 27; // Escape- do nothing
3567 } else if (strchr("wW", c1)) {
3569 // don't include trailing WS as part of word
3570 while (isblnk(*q)) {
3571 if (q <= text || q[-1] == '\n')
3576 dot = yank_delete(p, q, 0, yf); // delete word
3577 } else if (strchr("^0bBeEft$", c1)) {
3578 // single line copy text into a register and delete
3579 dot = yank_delete(p, q, 0, yf); // delete word
3580 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3581 // multiple line copy text into a register and delete
3582 dot = yank_delete(p, q, 1, yf); // delete lines
3584 dot = char_insert(dot, '\n');
3585 // on the last line of file don't move to prev line
3586 if (dot != (end-1)) {
3589 } else if (c == 'd') {
3594 // could not recognize object
3595 c = c1 = 27; // error-
3599 // if CHANGING, not deleting, start inserting after the delete
3601 strcpy((char *) buf, "Change");
3602 goto dc_i; // start inserting
3605 strcpy((char *) buf, "Delete");
3607 #if ENABLE_FEATURE_VI_YANKMARK
3608 if (c == 'y' || c == 'Y') {
3609 strcpy((char *) buf, "Yank");
3612 q = p + strlen((char *) p);
3613 for (cnt = 0; p <= q; p++) {
3617 psb("%s %d lines (%d chars) using [%c]",
3618 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3620 end_cmd_q(); // stop adding to q
3623 case 'k': // k- goto prev line, same col
3624 case VI_K_UP: // cursor key Up
3629 dot = move_to_col(dot, ccol + offset); // try stay in same col
3631 case 'r': // r- replace the current char with user input
3632 c1 = get_one_char(); // get the replacement char
3635 file_modified++; // has the file been modified
3637 end_cmd_q(); // stop adding to q
3639 case 't': // t- move to char prior to next x
3640 last_forward_char = get_one_char();
3642 if (*dot == last_forward_char)
3644 last_forward_char= 0;
3646 case 'w': // w- forward a word
3650 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3651 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3652 } else if (ispunct(*dot)) { // we are on PUNCT
3653 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3656 dot++; // move over word
3657 if (isspace(*dot)) {
3658 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3662 c1 = get_one_char(); // get the replacement char
3665 cnt = (rows - 2) / 2; // put dot at center
3667 cnt = rows - 2; // put dot at bottom
3668 screenbegin = begin_line(dot); // start dot at top
3669 dot_scroll(cnt, -1);
3671 case '|': // |- move to column "cmdcnt"
3672 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3674 case '~': // ~- flip the case of letters a-z -> A-Z
3678 if (islower(*dot)) {
3679 *dot = toupper(*dot);
3680 file_modified++; // has the file been modified
3681 } else if (isupper(*dot)) {
3682 *dot = tolower(*dot);
3683 file_modified++; // has the file been modified
3686 end_cmd_q(); // stop adding to q
3688 //----- The Cursor and Function Keys -----------------------------
3689 case VI_K_HOME: // Cursor Key Home
3692 // The Fn keys could point to do_macro which could translate them
3693 case VI_K_FUN1: // Function Key F1
3694 case VI_K_FUN2: // Function Key F2
3695 case VI_K_FUN3: // Function Key F3
3696 case VI_K_FUN4: // Function Key F4
3697 case VI_K_FUN5: // Function Key F5
3698 case VI_K_FUN6: // Function Key F6
3699 case VI_K_FUN7: // Function Key F7
3700 case VI_K_FUN8: // Function Key F8
3701 case VI_K_FUN9: // Function Key F9
3702 case VI_K_FUN10: // Function Key F10
3703 case VI_K_FUN11: // Function Key F11
3704 case VI_K_FUN12: // Function Key F12
3709 // if text[] just became empty, add back an empty line
3711 (void) char_insert(text, '\n'); // start empty buf with dummy line
3714 // it is OK for dot to exactly equal to end, otherwise check dot validity
3716 dot = bound_dot(dot); // make sure "dot" is valid
3718 #if ENABLE_FEATURE_VI_YANKMARK
3719 check_context(c); // update the current context
3723 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3724 cnt = dot - begin_line(dot);
3725 // Try to stay off of the Newline
3726 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3730 #if ENABLE_FEATURE_VI_CRASHME
3731 static int totalcmds = 0;
3732 static int Mp = 85; // Movement command Probability
3733 static int Np = 90; // Non-movement command Probability
3734 static int Dp = 96; // Delete command Probability
3735 static int Ip = 97; // Insert command Probability
3736 static int Yp = 98; // Yank command Probability
3737 static int Pp = 99; // Put command Probability
3738 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3739 char chars[20] = "\t012345 abcdABCD-=.$";
3740 char *words[20] = { "this", "is", "a", "test",
3741 "broadcast", "the", "emergency", "of",
3742 "system", "quick", "brown", "fox",
3743 "jumped", "over", "lazy", "dogs",
3744 "back", "January", "Febuary", "March"
3747 "You should have received a copy of the GNU General Public License\n",
3748 "char c, cm, *cmd, *cmd1;\n",
3749 "generate a command by percentages\n",
3750 "Numbers may be typed as a prefix to some commands.\n",
3751 "Quit, discarding changes!\n",
3752 "Forced write, if permission originally not valid.\n",
3753 "In general, any ex or ed command (such as substitute or delete).\n",
3754 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3755 "Please get w/ me and I will go over it with you.\n",
3756 "The following is a list of scheduled, committed changes.\n",
3757 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3758 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3759 "Any question about transactions please contact Sterling Huxley.\n",
3760 "I will try to get back to you by Friday, December 31.\n",
3761 "This Change will be implemented on Friday.\n",
3762 "Let me know if you have problems accessing this;\n",
3763 "Sterling Huxley recently added you to the access list.\n",
3764 "Would you like to go to lunch?\n",
3765 "The last command will be automatically run.\n",
3766 "This is too much english for a computer geek.\n",
3768 char *multilines[20] = {
3769 "You should have received a copy of the GNU General Public License\n",
3770 "char c, cm, *cmd, *cmd1;\n",
3771 "generate a command by percentages\n",
3772 "Numbers may be typed as a prefix to some commands.\n",
3773 "Quit, discarding changes!\n",
3774 "Forced write, if permission originally not valid.\n",
3775 "In general, any ex or ed command (such as substitute or delete).\n",
3776 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3777 "Please get w/ me and I will go over it with you.\n",
3778 "The following is a list of scheduled, committed changes.\n",
3779 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3780 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3781 "Any question about transactions please contact Sterling Huxley.\n",
3782 "I will try to get back to you by Friday, December 31.\n",
3783 "This Change will be implemented on Friday.\n",
3784 "Let me know if you have problems accessing this;\n",
3785 "Sterling Huxley recently added you to the access list.\n",
3786 "Would you like to go to lunch?\n",
3787 "The last command will be automatically run.\n",
3788 "This is too much english for a computer geek.\n",
3791 // create a random command to execute
3792 static void crash_dummy()
3794 static int sleeptime; // how long to pause between commands
3795 char c, cm, *cmd, *cmd1;
3796 int i, cnt, thing, rbi, startrbi, percent;
3798 // "dot" movement commands
3799 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3801 // is there already a command running?
3802 if (readed_for_parse > 0)
3806 sleeptime = 0; // how long to pause between commands
3807 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3808 // generate a command by percentages
3809 percent = (int) lrand48() % 100; // get a number from 0-99
3810 if (percent < Mp) { // Movement commands
3811 // available commands
3814 } else if (percent < Np) { // non-movement commands
3815 cmd = "mz<>\'\""; // available commands
3817 } else if (percent < Dp) { // Delete commands
3818 cmd = "dx"; // available commands
3820 } else if (percent < Ip) { // Inset commands
3821 cmd = "iIaAsrJ"; // available commands
3823 } else if (percent < Yp) { // Yank commands
3824 cmd = "yY"; // available commands
3826 } else if (percent < Pp) { // Put commands
3827 cmd = "pP"; // available commands
3830 // We do not know how to handle this command, try again
3834 // randomly pick one of the available cmds from "cmd[]"
3835 i = (int) lrand48() % strlen(cmd);
3837 if (strchr(":\024", cm))
3838 goto cd0; // dont allow colon or ctrl-T commands
3839 readbuffer[rbi++] = cm; // put cmd into input buffer
3841 // now we have the command-
3842 // there are 1, 2, and multi char commands
3843 // find out which and generate the rest of command as necessary
3844 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3845 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3846 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3847 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3849 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3851 readbuffer[rbi++] = c; // add movement to input buffer
3853 if (strchr("iIaAsc", cm)) { // multi-char commands
3855 // change some thing
3856 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3858 readbuffer[rbi++] = c; // add movement to input buffer
3860 thing = (int) lrand48() % 4; // what thing to insert
3861 cnt = (int) lrand48() % 10; // how many to insert
3862 for (i = 0; i < cnt; i++) {
3863 if (thing == 0) { // insert chars
3864 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3865 } else if (thing == 1) { // insert words
3866 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3867 strcat((char *) readbuffer, " ");
3868 sleeptime = 0; // how fast to type
3869 } else if (thing == 2) { // insert lines
3870 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3871 sleeptime = 0; // how fast to type
3872 } else { // insert multi-lines
3873 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3874 sleeptime = 0; // how fast to type
3877 strcat((char *) readbuffer, "\033");
3879 readed_for_parse = strlen(readbuffer);
3883 (void) mysleep(sleeptime); // sleep 1/100 sec
3886 // test to see if there are any errors
3887 static void crash_test()
3889 static time_t oldtim;
3891 char d[2], msg[BUFSIZ];
3895 strcat((char *) msg, "end<text ");
3897 if (end > textend) {
3898 strcat((char *) msg, "end>textend ");
3901 strcat((char *) msg, "dot<text ");
3904 strcat((char *) msg, "dot>end ");
3906 if (screenbegin < text) {
3907 strcat((char *) msg, "screenbegin<text ");
3909 if (screenbegin > end - 1) {
3910 strcat((char *) msg, "screenbegin>end-1 ");
3913 if (strlen(msg) > 0) {
3915 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3916 totalcmds, last_input_char, msg, SOs, SOn);
3918 while (read(0, d, 1) > 0) {
3919 if (d[0] == '\n' || d[0] == '\r')
3924 tim = (time_t) time((time_t *) 0);
3925 if (tim >= (oldtim + 3)) {
3926 sprintf((char *) status_buffer,
3927 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3928 totalcmds, M, N, I, D, Y, P, U, end - text + 1);