1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
26 #define ENABLE_FEATURE_VI_CRASHME 0
28 #if ENABLE_LOCALE_SUPPORT
29 #define Isprint(c) isprint((c))
31 /* 0x9b is Meta-ESC */
32 #define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
36 MAX_LINELEN = CONFIG_FEATURE_VI_MAX_LEN,
37 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
40 // Misc. non-Ascii keys that report an escape sequence
41 #define VI_K_UP (char)128 // cursor key Up
42 #define VI_K_DOWN (char)129 // cursor key Down
43 #define VI_K_RIGHT (char)130 // Cursor Key Right
44 #define VI_K_LEFT (char)131 // cursor key Left
45 #define VI_K_HOME (char)132 // Cursor Key Home
46 #define VI_K_END (char)133 // Cursor Key End
47 #define VI_K_INSERT (char)134 // Cursor Key Insert
48 #define VI_K_PAGEUP (char)135 // Cursor Key Page Up
49 #define VI_K_PAGEDOWN (char)136 // Cursor Key Page Down
50 #define VI_K_FUN1 (char)137 // Function Key F1
51 #define VI_K_FUN2 (char)138 // Function Key F2
52 #define VI_K_FUN3 (char)139 // Function Key F3
53 #define VI_K_FUN4 (char)140 // Function Key F4
54 #define VI_K_FUN5 (char)141 // Function Key F5
55 #define VI_K_FUN6 (char)142 // Function Key F6
56 #define VI_K_FUN7 (char)143 // Function Key F7
57 #define VI_K_FUN8 (char)144 // Function Key F8
58 #define VI_K_FUN9 (char)145 // Function Key F9
59 #define VI_K_FUN10 (char)146 // Function Key F10
60 #define VI_K_FUN11 (char)147 // Function Key F11
61 #define VI_K_FUN12 (char)148 // Function Key F12
63 /* vt102 typical ESC sequence */
64 /* terminal standout start/normal ESC sequence */
65 static const char SOs[] ALIGN1 = "\033[7m";
66 static const char SOn[] ALIGN1 = "\033[0m";
67 /* terminal bell sequence */
68 static const char bell[] ALIGN1 = "\007";
69 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
70 static const char Ceol[] ALIGN1 = "\033[0K";
71 static const char Ceos[] ALIGN1 = "\033[0J";
72 /* Cursor motion arbitrary destination ESC sequence */
73 static const char CMrc[] ALIGN1 = "\033[%d;%dH";
74 /* Cursor motion up and down ESC sequence */
75 static const char CMup[] ALIGN1 = "\033[A";
76 static const char CMdown[] ALIGN1 = "\n";
82 FORWARD = 1, // code depends on "1" for array index
83 BACK = -1, // code depends on "-1" for array index
84 LIMITED = 0, // how much of text[] in char_search
85 FULL = 1, // how much of text[] in char_search
87 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
88 S_TO_WS = 2, // used in skip_thing() for moving "dot"
89 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
90 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
91 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
94 /* vi.c expects chars to be unsigned. */
95 /* busybox build system provides that, but it's better */
96 /* to audit and fix the source */
98 static smallint vi_setops;
99 #define VI_AUTOINDENT 1
100 #define VI_SHOWMATCH 2
101 #define VI_IGNORECASE 4
102 #define VI_ERR_METHOD 8
103 #define autoindent (vi_setops & VI_AUTOINDENT)
104 #define showmatch (vi_setops & VI_SHOWMATCH )
105 #define ignorecase (vi_setops & VI_IGNORECASE)
106 /* indicate error with beep or flash */
107 #define err_method (vi_setops & VI_ERR_METHOD)
110 static smallint editing; // >0 while we are editing a file
111 // [code audit says "can be 0 or 1 only"]
112 static smallint cmd_mode; // 0=command 1=insert 2=replace
113 static smallint file_modified; // buffer contents changed
114 static smallint last_file_modified = -1;
115 static int fn_start; // index of first cmd line file name
116 static int save_argc; // how many file names on cmd line
117 static int cmdcnt; // repetition count
118 static int rows, columns; // the terminal screen is this size
119 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
120 static char *status_buffer; // mesages to the user
121 #define STATUS_BUFFER_LEN 200
122 static int have_status_msg; // is default edit status needed?
123 // [don't make smallint!]
124 static int last_status_cksum; // hash of current status line
125 static char *current_filename; // current file name
126 //static char *text, *end; // pointers to the user data in memory
127 static char *screen; // pointer to the virtual screen buffer
128 static int screensize; // and its size
129 static char *screenbegin; // index into text[], of top line on the screen
130 //static char *dot; // where all the action takes place
132 static char erase_char; // the users erase character
133 static char last_input_char; // last char read from user
134 static char last_forward_char; // last char searched for with 'f'
136 #if ENABLE_FEATURE_VI_READONLY
137 //static smallint vi_readonly, readonly;
138 static smallint readonly_mode = 0;
139 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
140 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
141 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
143 #define readonly_mode 0
144 #define SET_READONLY_FILE(flags)
145 #define SET_READONLY_MODE(flags)
146 #define UNSET_READONLY_FILE(flags)
149 #if ENABLE_FEATURE_VI_DOT_CMD
150 static smallint adding2q; // are we currently adding user input to q
151 static char *last_modifying_cmd; // last modifying cmd for "."
152 static char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
154 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
155 static int last_row; // where the cursor was last moved to
157 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
160 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
161 static char *modifying_cmds; // cmds that modify text[]
163 #if ENABLE_FEATURE_VI_SEARCH
164 static char *last_search_pattern; // last pattern from a '/' or '?' search
167 /* Moving biggest data to malloced space... */
169 /* many references - keep near the top of globals */
170 char *text, *end; // pointers to the user data in memory
171 int text_size; // size of the allocated buffer
172 char *dot; // where all the action takes place
173 #if ENABLE_FEATURE_VI_YANKMARK
174 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
175 int YDreg, Ureg; // default delete register and orig line for "U"
176 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
177 char *context_start, *context_end;
179 /* a few references only */
180 #if ENABLE_FEATURE_VI_USE_SIGNALS
181 jmp_buf restart; // catch_sig()
183 struct termios term_orig, term_vi; // remember what the cooked mode was
184 #if ENABLE_FEATURE_VI_COLON
185 char *initial_cmds[3]; // currently 2 entries, NULL terminated
188 #define G (*ptr_to_globals)
189 #define text (G.text )
190 #define text_size (G.text_size )
194 #define YDreg (G.YDreg )
195 #define Ureg (G.Ureg )
196 #define mark (G.mark )
197 #define context_start (G.context_start )
198 #define context_end (G.context_end )
199 #define restart (G.restart )
200 #define term_orig (G.term_orig )
201 #define term_vi (G.term_vi )
202 #define initial_cmds (G.initial_cmds )
204 static int init_text_buffer(char *); // init from file or create new
205 static void edit_file(char *); // edit one file
206 static void do_cmd(char); // execute a command
207 static int next_tabstop(int);
208 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
209 static char *begin_line(char *); // return pointer to cur line B-o-l
210 static char *end_line(char *); // return pointer to cur line E-o-l
211 static char *prev_line(char *); // return pointer to prev line B-o-l
212 static char *next_line(char *); // return pointer to next line B-o-l
213 static char *end_screen(void); // get pointer to last char on screen
214 static int count_lines(char *, char *); // count line from start to stop
215 static char *find_line(int); // find begining of line #li
216 static char *move_to_col(char *, int); // move "p" to column l
217 static void dot_left(void); // move dot left- dont leave line
218 static void dot_right(void); // move dot right- dont leave line
219 static void dot_begin(void); // move dot to B-o-l
220 static void dot_end(void); // move dot to E-o-l
221 static void dot_next(void); // move dot to next line B-o-l
222 static void dot_prev(void); // move dot to prev line B-o-l
223 static void dot_scroll(int, int); // move the screen up or down
224 static void dot_skip_over_ws(void); // move dot pat WS
225 static void dot_delete(void); // delete the char at 'dot'
226 static char *bound_dot(char *); // make sure text[0] <= P < "end"
227 static char *new_screen(int, int); // malloc virtual screen memory
228 static char *char_insert(char *, char); // insert the char c at 'p'
229 static char *stupid_insert(char *, char); // stupidly insert the char c at 'p'
230 static char find_range(char **, char **, char); // return pointers for an object
231 static int st_test(char *, int, int, char *); // helper for skip_thing()
232 static char *skip_thing(char *, int, int, int); // skip some object
233 static char *find_pair(char *, char); // find matching pair () [] {}
234 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
235 static char *text_hole_make(char *, int); // at "p", make a 'size' byte hole
236 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
237 static void show_help(void); // display some help info
238 static void rawmode(void); // set "raw" mode on tty
239 static void cookmode(void); // return to "cooked" mode on tty
240 static int mysleep(int); // sleep for 'h' 1/100 seconds
241 static char readit(void); // read (maybe cursor) key from stdin
242 static char get_one_char(void); // read 1 char from stdin
243 static int file_size(const char *); // what is the byte size of "fn"
244 #if ENABLE_FEATURE_VI_READONLY
245 static int file_insert(const char *, char *, int);
247 static int file_insert(const char *, char *);
249 static int file_write(char *, char *, char *);
250 static void place_cursor(int, int, int);
251 static void screen_erase(void);
252 static void clear_to_eol(void);
253 static void clear_to_eos(void);
254 static void standout_start(void); // send "start reverse video" sequence
255 static void standout_end(void); // send "end reverse video" sequence
256 static void flash(int); // flash the terminal screen
257 static void show_status_line(void); // put a message on the bottom line
258 static void psb(const char *, ...); // Print Status Buf
259 static void psbs(const char *, ...); // Print Status Buf in standout mode
260 static void ni(const char *); // display messages
261 static int format_edit_status(void); // format file status on status line
262 static void redraw(int); // force a full screen refresh
263 static void format_line(char*, char*, int);
264 static void refresh(int); // update the terminal from screen[]
266 static void Indicate_Error(void); // use flash or beep to indicate error
267 #define indicate_error(c) Indicate_Error()
268 static void Hit_Return(void);
270 #if ENABLE_FEATURE_VI_SEARCH
271 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
272 static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase"
274 #if ENABLE_FEATURE_VI_COLON
275 static char *get_one_address(char *, int *); // get colon addr, if present
276 static char *get_address(char *, int *, int *); // get two colon addrs, if present
277 static void colon(char *); // execute the "colon" mode cmds
279 #if ENABLE_FEATURE_VI_USE_SIGNALS
280 static void winch_sig(int); // catch window size changes
281 static void suspend_sig(int); // catch ctrl-Z
282 static void catch_sig(int); // catch ctrl-C and alarm time-outs
284 #if ENABLE_FEATURE_VI_DOT_CMD
285 static void start_new_cmd_q(char); // new queue for command
286 static void end_cmd_q(void); // stop saving input chars
288 #define end_cmd_q() ((void)0)
290 #if ENABLE_FEATURE_VI_SETOPTS
291 static void showmatching(char *); // show the matching pair () [] {}
293 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
294 static char *string_insert(char *, char *); // insert the string at 'p'
296 #if ENABLE_FEATURE_VI_YANKMARK
297 static char *text_yank(char *, char *, int); // save copy of "p" into a register
298 static char what_reg(void); // what is letter of current YDreg
299 static void check_context(char); // remember context for '' command
301 #if ENABLE_FEATURE_VI_CRASHME
302 static void crash_dummy();
303 static void crash_test();
304 static int crashme = 0;
308 static void write1(const char *out)
313 int vi_main(int argc, char **argv);
314 int vi_main(int argc, char **argv)
317 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
319 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
323 PTR_TO_GLOBALS = xzalloc(sizeof(G));
325 #if ENABLE_FEATURE_VI_CRASHME
326 srand((long) my_pid);
329 status_buffer = STATUS_BUFFER;
330 last_status_cksum = 0;
333 #ifdef NO_SUCH_APPLET_YET
334 /* If we aren't "vi", we are "view" */
335 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
336 SET_READONLY_MODE(readonly_mode);
340 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
341 #if ENABLE_FEATURE_VI_YANKMARK
342 memset(reg, 0, sizeof(reg)); // init the yank regs
344 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
345 modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
348 // 1- process $HOME/.exrc file (not inplemented yet)
349 // 2- process EXINIT variable from environment
350 // 3- process command line args
351 #if ENABLE_FEATURE_VI_COLON
353 char *p = getenv("EXINIT");
355 initial_cmds[0] = xstrdup(p);
358 while ((c = getopt(argc, argv, "hCR" USE_FEATURE_VI_COLON("c:"))) != -1) {
360 #if ENABLE_FEATURE_VI_CRASHME
365 #if ENABLE_FEATURE_VI_READONLY
366 case 'R': // Read-only flag
367 SET_READONLY_MODE(readonly_mode);
370 //case 'r': // recover flag- ignore- we don't use tmp file
371 //case 'x': // encryption flag- ignore
372 //case 'c': // execute command first
373 #if ENABLE_FEATURE_VI_COLON
374 case 'c': // cmd line vi command
376 initial_cmds[initial_cmds[0] != 0] = xstrdup(optarg);
378 //case 'h': // help -- just use default
386 // The argv array can be used by the ":next" and ":rewind" commands
388 fn_start = optind; // remember first file name for :next and :rew
391 //----- This is the main file handling loop --------------
392 if (optind >= argc) {
395 for (; optind < argc; optind++) {
396 edit_file(argv[optind]);
399 //-----------------------------------------------------------
404 /* read text from file or create an empty buf */
405 /* will also update current_filename */
406 static int init_text_buffer(char *fn)
409 int size = file_size(fn); // file size. -1 means does not exist.
411 /* allocate/reallocate text buffer */
413 text_size = size * 2;
414 if (text_size < 10240)
415 text_size = 10240; // have a minimum size for new files
416 screenbegin = dot = end = text = xzalloc(text_size);
418 if (fn != current_filename) {
419 free(current_filename);
420 current_filename = xstrdup(fn);
423 // file dont exist. Start empty buf with dummy line
424 char_insert(text, '\n');
427 rc = file_insert(fn, text
428 USE_FEATURE_VI_READONLY(, 1));
431 last_file_modified = -1;
432 #if ENABLE_FEATURE_VI_YANKMARK
433 /* init the marks. */
434 memset(mark, 0, sizeof(mark));
439 static void edit_file(char * fn)
444 #if ENABLE_FEATURE_VI_USE_SIGNALS
447 #if ENABLE_FEATURE_VI_YANKMARK
448 static char *cur_line;
451 editing = 1; // 0= exit, 1= one file, 2= multiple files
456 if (ENABLE_FEATURE_VI_WIN_RESIZE)
457 get_terminal_width_height(0, &columns, &rows);
458 new_screen(rows, columns); // get memory for virtual screen
459 init_text_buffer(fn);
461 #if ENABLE_FEATURE_VI_YANKMARK
462 YDreg = 26; // default Yank/Delete reg
463 Ureg = 27; // hold orig line for "U" cmd
464 mark[26] = mark[27] = text; // init "previous context"
467 last_forward_char = last_input_char = '\0';
471 #if ENABLE_FEATURE_VI_USE_SIGNALS
473 signal(SIGWINCH, winch_sig);
474 signal(SIGTSTP, suspend_sig);
475 sig = setjmp(restart);
477 screenbegin = dot = text;
481 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
484 offset = 0; // no horizontal offset
486 #if ENABLE_FEATURE_VI_DOT_CMD
487 free(last_modifying_cmd);
489 ioq = ioq_start = last_modifying_cmd = NULL;
492 redraw(FALSE); // dont force every col re-draw
494 #if ENABLE_FEATURE_VI_COLON
499 while ((p = initial_cmds[n])) {
509 free(initial_cmds[n]);
510 initial_cmds[n] = NULL;
515 //------This is the main Vi cmd handling loop -----------------------
516 while (editing > 0) {
517 #if ENABLE_FEATURE_VI_CRASHME
519 if ((end - text) > 1) {
520 crash_dummy(); // generate a random command
523 dot = string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
528 last_input_char = c = get_one_char(); // get a cmd from user
529 #if ENABLE_FEATURE_VI_YANKMARK
530 // save a copy of the current line- for the 'U" command
531 if (begin_line(dot) != cur_line) {
532 cur_line = begin_line(dot);
533 text_yank(begin_line(dot), end_line(dot), Ureg);
536 #if ENABLE_FEATURE_VI_DOT_CMD
537 // These are commands that change text[].
538 // Remember the input for the "." command
539 if (!adding2q && ioq_start == 0
540 && strchr(modifying_cmds, c)
545 do_cmd(c); // execute the user command
547 // poll to see if there is input already waiting. if we are
548 // not able to display output fast enough to keep up, skip
549 // the display update until we catch up with input.
550 if (mysleep(0) == 0) {
551 // no input pending- so update output
555 #if ENABLE_FEATURE_VI_CRASHME
557 crash_test(); // test editor variables
560 //-------------------------------------------------------------------
562 place_cursor(rows, 0, FALSE); // go to bottom of screen
563 clear_to_eol(); // Erase to end of line
567 //----- The Colon commands -------------------------------------
568 #if ENABLE_FEATURE_VI_COLON
569 static char *get_one_address(char * p, int *addr) // get colon addr, if present
574 #if ENABLE_FEATURE_VI_YANKMARK
577 #if ENABLE_FEATURE_VI_SEARCH
578 char *pat, buf[MAX_LINELEN];
581 *addr = -1; // assume no addr
582 if (*p == '.') { // the current line
585 *addr = count_lines(text, q);
586 #if ENABLE_FEATURE_VI_YANKMARK
587 } else if (*p == '\'') { // is this a mark addr
591 if (c >= 'a' && c <= 'z') {
594 q = mark[(unsigned char) c];
595 if (q != NULL) { // is mark valid
596 *addr = count_lines(text, q); // count lines
600 #if ENABLE_FEATURE_VI_SEARCH
601 } else if (*p == '/') { // a search pattern
609 pat = xstrdup(buf); // save copy of pattern
612 q = char_search(dot, pat, FORWARD, FULL);
614 *addr = count_lines(text, q);
618 } else if (*p == '$') { // the last line in file
620 q = begin_line(end - 1);
621 *addr = count_lines(text, q);
622 } else if (isdigit(*p)) { // specific line number
623 sscanf(p, "%d%n", addr, &st);
625 } else { // I don't reconise this
626 // unrecognised address- assume -1
632 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
634 //----- get the address' i.e., 1,3 'a,'b -----
635 // get FIRST addr, if present
637 p++; // skip over leading spaces
638 if (*p == '%') { // alias for 1,$
641 *e = count_lines(text, end-1);
644 p = get_one_address(p, b);
647 if (*p == ',') { // is there a address separator
651 // get SECOND addr, if present
652 p = get_one_address(p, e);
656 p++; // skip over trailing spaces
660 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
661 static void setops(const char *args, const char *opname, int flg_no,
662 const char *short_opname, int opt)
664 const char *a = args + flg_no;
665 int l = strlen(opname) - 1; /* opname have + ' ' */
667 if (strncasecmp(a, opname, l) == 0
668 || strncasecmp(a, short_opname, 2) == 0
678 static void colon(char * buf)
680 char c, *orig_buf, *buf1, *q, *r;
681 char *fn, cmd[MAX_LINELEN], args[MAX_LINELEN];
682 int i, l, li, ch, b, e;
683 int useforce = FALSE, forced = FALSE;
685 // :3154 // if (-e line 3154) goto it else stay put
686 // :4,33w! foo // write a portion of buffer to file "foo"
687 // :w // write all of buffer to current file
689 // :q! // quit- dont care about modified file
690 // :'a,'z!sort -u // filter block through sort
691 // :'f // goto mark "f"
692 // :'fl // list literal the mark "f" line
693 // :.r bar // read file "bar" into buffer before dot
694 // :/123/,/abc/d // delete lines from "123" line to "abc" line
695 // :/xyz/ // goto the "xyz" line
696 // :s/find/replace/ // substitute pattern "find" with "replace"
697 // :!<cmd> // run <cmd> then return
703 buf++; // move past the ':'
707 q = text; // assume 1,$ for the range
709 li = count_lines(text, end - 1);
710 fn = current_filename; // default to current file
711 memset(cmd, '\0', MAX_LINELEN); // clear cmd[]
712 memset(args, '\0', MAX_LINELEN); // clear args[]
714 // look for optional address(es) :. :1 :1,9 :'q,'a :%
715 buf = get_address(buf, &b, &e);
717 // remember orig command line
720 // get the COMMAND into cmd[]
722 while (*buf != '\0') {
728 while (isblank(*buf))
731 buf1 = last_char_is(cmd, '!');
734 *buf1 = '\0'; // get rid of !
737 // if there is only one addr, then the addr
738 // is the line number of the single line the
739 // user wants. So, reset the end
740 // pointer to point at end of the "b" line
741 q = find_line(b); // what line is #b
746 // we were given two addrs. change the
747 // end pointer to the addr given by user.
748 r = find_line(e); // what line is #e
752 // ------------ now look for the command ------------
754 if (i == 0) { // :123CR goto line #123
756 dot = find_line(b); // what line is #b
760 #if ENABLE_FEATURE_ALLOW_EXEC
761 else if (strncmp(cmd, "!", 1) == 0) { // run a cmd
763 // :!ls run the <cmd>
764 alarm(0); // wait for input- no alarms
765 place_cursor(rows - 1, 0, FALSE); // go to Status line
766 clear_to_eol(); // clear the line
768 retcode = system(orig_buf + 1); // run the cmd
770 printf("\nshell returned %i\n\n", retcode);
772 Hit_Return(); // let user see results
773 alarm(3); // done waiting for input
776 else if (strncmp(cmd, "=", i) == 0) { // where is the address
777 if (b < 0) { // no addr given- use defaults
778 b = e = count_lines(text, dot);
781 } else if (strncasecmp(cmd, "delete", i) == 0) { // delete lines
782 if (b < 0) { // no addr given- use defaults
783 q = begin_line(dot); // assume .,. for the range
786 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
788 } else if (strncasecmp(cmd, "edit", i) == 0) { // Edit a file
789 // don't edit, if the current file has been modified
790 if (file_modified && ! useforce) {
791 psbs("No write since last change (:edit! overrides)");
795 // the user supplied a file name
797 } else if (current_filename && current_filename[0]) {
798 // no user supplied name- use the current filename
799 // fn = current_filename; was set by default
801 // no user file name, no current name- punt
802 psbs("No current filename");
806 if (init_text_buffer(fn) < 0)
809 #if ENABLE_FEATURE_VI_YANKMARK
810 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
811 free(reg[Ureg]); // free orig line reg- for 'U'
814 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
815 free(reg[YDreg]); // free default yank/delete register
819 // how many lines in text[]?
820 li = count_lines(text, end - 1);
822 USE_FEATURE_VI_READONLY("%s")
823 " %dL, %dC", current_filename,
824 (file_size(fn) < 0 ? " [New file]" : ""),
825 USE_FEATURE_VI_READONLY(
826 ((readonly_mode) ? " [Readonly]" : ""),
829 } else if (strncasecmp(cmd, "file", i) == 0) { // what File is this
830 if (b != -1 || e != -1) {
831 ni("No address allowed on this command");
835 // user wants a new filename
836 free(current_filename);
837 current_filename = xstrdup(args);
839 // user wants file status info
840 last_status_cksum = 0; // force status update
842 } else if (strncasecmp(cmd, "features", i) == 0) { // what features are available
843 // print out values of all features
844 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
845 clear_to_eol(); // clear the line
850 } else if (strncasecmp(cmd, "list", i) == 0) { // literal print line
851 if (b < 0) { // no addr given- use defaults
852 q = begin_line(dot); // assume .,. for the range
855 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
856 clear_to_eol(); // clear the line
858 for (; q <= r; q++) {
862 c_is_no_print = (c & 0x80) && !Isprint(c);
869 } else if (c < ' ' || c == 127) {
880 #if ENABLE_FEATURE_VI_SET
884 } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
885 || strncasecmp(cmd, "next", i) == 0 // edit next file
888 // force end of argv list
895 // don't exit if the file been modified
897 psbs("No write since last change (:%s! overrides)",
898 (*cmd == 'q' ? "quit" : "next"));
901 // are there other file to edit
902 if (*cmd == 'q' && optind < save_argc - 1) {
903 psbs("%d more file to edit", (save_argc - optind - 1));
906 if (*cmd == 'n' && optind >= save_argc - 1) {
907 psbs("No more files to edit");
911 } else if (strncasecmp(cmd, "read", i) == 0) { // read file into text[]
914 psbs("No filename given");
917 if (b < 0) { // no addr given- use defaults
918 q = begin_line(dot); // assume "dot"
920 // read after current line- unless user said ":0r foo"
923 ch = file_insert(fn, q USE_FEATURE_VI_READONLY(, 0));
925 goto vc1; // nothing was inserted
926 // how many lines in text[]?
927 li = count_lines(q, q + ch - 1);
929 USE_FEATURE_VI_READONLY("%s")
931 USE_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
934 // if the insert is before "dot" then we need to update
939 } else if (strncasecmp(cmd, "rewind", i) == 0) { // rewind cmd line args
940 if (file_modified && ! useforce) {
941 psbs("No write since last change (:rewind! overrides)");
943 // reset the filenames to edit
944 optind = fn_start - 1;
947 #if ENABLE_FEATURE_VI_SET
948 } else if (strncasecmp(cmd, "set", i) == 0) { // set or clear features
949 #if ENABLE_FEATURE_VI_SETOPTS
952 i = 0; // offset into args
953 // only blank is regarded as args delmiter. What about tab '\t' ?
954 if (!args[0] || strcasecmp(args, "all") == 0) {
955 // print out values of all options
956 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
957 clear_to_eol(); // clear the line
958 printf("----------------------------------------\r\n");
959 #if ENABLE_FEATURE_VI_SETOPTS
962 printf("autoindent ");
968 printf("ignorecase ");
971 printf("showmatch ");
972 printf("tabstop=%d ", tabstop);
977 #if ENABLE_FEATURE_VI_SETOPTS
980 if (strncasecmp(argp, "no", 2) == 0)
981 i = 2; // ":set noautoindent"
982 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
983 setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
984 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
985 setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
987 if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
988 sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
989 if (ch > 0 && ch < columns - 1)
992 while (*argp && *argp != ' ')
993 argp++; // skip to arg delimiter (i.e. blank)
994 while (*argp && *argp == ' ')
995 argp++; // skip all delimiting blanks
997 #endif /* FEATURE_VI_SETOPTS */
998 #endif /* FEATURE_VI_SET */
999 #if ENABLE_FEATURE_VI_SEARCH
1000 } else if (strncasecmp(cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1004 // F points to the "find" pattern
1005 // R points to the "replace" pattern
1006 // replace the cmd line delimiters "/" with NULLs
1007 gflag = 0; // global replace flag
1008 c = orig_buf[1]; // what is the delimiter
1009 F = orig_buf + 2; // start of "find"
1010 R = strchr(F, c); // middle delimiter
1011 if (!R) goto colon_s_fail;
1012 *R++ = '\0'; // terminate "find"
1013 buf1 = strchr(R, c);
1014 if (!buf1) goto colon_s_fail;
1015 *buf1++ = '\0'; // terminate "replace"
1016 if (*buf1 == 'g') { // :s/foo/bar/g
1018 gflag++; // turn on gflag
1021 if (b < 0) { // maybe :s/foo/bar/
1022 q = begin_line(dot); // start with cur line
1023 b = count_lines(text, q); // cur line number
1026 e = b; // maybe :.s/foo/bar/
1027 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1028 ls = q; // orig line start
1030 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1032 // we found the "find" pattern - delete it
1033 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1034 // inset the "replace" patern
1035 string_insert(buf1, R); // insert the string
1036 // check for "global" :s/foo/bar/g
1038 if ((buf1 + strlen(R)) < end_line(ls)) {
1039 q = buf1 + strlen(R);
1040 goto vc4; // don't let q move past cur line
1046 #endif /* FEATURE_VI_SEARCH */
1047 } else if (strncasecmp(cmd, "version", i) == 0) { // show software version
1048 psb("%s", BB_VER " " BB_BT);
1049 } else if (strncasecmp(cmd, "write", i) == 0 // write text to file
1050 || strncasecmp(cmd, "wq", i) == 0
1051 || strncasecmp(cmd, "wn", i) == 0
1052 || strncasecmp(cmd, "x", i) == 0
1054 // is there a file name to write to?
1058 #if ENABLE_FEATURE_VI_READONLY
1059 if (readonly_mode && !useforce) {
1060 psbs("\"%s\" File is read only", fn);
1064 // how many lines in text[]?
1065 li = count_lines(q, r);
1067 // see if file exists- if not, its just a new file request
1069 // if "fn" is not write-able, chmod u+w
1070 // sprintf(syscmd, "chmod u+w %s", fn);
1074 l = file_write(fn, q, r);
1075 if (useforce && forced) {
1077 // sprintf(syscmd, "chmod u-w %s", fn);
1083 psbs("\"%s\" %s", fn, strerror(errno));
1085 psb("\"%s\" %dL, %dC", fn, li, l);
1086 if (q == text && r == end - 1 && l == ch) {
1088 last_file_modified = -1;
1090 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1091 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1096 #if ENABLE_FEATURE_VI_READONLY
1099 #if ENABLE_FEATURE_VI_YANKMARK
1100 } else if (strncasecmp(cmd, "yank", i) == 0) { // yank lines
1101 if (b < 0) { // no addr given- use defaults
1102 q = begin_line(dot); // assume .,. for the range
1105 text_yank(q, r, YDreg);
1106 li = count_lines(q, r);
1107 psb("Yank %d lines (%d chars) into [%c]",
1108 li, strlen(reg[YDreg]), what_reg());
1115 dot = bound_dot(dot); // make sure "dot" is valid
1117 #if ENABLE_FEATURE_VI_SEARCH
1119 psb(":s expression missing delimiters");
1123 #endif /* FEATURE_VI_COLON */
1125 static void Hit_Return(void)
1129 standout_start(); // start reverse video
1130 write1("[Hit return to continue]");
1131 standout_end(); // end reverse video
1132 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1134 redraw(TRUE); // force redraw all
1137 static int next_tabstop(int col)
1139 return col + ((tabstop - 1) - (col % tabstop));
1142 //----- Synchronize the cursor to Dot --------------------------
1143 static void sync_cursor(char * d, int *row, int *col)
1145 char *beg_cur; // begin and end of "d" line
1146 char *end_scr; // begin and end of screen
1150 beg_cur = begin_line(d); // first char of cur line
1152 end_scr = end_screen(); // last char of screen
1154 if (beg_cur < screenbegin) {
1155 // "d" is before top line on screen
1156 // how many lines do we have to move
1157 cnt = count_lines(beg_cur, screenbegin);
1159 screenbegin = beg_cur;
1160 if (cnt > (rows - 1) / 2) {
1161 // we moved too many lines. put "dot" in middle of screen
1162 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1163 screenbegin = prev_line(screenbegin);
1166 } else if (beg_cur > end_scr) {
1167 // "d" is after bottom line on screen
1168 // how many lines do we have to move
1169 cnt = count_lines(end_scr, beg_cur);
1170 if (cnt > (rows - 1) / 2)
1171 goto sc1; // too many lines
1172 for (ro = 0; ro < cnt - 1; ro++) {
1173 // move screen begin the same amount
1174 screenbegin = next_line(screenbegin);
1175 // now, move the end of screen
1176 end_scr = next_line(end_scr);
1177 end_scr = end_line(end_scr);
1180 // "d" is on screen- find out which row
1182 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1188 // find out what col "d" is on
1190 do { // drive "co" to correct column
1191 if (*tp == '\n' || *tp == '\0')
1194 if (d == tp && cmd_mode) { /* handle tabs like real vi */
1197 co = next_tabstop(co);
1199 } else if (*tp < ' ' || *tp == 127) {
1200 co++; // display as ^X, use 2 columns
1202 } while (tp++ < d && ++co);
1204 // "co" is the column where "dot" is.
1205 // The screen has "columns" columns.
1206 // The currently displayed columns are 0+offset -- columns+ofset
1207 // |-------------------------------------------------------------|
1209 // offset | |------- columns ----------------|
1211 // If "co" is already in this range then we do not have to adjust offset
1212 // but, we do have to subtract the "offset" bias from "co".
1213 // If "co" is outside this range then we have to change "offset".
1214 // If the first char of a line is a tab the cursor will try to stay
1215 // in column 7, but we have to set offset to 0.
1217 if (co < 0 + offset) {
1220 if (co >= columns + offset) {
1221 offset = co - columns + 1;
1223 // if the first char of the line is a tab, and "dot" is sitting on it
1224 // force offset to 0.
1225 if (d == beg_cur && *d == '\t') {
1234 //----- Text Movement Routines ---------------------------------
1235 static char *begin_line(char * p) // return pointer to first char cur line
1237 while (p > text && p[-1] != '\n')
1238 p--; // go to cur line B-o-l
1242 static char *end_line(char * p) // return pointer to NL of cur line line
1244 while (p < end - 1 && *p != '\n')
1245 p++; // go to cur line E-o-l
1249 static inline char *dollar_line(char * p) // return pointer to just before NL line
1251 while (p < end - 1 && *p != '\n')
1252 p++; // go to cur line E-o-l
1253 // Try to stay off of the Newline
1254 if (*p == '\n' && (p - begin_line(p)) > 0)
1259 static char *prev_line(char * p) // return pointer first char prev line
1261 p = begin_line(p); // goto begining of cur line
1262 if (p[-1] == '\n' && p > text)
1263 p--; // step to prev line
1264 p = begin_line(p); // goto begining of prev line
1268 static char *next_line(char * p) // return pointer first char next line
1271 if (*p == '\n' && p < end - 1)
1272 p++; // step to next line
1276 //----- Text Information Routines ------------------------------
1277 static char *end_screen(void)
1282 // find new bottom line
1284 for (cnt = 0; cnt < rows - 2; cnt++)
1290 static int count_lines(char * start, char * stop) // count line from start to stop
1295 if (stop < start) { // start and stop are backwards- reverse them
1301 stop = end_line(stop); // get to end of this line
1302 for (q = start; q <= stop && q <= end - 1; q++) {
1309 static char *find_line(int li) // find begining of line #li
1313 for (q = text; li > 1; li--) {
1319 //----- Dot Movement Routines ----------------------------------
1320 static void dot_left(void)
1322 if (dot > text && dot[-1] != '\n')
1326 static void dot_right(void)
1328 if (dot < end - 1 && *dot != '\n')
1332 static void dot_begin(void)
1334 dot = begin_line(dot); // return pointer to first char cur line
1337 static void dot_end(void)
1339 dot = end_line(dot); // return pointer to last char cur line
1342 static char *move_to_col(char * p, int l)
1349 if (*p == '\n' || *p == '\0')
1352 co = next_tabstop(co);
1353 } else if (*p < ' ' || *p == 127) {
1354 co++; // display as ^X, use 2 columns
1356 } while (++co <= l && p++ < end);
1360 static void dot_next(void)
1362 dot = next_line(dot);
1365 static void dot_prev(void)
1367 dot = prev_line(dot);
1370 static void dot_scroll(int cnt, int dir)
1374 for (; cnt > 0; cnt--) {
1377 // ctrl-Y scroll up one line
1378 screenbegin = prev_line(screenbegin);
1381 // ctrl-E scroll down one line
1382 screenbegin = next_line(screenbegin);
1385 // make sure "dot" stays on the screen so we dont scroll off
1386 if (dot < screenbegin)
1388 q = end_screen(); // find new bottom line
1390 dot = begin_line(q); // is dot is below bottom line?
1394 static void dot_skip_over_ws(void)
1397 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1401 static void dot_delete(void) // delete the char at 'dot'
1403 text_hole_delete(dot, dot);
1406 static char *bound_dot(char * p) // make sure text[0] <= P < "end"
1408 if (p >= end && end > text) {
1410 indicate_error('1');
1414 indicate_error('2');
1419 //----- Helper Utility Routines --------------------------------
1421 //----------------------------------------------------------------
1422 //----- Char Routines --------------------------------------------
1423 /* Chars that are part of a word-
1424 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1425 * Chars that are Not part of a word (stoppers)
1426 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1427 * Chars that are WhiteSpace
1428 * TAB NEWLINE VT FF RETURN SPACE
1429 * DO NOT COUNT NEWLINE AS WHITESPACE
1432 static char *new_screen(int ro, int co)
1437 screensize = ro * co + 8;
1438 screen = xmalloc(screensize);
1439 // initialize the new screen. assume this will be a empty file.
1441 // non-existent text[] lines start with a tilde (~).
1442 for (li = 1; li < ro - 1; li++) {
1443 screen[(li * co) + 0] = '~';
1448 #if ENABLE_FEATURE_VI_SEARCH
1449 static int mycmp(const char * s1, const char * s2, int len)
1453 i = strncmp(s1, s2, len);
1454 if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) {
1455 i = strncasecmp(s1, s2, len);
1460 // search for pattern starting at p
1461 static char *char_search(char * p, const char * pat, int dir, int range)
1463 #ifndef REGEX_SEARCH
1468 if (dir == FORWARD) {
1469 stop = end - 1; // assume range is p - end-1
1470 if (range == LIMITED)
1471 stop = next_line(p); // range is to next line
1472 for (start = p; start < stop; start++) {
1473 if (mycmp(start, pat, len) == 0) {
1477 } else if (dir == BACK) {
1478 stop = text; // assume range is text - p
1479 if (range == LIMITED)
1480 stop = prev_line(p); // range is to prev line
1481 for (start = p - len; start >= stop; start--) {
1482 if (mycmp(start, pat, len) == 0) {
1487 // pattern not found
1489 #else /* REGEX_SEARCH */
1491 struct re_pattern_buffer preg;
1495 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1501 // assume a LIMITED forward search
1509 // count the number of chars to search over, forward or backward
1513 // RANGE could be negative if we are searching backwards
1516 q = re_compile_pattern(pat, strlen(pat), &preg);
1518 // The pattern was not compiled
1519 psbs("bad search pattern: \"%s\": %s", pat, q);
1520 i = 0; // return p if pattern not compiled
1530 // search for the compiled pattern, preg, in p[]
1531 // range < 0- search backward
1532 // range > 0- search forward
1534 // re_search() < 0 not found or error
1535 // re_search() > 0 index of found pattern
1536 // struct pattern char int int int struct reg
1537 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1538 i = re_search(&preg, q, size, 0, range, 0);
1541 i = 0; // return NULL if pattern not found
1544 if (dir == FORWARD) {
1550 #endif /* REGEX_SEARCH */
1552 #endif /* FEATURE_VI_SEARCH */
1554 static char *char_insert(char * p, char c) // insert the char c at 'p'
1556 if (c == 22) { // Is this an ctrl-V?
1557 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1558 p--; // backup onto ^
1559 refresh(FALSE); // show the ^
1563 file_modified++; // has the file been modified
1564 } else if (c == 27) { // Is this an ESC?
1567 end_cmd_q(); // stop adding to q
1568 last_status_cksum = 0; // force status update
1569 if ((p[-1] != '\n') && (dot > text)) {
1572 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1574 if ((p[-1] != '\n') && (dot>text)) {
1576 p = text_hole_delete(p, p); // shrink buffer 1 char
1579 // insert a char into text[]
1580 char *sp; // "save p"
1583 c = '\n'; // translate \r to \n
1584 sp = p; // remember addr of insert
1585 p = stupid_insert(p, c); // insert the char
1586 #if ENABLE_FEATURE_VI_SETOPTS
1587 if (showmatch && strchr(")]}", *sp) != NULL) {
1590 if (autoindent && c == '\n') { // auto indent the new line
1593 q = prev_line(p); // use prev line as templet
1594 for (; isblank(*q); q++) {
1595 p = stupid_insert(p, *q); // insert the char
1603 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1605 p = text_hole_make(p, 1);
1608 file_modified++; // has the file been modified
1614 static char find_range(char ** start, char ** stop, char c)
1616 char *save_dot, *p, *q;
1622 if (strchr("cdy><", c)) {
1623 // these cmds operate on whole lines
1624 p = q = begin_line(p);
1625 for (cnt = 1; cnt < cmdcnt; cnt++) {
1629 } else if (strchr("^%$0bBeEft", c)) {
1630 // These cmds operate on char positions
1631 do_cmd(c); // execute movement cmd
1633 } else if (strchr("wW", c)) {
1634 do_cmd(c); // execute movement cmd
1635 // if we are at the next word's first char
1636 // step back one char
1637 // but check the possibilities when it is true
1638 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1639 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1640 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1641 dot--; // move back off of next word
1642 if (dot > text && *dot == '\n')
1643 dot--; // stay off NL
1645 } else if (strchr("H-k{", c)) {
1646 // these operate on multi-lines backwards
1647 q = end_line(dot); // find NL
1648 do_cmd(c); // execute movement cmd
1651 } else if (strchr("L+j}\r\n", c)) {
1652 // these operate on multi-lines forwards
1653 p = begin_line(dot);
1654 do_cmd(c); // execute movement cmd
1655 dot_end(); // find NL
1658 c = 27; // error- return an ESC char
1671 static int st_test(char * p, int type, int dir, char * tested)
1681 if (type == S_BEFORE_WS) {
1683 test = ((!isspace(c)) || c == '\n');
1685 if (type == S_TO_WS) {
1687 test = ((!isspace(c)) || c == '\n');
1689 if (type == S_OVER_WS) {
1691 test = ((isspace(c)));
1693 if (type == S_END_PUNCT) {
1695 test = ((ispunct(c)));
1697 if (type == S_END_ALNUM) {
1699 test = ((isalnum(c)) || c == '_');
1705 static char *skip_thing(char * p, int linecnt, int dir, int type)
1709 while (st_test(p, type, dir, &c)) {
1710 // make sure we limit search to correct number of lines
1711 if (c == '\n' && --linecnt < 1)
1713 if (dir >= 0 && p >= end - 1)
1715 if (dir < 0 && p <= text)
1717 p += dir; // move to next char
1722 // find matching char of pair () [] {}
1723 static char *find_pair(char * p, char c)
1730 dir = 1; // assume forward
1754 for (q = p + dir; text <= q && q < end; q += dir) {
1755 // look for match, count levels of pairs (( ))
1757 level++; // increase pair levels
1759 level--; // reduce pair level
1761 break; // found matching pair
1764 q = NULL; // indicate no match
1768 #if ENABLE_FEATURE_VI_SETOPTS
1769 // show the matching char of a pair, () [] {}
1770 static void showmatching(char * p)
1774 // we found half of a pair
1775 q = find_pair(p, *p); // get loc of matching char
1777 indicate_error('3'); // no matching char
1779 // "q" now points to matching pair
1780 save_dot = dot; // remember where we are
1781 dot = q; // go to new loc
1782 refresh(FALSE); // let the user see it
1783 mysleep(40); // give user some time
1784 dot = save_dot; // go back to old loc
1788 #endif /* FEATURE_VI_SETOPTS */
1790 // open a hole in text[]
1791 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1800 cnt = end - src; // the rest of buffer
1801 if ( ((end + size) >= (text + text_size)) // TODO: realloc here
1802 || memmove(dest, src, cnt) != dest) {
1803 psbs("can't create room for new characters");
1807 memset(p, ' ', size); // clear new hole
1808 end += size; // adjust the new END
1809 file_modified++; // has the file been modified
1814 // close a hole in text[]
1815 static char *text_hole_delete(char * p, char * q) // delete "p" thru "q", inclusive
1820 // move forwards, from beginning
1824 if (q < p) { // they are backward- swap them
1828 hole_size = q - p + 1;
1830 if (src < text || src > end)
1832 if (dest < text || dest >= end)
1835 goto thd_atend; // just delete the end of the buffer
1836 if (memmove(dest, src, cnt) != dest) {
1837 psbs("can't delete the character");
1840 end = end - hole_size; // adjust the new END
1842 dest = end - 1; // make sure dest in below end-1
1844 dest = end = text; // keep pointers valid
1845 file_modified++; // has the file been modified
1850 // copy text into register, then delete text.
1851 // if dist <= 0, do not include, or go past, a NewLine
1853 static char *yank_delete(char * start, char * stop, int dist, int yf)
1857 // make sure start <= stop
1859 // they are backwards, reverse them
1865 // we cannot cross NL boundaries
1869 // dont go past a NewLine
1870 for (; p + 1 <= stop; p++) {
1872 stop = p; // "stop" just before NewLine
1878 #if ENABLE_FEATURE_VI_YANKMARK
1879 text_yank(start, stop, YDreg);
1881 if (yf == YANKDEL) {
1882 p = text_hole_delete(start, stop);
1887 static void show_help(void)
1889 puts("These features are available:"
1890 #if ENABLE_FEATURE_VI_SEARCH
1891 "\n\tPattern searches with / and ?"
1893 #if ENABLE_FEATURE_VI_DOT_CMD
1894 "\n\tLast command repeat with \'.\'"
1896 #if ENABLE_FEATURE_VI_YANKMARK
1897 "\n\tLine marking with 'x"
1898 "\n\tNamed buffers with \"x"
1900 #if ENABLE_FEATURE_VI_READONLY
1901 "\n\tReadonly if vi is called as \"view\""
1902 "\n\tReadonly with -R command line arg"
1904 #if ENABLE_FEATURE_VI_SET
1905 "\n\tSome colon mode commands with \':\'"
1907 #if ENABLE_FEATURE_VI_SETOPTS
1908 "\n\tSettable options with \":set\""
1910 #if ENABLE_FEATURE_VI_USE_SIGNALS
1911 "\n\tSignal catching- ^C"
1912 "\n\tJob suspend and resume with ^Z"
1914 #if ENABLE_FEATURE_VI_WIN_RESIZE
1915 "\n\tAdapt to window re-sizes"
1920 static inline void print_literal(char * buf, const char * s) // copy s to buf, convert unprintable
1933 c_is_no_print = (c & 0x80) && !Isprint(c);
1934 if (c_is_no_print) {
1938 if (c < ' ' || c == 127) {
1954 #if ENABLE_FEATURE_VI_DOT_CMD
1955 static void start_new_cmd_q(char c)
1958 free(last_modifying_cmd);
1959 // get buffer for new cmd
1960 last_modifying_cmd = xzalloc(MAX_LINELEN);
1961 // if there is a current cmd count put it in the buffer first
1963 sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1964 else // just save char c onto queue
1965 last_modifying_cmd[0] = c;
1969 static void end_cmd_q(void)
1971 #if ENABLE_FEATURE_VI_YANKMARK
1972 YDreg = 26; // go back to default Yank/Delete reg
1976 #endif /* FEATURE_VI_DOT_CMD */
1978 #if ENABLE_FEATURE_VI_YANKMARK \
1979 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1980 || ENABLE_FEATURE_VI_CRASHME
1981 static char *string_insert(char * p, char * s) // insert the string at 'p'
1986 if (text_hole_make(p, i)) {
1988 for (cnt = 0; *s != '\0'; s++) {
1992 #if ENABLE_FEATURE_VI_YANKMARK
1993 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2000 #if ENABLE_FEATURE_VI_YANKMARK
2001 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2006 if (q < p) { // they are backwards- reverse them
2013 free(t); // if already a yank register, free it
2014 t = xmalloc(cnt + 1); // get a new register
2015 memset(t, '\0', cnt + 1); // clear new text[]
2016 strncpy(t, p, cnt); // copy text[] into bufer
2021 static char what_reg(void)
2025 c = 'D'; // default to D-reg
2026 if (0 <= YDreg && YDreg <= 25)
2027 c = 'a' + (char) YDreg;
2035 static void check_context(char cmd)
2037 // A context is defined to be "modifying text"
2038 // Any modifying command establishes a new context.
2040 if (dot < context_start || dot > context_end) {
2041 if (strchr(modifying_cmds, cmd) != NULL) {
2042 // we are trying to modify text[]- make this the current context
2043 mark[27] = mark[26]; // move cur to prev
2044 mark[26] = dot; // move local to cur
2045 context_start = prev_line(prev_line(dot));
2046 context_end = next_line(next_line(dot));
2047 //loiter= start_loiter= now;
2052 static inline char *swap_context(char * p) // goto new context for '' command make this the current context
2056 // the current context is in mark[26]
2057 // the previous context is in mark[27]
2058 // only swap context if other context is valid
2059 if (text <= mark[27] && mark[27] <= end - 1) {
2061 mark[27] = mark[26];
2063 p = mark[26]; // where we are going- previous context
2064 context_start = prev_line(prev_line(prev_line(p)));
2065 context_end = next_line(next_line(next_line(p)));
2069 #endif /* FEATURE_VI_YANKMARK */
2071 //----- Set terminal attributes --------------------------------
2072 static void rawmode(void)
2074 tcgetattr(0, &term_orig);
2075 term_vi = term_orig;
2076 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2077 term_vi.c_iflag &= (~IXON & ~ICRNL);
2078 term_vi.c_oflag &= (~ONLCR);
2079 term_vi.c_cc[VMIN] = 1;
2080 term_vi.c_cc[VTIME] = 0;
2081 erase_char = term_vi.c_cc[VERASE];
2082 tcsetattr(0, TCSANOW, &term_vi);
2085 static void cookmode(void)
2088 tcsetattr(0, TCSANOW, &term_orig);
2091 //----- Come here when we get a window resize signal ---------
2092 #if ENABLE_FEATURE_VI_USE_SIGNALS
2093 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2095 signal(SIGWINCH, winch_sig);
2096 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2097 get_terminal_width_height(0, &columns, &rows);
2098 new_screen(rows, columns); // get memory for virtual screen
2099 redraw(TRUE); // re-draw the screen
2102 //----- Come here when we get a continue signal -------------------
2103 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2105 rawmode(); // terminal to "raw"
2106 last_status_cksum = 0; // force status update
2107 redraw(TRUE); // re-draw the screen
2109 signal(SIGTSTP, suspend_sig);
2110 signal(SIGCONT, SIG_DFL);
2111 kill(my_pid, SIGCONT);
2114 //----- Come here when we get a Suspend signal -------------------
2115 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2117 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2118 clear_to_eol(); // Erase to end of line
2119 cookmode(); // terminal to "cooked"
2121 signal(SIGCONT, cont_sig);
2122 signal(SIGTSTP, SIG_DFL);
2123 kill(my_pid, SIGTSTP);
2126 //----- Come here when we get a signal ---------------------------
2127 static void catch_sig(int sig)
2129 signal(SIGINT, catch_sig);
2131 longjmp(restart, sig);
2133 #endif /* FEATURE_VI_USE_SIGNALS */
2135 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2140 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2145 tv.tv_usec = hund * 10000;
2146 select(1, &rfds, NULL, NULL, &tv);
2147 return FD_ISSET(0, &rfds);
2150 #define readbuffer bb_common_bufsiz1
2152 static int readed_for_parse;
2154 //----- IO Routines --------------------------------------------
2155 static char readit(void) // read (maybe cursor) key from stdin
2164 static const struct esc_cmds esccmds[] = {
2165 {"OA", VI_K_UP}, // cursor key Up
2166 {"OB", VI_K_DOWN}, // cursor key Down
2167 {"OC", VI_K_RIGHT}, // Cursor Key Right
2168 {"OD", VI_K_LEFT}, // cursor key Left
2169 {"OH", VI_K_HOME}, // Cursor Key Home
2170 {"OF", VI_K_END}, // Cursor Key End
2171 {"[A", VI_K_UP}, // cursor key Up
2172 {"[B", VI_K_DOWN}, // cursor key Down
2173 {"[C", VI_K_RIGHT}, // Cursor Key Right
2174 {"[D", VI_K_LEFT}, // cursor key Left
2175 {"[H", VI_K_HOME}, // Cursor Key Home
2176 {"[F", VI_K_END}, // Cursor Key End
2177 {"[1~", VI_K_HOME}, // Cursor Key Home
2178 {"[2~", VI_K_INSERT}, // Cursor Key Insert
2179 {"[4~", VI_K_END}, // Cursor Key End
2180 {"[5~", VI_K_PAGEUP}, // Cursor Key Page Up
2181 {"[6~", VI_K_PAGEDOWN},// Cursor Key Page Down
2182 {"OP", VI_K_FUN1}, // Function Key F1
2183 {"OQ", VI_K_FUN2}, // Function Key F2
2184 {"OR", VI_K_FUN3}, // Function Key F3
2185 {"OS", VI_K_FUN4}, // Function Key F4
2186 {"[15~", VI_K_FUN5}, // Function Key F5
2187 {"[17~", VI_K_FUN6}, // Function Key F6
2188 {"[18~", VI_K_FUN7}, // Function Key F7
2189 {"[19~", VI_K_FUN8}, // Function Key F8
2190 {"[20~", VI_K_FUN9}, // Function Key F9
2191 {"[21~", VI_K_FUN10}, // Function Key F10
2192 {"[23~", VI_K_FUN11}, // Function Key F11
2193 {"[24~", VI_K_FUN12}, // Function Key F12
2194 {"[11~", VI_K_FUN1}, // Function Key F1
2195 {"[12~", VI_K_FUN2}, // Function Key F2
2196 {"[13~", VI_K_FUN3}, // Function Key F3
2197 {"[14~", VI_K_FUN4}, // Function Key F4
2199 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2201 alarm(0); // turn alarm OFF while we wait for input
2203 n = readed_for_parse;
2204 // get input from User- are there already input chars in Q?
2207 // the Q is empty, wait for a typed char
2208 n = read(0, readbuffer, MAX_LINELEN - 1);
2211 goto ri0; // interrupted sys call
2212 if (errno == EBADF || errno == EFAULT || errno == EINVAL
2219 if (readbuffer[0] == 27) {
2223 // This is an ESC char. Is this Esc sequence?
2224 // Could be bare Esc key. See if there are any
2225 // more chars to read after the ESC. This would
2226 // be a Function or Cursor Key sequence.
2230 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2232 // keep reading while there are input chars and room in buffer
2233 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (MAX_LINELEN - 5)) {
2234 // read the rest of the ESC string
2235 int r = read(0, (void *) (readbuffer + n), MAX_LINELEN - n);
2241 readed_for_parse = n;
2244 if (c == 27 && n > 1) {
2245 // Maybe cursor or function key?
2246 const struct esc_cmds *eindex;
2248 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2249 int cnt = strlen(eindex->seq);
2253 if (strncmp(eindex->seq, readbuffer + 1, cnt))
2255 // is a Cursor key- put derived value back into Q
2257 // for squeeze out the ESC sequence
2261 if (eindex == &esccmds[ESCCMDS_COUNT]) {
2262 /* defined ESC sequence not found, set only one ESC */
2268 // remove key sequence from Q
2269 readed_for_parse -= n;
2270 memmove(readbuffer, readbuffer + n, MAX_LINELEN - n);
2271 alarm(3); // we are done waiting for input, turn alarm ON
2275 //----- IO Routines --------------------------------------------
2276 static char get_one_char(void)
2280 #if ENABLE_FEATURE_VI_DOT_CMD
2281 // ! adding2q && ioq == 0 read()
2282 // ! adding2q && ioq != 0 *ioq
2283 // adding2q *last_modifying_cmd= read()
2285 // we are not adding to the q.
2286 // but, we may be reading from a q
2288 // there is no current q, read from STDIN
2289 c = readit(); // get the users input
2291 // there is a queue to get chars from first
2294 // the end of the q, read from STDIN
2296 ioq_start = ioq = 0;
2297 c = readit(); // get the users input
2301 // adding STDIN chars to q
2302 c = readit(); // get the users input
2303 if (last_modifying_cmd != 0) {
2304 int len = strlen(last_modifying_cmd);
2305 if (len >= MAX_LINELEN - 1) {
2306 psbs("last_modifying_cmd overrun");
2308 // add new char to q
2309 last_modifying_cmd[len] = c;
2314 c = readit(); // get the users input
2315 #endif /* FEATURE_VI_DOT_CMD */
2316 return c; // return the char, where ever it came from
2319 static char *get_input_line(const char * prompt) // get input line- use "status line"
2323 char buf[MAX_LINELEN];
2327 strcpy(buf, prompt);
2328 last_status_cksum = 0; // force status update
2329 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2330 clear_to_eol(); // clear the line
2331 write1(prompt); // write out the :, /, or ? prompt
2334 while (i < MAX_LINELEN) {
2335 c = get_one_char(); // read user input
2336 if (c == '\n' || c == '\r' || c == 27)
2337 break; // is this end of input
2338 if (c == erase_char || c == 8 || c == 127) {
2339 // user wants to erase prev char
2340 i--; // backup to prev char
2341 buf[i] = '\0'; // erase the char
2342 buf[i + 1] = '\0'; // null terminate buffer
2343 write1("\b \b"); // erase char on screen
2344 if (i <= 0) { // user backs up before b-o-l, exit
2348 buf[i] = c; // save char in buffer
2349 buf[i + 1] = '\0'; // make sure buffer is null terminated
2350 putchar(c); // echo the char back to user
2356 obufp = xstrdup(buf);
2360 static int file_size(const char *fn) // what is the byte size of "fn"
2366 if (fn && fn[0] && stat(fn, &st_buf) == 0) // see if file exists
2367 cnt = (int) st_buf.st_size;
2371 static int file_insert(const char * fn, char *p
2372 USE_FEATURE_VI_READONLY(, int update_ro_status))
2376 struct stat statbuf;
2379 if (stat(fn, &statbuf) < 0) {
2380 psbs("\"%s\" %s", fn, strerror(errno));
2383 if ((statbuf.st_mode & S_IFREG) == 0) {
2384 // This is not a regular file
2385 psbs("\"%s\" Not a regular file", fn);
2388 /* // this check is done by open()
2389 if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2390 // dont have any read permissions
2391 psbs("\"%s\" Not readable", fn);
2395 if (p < text || p > end) {
2396 psbs("Trying to insert file outside of memory");
2400 // read file to buffer
2401 fd = open(fn, O_RDONLY);
2403 psbs("\"%s\" %s", fn, strerror(errno));
2406 size = statbuf.st_size;
2407 p = text_hole_make(p, size);
2410 cnt = read(fd, p, size);
2412 psbs("\"%s\" %s", fn, strerror(errno));
2413 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2414 } else if (cnt < size) {
2415 // There was a partial read, shrink unused space text[]
2416 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2417 psbs("cannot read all of file \"%s\"", fn);
2423 #if ENABLE_FEATURE_VI_READONLY
2424 if (update_ro_status
2425 && ((access(fn, W_OK) < 0) ||
2426 /* root will always have access()
2427 * so we check fileperms too */
2428 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2431 SET_READONLY_FILE(readonly_mode);
2438 static int file_write(char * fn, char * first, char * last)
2440 int fd, cnt, charcnt;
2443 psbs("No current filename");
2447 // FIXIT- use the correct umask()
2448 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2451 cnt = last - first + 1;
2452 charcnt = write(fd, first, cnt);
2453 if (charcnt == cnt) {
2455 //file_modified = FALSE; // the file has not been modified
2463 //----- Terminal Drawing ---------------------------------------
2464 // The terminal is made up of 'rows' line of 'columns' columns.
2465 // classically this would be 24 x 80.
2466 // screen coordinates
2472 // 23,0 ... 23,79 status line
2475 //----- Move the cursor to row x col (count from 0, not 1) -------
2476 static void place_cursor(int row, int col, int opti)
2478 char cm1[MAX_LINELEN];
2480 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2481 char cm2[MAX_LINELEN];
2483 // char cm3[MAX_LINELEN];
2484 int Rrow = last_row;
2487 memset(cm1, '\0', MAX_LINELEN); // clear the buffer
2489 if (row < 0) row = 0;
2490 if (row >= rows) row = rows - 1;
2491 if (col < 0) col = 0;
2492 if (col >= columns) col = columns - 1;
2494 //----- 1. Try the standard terminal ESC sequence
2495 sprintf(cm1, CMrc, row + 1, col + 1);
2500 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2501 //----- find the minimum # of chars to move cursor -------------
2502 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2503 memset(cm2, '\0', MAX_LINELEN); // clear the buffer
2505 // move to the correct row
2506 while (row < Rrow) {
2507 // the cursor has to move up
2511 while (row > Rrow) {
2512 // the cursor has to move down
2513 strcat(cm2, CMdown);
2517 // now move to the correct column
2518 strcat(cm2, "\r"); // start at col 0
2519 // just send out orignal source char to get to correct place
2520 screenp = &screen[row * columns]; // start of screen line
2521 strncat(cm2, screenp, col);
2523 //----- 3. Try some other way of moving cursor
2524 //---------------------------------------------
2526 // pick the shortest cursor motion to send out
2528 if (strlen(cm2) < strlen(cm)) {
2530 } /* else if (strlen(cm3) < strlen(cm)) {
2533 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2535 write1(cm); // move the cursor
2538 //----- Erase from cursor to end of line -----------------------
2539 static void clear_to_eol(void)
2541 write1(Ceol); // Erase from cursor to end of line
2544 //----- Erase from cursor to end of screen -----------------------
2545 static void clear_to_eos(void)
2547 write1(Ceos); // Erase from cursor to end of screen
2550 //----- Start standout mode ------------------------------------
2551 static void standout_start(void) // send "start reverse video" sequence
2553 write1(SOs); // Start reverse video mode
2556 //----- End standout mode --------------------------------------
2557 static void standout_end(void) // send "end reverse video" sequence
2559 write1(SOn); // End reverse video mode
2562 //----- Flash the screen --------------------------------------
2563 static void flash(int h)
2565 standout_start(); // send "start reverse video" sequence
2568 standout_end(); // send "end reverse video" sequence
2572 static void Indicate_Error(void)
2574 #if ENABLE_FEATURE_VI_CRASHME
2576 return; // generate a random command
2579 write1(bell); // send out a bell character
2585 //----- Screen[] Routines --------------------------------------
2586 //----- Erase the Screen[] memory ------------------------------
2587 static void screen_erase(void)
2589 memset(screen, ' ', screensize); // clear new screen
2592 static int bufsum(char *buf, int count)
2595 char *e = buf + count;
2598 sum += (unsigned char) *buf++;
2602 //----- Draw the status line at bottom of the screen -------------
2603 static void show_status_line(void)
2605 int cnt = 0, cksum = 0;
2607 // either we already have an error or status message, or we
2609 if (!have_status_msg) {
2610 cnt = format_edit_status();
2611 cksum = bufsum(status_buffer, cnt);
2613 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2614 last_status_cksum= cksum; // remember if we have seen this line
2615 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2616 write1(status_buffer);
2618 if (have_status_msg) {
2619 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2621 have_status_msg = 0;
2624 have_status_msg = 0;
2626 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2631 //----- format the status buffer, the bottom line of screen ------
2632 // format status buffer, with STANDOUT mode
2633 static void psbs(const char *format, ...)
2637 va_start(args, format);
2638 strcpy(status_buffer, SOs); // Terminal standout mode on
2639 vsprintf(status_buffer + strlen(status_buffer), format, args);
2640 strcat(status_buffer, SOn); // Terminal standout mode off
2643 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2646 // format status buffer
2647 static void psb(const char *format, ...)
2651 va_start(args, format);
2652 vsprintf(status_buffer, format, args);
2655 have_status_msg = 1;
2658 static void ni(const char * s) // display messages
2660 char buf[MAX_LINELEN];
2662 print_literal(buf, s);
2663 psbs("\'%s\' is not implemented", buf);
2666 static int format_edit_status(void) // show file status on status line
2669 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2670 int cur, percent, ret, trunc_at;
2672 // file_modified is now a counter rather than a flag. this
2673 // helps reduce the amount of line counting we need to do.
2674 // (this will cause a mis-reporting of modified status
2675 // once every MAXINT editing operations.)
2677 // it would be nice to do a similar optimization here -- if
2678 // we haven't done a motion that could have changed which line
2679 // we're on, then we shouldn't have to do this count_lines()
2680 cur = count_lines(text, dot);
2682 // reduce counting -- the total lines can't have
2683 // changed if we haven't done any edits.
2684 if (file_modified != last_file_modified) {
2685 tot = cur + count_lines(dot, end - 1) - 1;
2686 last_file_modified = file_modified;
2689 // current line percent
2690 // ------------- ~~ ----------
2693 percent = (100 * cur) / tot;
2699 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2700 columns : STATUS_BUFFER_LEN-1;
2702 ret = snprintf(status_buffer, trunc_at+1,
2703 #if ENABLE_FEATURE_VI_READONLY
2704 "%c %s%s%s %d/%d %d%%",
2706 "%c %s%s %d/%d %d%%",
2708 cmd_mode_indicator[cmd_mode & 3],
2709 (current_filename != NULL ? current_filename : "No file"),
2710 #if ENABLE_FEATURE_VI_READONLY
2711 (readonly_mode ? " [Readonly]" : ""),
2713 (file_modified ? " [Modified]" : ""),
2716 if (ret >= 0 && ret < trunc_at)
2717 return ret; /* it all fit */
2719 return trunc_at; /* had to truncate */
2722 //----- Force refresh of all Lines -----------------------------
2723 static void redraw(int full_screen)
2725 place_cursor(0, 0, FALSE); // put cursor in correct place
2726 clear_to_eos(); // tel terminal to erase display
2727 screen_erase(); // erase the internal screen buffer
2728 last_status_cksum = 0; // force status update
2729 refresh(full_screen); // this will redraw the entire display
2733 //----- Format a text[] line into a buffer ---------------------
2734 static void format_line(char *dest, char *src, int li)
2739 for (co = 0; co < MAX_SCR_COLS; co++) {
2740 c = ' '; // assume blank
2741 if (li > 0 && co == 0) {
2742 c = '~'; // not first line, assume Tilde
2744 // are there chars in text[] and have we gone past the end
2745 if (text < end && src < end) {
2750 if ((c & 0x80) && !Isprint(c)) {
2753 if ((unsigned char)(c) < ' ' || c == 0x7f) {
2757 for (; (co % tabstop) != (tabstop - 1); co++) {
2765 c += '@'; // make it visible
2768 // the co++ is done here so that the column will
2769 // not be overwritten when we blank-out the rest of line
2776 //----- Refresh the changed screen lines -----------------------
2777 // Copy the source line from text[] into the buffer and note
2778 // if the current screenline is different from the new buffer.
2779 // If they differ then that line needs redrawing on the terminal.
2781 static void refresh(int full_screen)
2783 static int old_offset;
2786 char buf[MAX_SCR_COLS];
2787 char *tp, *sp; // pointer into text[] and screen[]
2788 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2789 int last_li = -2; // last line that changed- for optimizing cursor movement
2792 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2793 get_terminal_width_height(0, &columns, &rows);
2794 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2795 tp = screenbegin; // index into text[] of top line
2797 // compare text[] to screen[] and mark screen[] lines that need updating
2798 for (li = 0; li < rows - 1; li++) {
2799 int cs, ce; // column start & end
2800 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2801 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2802 // format current text line into buf
2803 format_line(buf, tp, li);
2805 // skip to the end of the current text[] line
2806 while (tp < end && *tp++ != '\n') /*no-op*/;
2808 // see if there are any changes between vitual screen and buf
2809 changed = FALSE; // assume no change
2812 sp = &screen[li * columns]; // start of screen line
2814 // force re-draw of every single column from 0 - columns-1
2817 // compare newly formatted buffer with virtual screen
2818 // look forward for first difference between buf and screen
2819 for (; cs <= ce; cs++) {
2820 if (buf[cs + offset] != sp[cs]) {
2821 changed = TRUE; // mark for redraw
2826 // look backward for last difference between buf and screen
2827 for ( ; ce >= cs; ce--) {
2828 if (buf[ce + offset] != sp[ce]) {
2829 changed = TRUE; // mark for redraw
2833 // now, cs is index of first diff, and ce is index of last diff
2835 // if horz offset has changed, force a redraw
2836 if (offset != old_offset) {
2841 // make a sanity check of columns indexes
2843 if (ce > columns-1) ce= columns-1;
2844 if (cs > ce) { cs= 0; ce= columns-1; }
2845 // is there a change between vitual screen and buf
2847 // copy changed part of buffer to virtual screen
2848 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2850 // move cursor to column of first change
2851 if (offset != old_offset) {
2852 // opti_cur_move is still too stupid
2853 // to handle offsets correctly
2854 place_cursor(li, cs, FALSE);
2856 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2857 // if this just the next line
2858 // try to optimize cursor movement
2859 // otherwise, use standard ESC sequence
2860 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2863 place_cursor(li, cs, FALSE); // use standard ESC sequence
2864 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2867 // write line out to terminal
2869 int nic = ce - cs + 1;
2870 char *out = sp + cs;
2877 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2883 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2884 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2887 place_cursor(crow, ccol, FALSE);
2890 if (offset != old_offset)
2891 old_offset = offset;
2894 //---------------------------------------------------------------------
2895 //----- the Ascii Chart -----------------------------------------------
2897 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2898 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2899 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2900 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2901 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2902 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2903 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2904 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2905 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2906 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2907 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2908 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2909 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2910 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2911 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2912 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2913 //---------------------------------------------------------------------
2915 //----- Execute a Vi Command -----------------------------------
2916 static void do_cmd(char c)
2919 char c1, *p, *q, buf[9], *save_dot;
2920 int cnt, i, j, dir, yf;
2922 c1 = c; // quiet the compiler
2923 cnt = yf = dir = 0; // quiet the compiler
2924 msg = p = q = save_dot = buf; // quiet the compiler
2925 memset(buf, '\0', 9); // clear buf
2929 /* if this is a cursor key, skip these checks */
2942 if (cmd_mode == 2) {
2943 // flip-flop Insert/Replace mode
2944 if (c == VI_K_INSERT)
2946 // we are 'R'eplacing the current *dot with new char
2948 // don't Replace past E-o-l
2949 cmd_mode = 1; // convert to insert
2951 if (1 <= c || Isprint(c)) {
2953 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2954 dot = char_insert(dot, c); // insert new char
2959 if (cmd_mode == 1) {
2960 // hitting "Insert" twice means "R" replace mode
2961 if (c == VI_K_INSERT) goto dc5;
2962 // insert the char c at "dot"
2963 if (1 <= c || Isprint(c)) {
2964 dot = char_insert(dot, c);
2979 #if ENABLE_FEATURE_VI_CRASHME
2980 case 0x14: // dc4 ctrl-T
2981 crashme = (crashme == 0) ? 1 : 0;
3012 //case 'u': // u- FIXME- there is no undo
3014 default: // unrecognised command
3023 end_cmd_q(); // stop adding to q
3024 case 0x00: // nul- ignore
3026 case 2: // ctrl-B scroll up full screen
3027 case VI_K_PAGEUP: // Cursor Key Page Up
3028 dot_scroll(rows - 2, -1);
3030 #if ENABLE_FEATURE_VI_USE_SIGNALS
3031 case 0x03: // ctrl-C interrupt
3032 longjmp(restart, 1);
3034 case 26: // ctrl-Z suspend
3035 suspend_sig(SIGTSTP);
3038 case 4: // ctrl-D scroll down half screen
3039 dot_scroll((rows - 2) / 2, 1);
3041 case 5: // ctrl-E scroll down one line
3044 case 6: // ctrl-F scroll down full screen
3045 case VI_K_PAGEDOWN: // Cursor Key Page Down
3046 dot_scroll(rows - 2, 1);
3048 case 7: // ctrl-G show current status
3049 last_status_cksum = 0; // force status update
3051 case 'h': // h- move left
3052 case VI_K_LEFT: // cursor key Left
3053 case 8: // ctrl-H- move left (This may be ERASE char)
3054 case 0x7f: // DEL- move left (This may be ERASE char)
3060 case 10: // Newline ^J
3061 case 'j': // j- goto next line, same col
3062 case VI_K_DOWN: // cursor key Down
3066 dot_next(); // go to next B-o-l
3067 dot = move_to_col(dot, ccol + offset); // try stay in same col
3069 case 12: // ctrl-L force redraw whole screen
3070 case 18: // ctrl-R force redraw
3071 place_cursor(0, 0, FALSE); // put cursor in correct place
3072 clear_to_eos(); // tel terminal to erase display
3074 screen_erase(); // erase the internal screen buffer
3075 last_status_cksum = 0; // force status update
3076 refresh(TRUE); // this will redraw the entire display
3078 case 13: // Carriage Return ^M
3079 case '+': // +- goto next line
3086 case 21: // ctrl-U scroll up half screen
3087 dot_scroll((rows - 2) / 2, -1);
3089 case 25: // ctrl-Y scroll up one line
3095 cmd_mode = 0; // stop insrting
3097 last_status_cksum = 0; // force status update
3099 case ' ': // move right
3100 case 'l': // move right
3101 case VI_K_RIGHT: // Cursor Key Right
3107 #if ENABLE_FEATURE_VI_YANKMARK
3108 case '"': // "- name a register to use for Delete/Yank
3109 c1 = get_one_char();
3117 case '\'': // '- goto a specific mark
3118 c1 = get_one_char();
3123 q = mark[(unsigned char) c1];
3124 if (text <= q && q < end) {
3126 dot_begin(); // go to B-o-l
3129 } else if (c1 == '\'') { // goto previous context
3130 dot = swap_context(dot); // swap current and previous context
3131 dot_begin(); // go to B-o-l
3137 case 'm': // m- Mark a line
3138 // this is really stupid. If there are any inserts or deletes
3139 // between text[0] and dot then this mark will not point to the
3140 // correct location! It could be off by many lines!
3141 // Well..., at least its quick and dirty.
3142 c1 = get_one_char();
3146 // remember the line
3147 mark[(int) c1] = dot;
3152 case 'P': // P- Put register before
3153 case 'p': // p- put register after
3156 psbs("Nothing in register %c", what_reg());
3159 // are we putting whole lines or strings
3160 if (strchr(p, '\n') != NULL) {
3162 dot_begin(); // putting lines- Put above
3165 // are we putting after very last line?
3166 if (end_line(dot) == (end - 1)) {
3167 dot = end; // force dot to end of text[]
3169 dot_next(); // next line, then put before
3174 dot_right(); // move to right, can move to NL
3176 dot = string_insert(dot, p); // insert the string
3177 end_cmd_q(); // stop adding to q
3179 case 'U': // U- Undo; replace current line with original version
3180 if (reg[Ureg] != 0) {
3181 p = begin_line(dot);
3183 p = text_hole_delete(p, q); // delete cur line
3184 p = string_insert(p, reg[Ureg]); // insert orig line
3189 #endif /* FEATURE_VI_YANKMARK */
3190 case '$': // $- goto end of line
3191 case VI_K_END: // Cursor Key End
3195 dot = end_line(dot);
3197 case '%': // %- find matching char of pair () [] {}
3198 for (q = dot; q < end && *q != '\n'; q++) {
3199 if (strchr("()[]{}", *q) != NULL) {
3200 // we found half of a pair
3201 p = find_pair(q, *q);
3213 case 'f': // f- forward to a user specified char
3214 last_forward_char = get_one_char(); // get the search char
3216 // dont separate these two commands. 'f' depends on ';'
3218 //**** fall thru to ... ';'
3219 case ';': // ;- look at rest of line for last forward char
3223 if (last_forward_char == 0)
3226 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3229 if (*q == last_forward_char)
3232 case '-': // -- goto prev line
3239 #if ENABLE_FEATURE_VI_DOT_CMD
3240 case '.': // .- repeat the last modifying command
3241 // Stuff the last_modifying_cmd back into stdin
3242 // and let it be re-executed.
3243 if (last_modifying_cmd != 0) {
3244 ioq = ioq_start = xstrdup(last_modifying_cmd);
3248 #if ENABLE_FEATURE_VI_SEARCH
3249 case '?': // /- search for a pattern
3250 case '/': // /- search for a pattern
3253 q = get_input_line(buf); // get input line- use "status line"
3255 goto dc3; // if no pat re-use old pat
3256 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3257 // there is a new pat
3258 free(last_search_pattern);
3259 last_search_pattern = xstrdup(q);
3260 goto dc3; // now find the pattern
3262 // user changed mind and erased the "/"- do nothing
3264 case 'N': // N- backward search for last pattern
3268 dir = BACK; // assume BACKWARD search
3270 if (last_search_pattern[0] == '?') {
3274 goto dc4; // now search for pattern
3276 case 'n': // n- repeat search for last pattern
3277 // search rest of text[] starting at next char
3278 // if search fails return orignal "p" not the "p+1" address
3283 if (last_search_pattern == 0) {
3284 msg = "No previous regular expression";
3287 if (last_search_pattern[0] == '/') {
3288 dir = FORWARD; // assume FORWARD search
3291 if (last_search_pattern[0] == '?') {
3296 q = char_search(p, last_search_pattern + 1, dir, FULL);
3298 dot = q; // good search, update "dot"
3302 // no pattern found between "dot" and "end"- continue at top
3307 q = char_search(p, last_search_pattern + 1, dir, FULL);
3308 if (q != NULL) { // found something
3309 dot = q; // found new pattern- goto it
3310 msg = "search hit BOTTOM, continuing at TOP";
3312 msg = "search hit TOP, continuing at BOTTOM";
3315 msg = "Pattern not found";
3321 case '{': // {- move backward paragraph
3322 q = char_search(dot, "\n\n", BACK, FULL);
3323 if (q != NULL) { // found blank line
3324 dot = next_line(q); // move to next blank line
3327 case '}': // }- move forward paragraph
3328 q = char_search(dot, "\n\n", FORWARD, FULL);
3329 if (q != NULL) { // found blank line
3330 dot = next_line(q); // move to next blank line
3333 #endif /* FEATURE_VI_SEARCH */
3334 case '0': // 0- goto begining of line
3344 if (c == '0' && cmdcnt < 1) {
3345 dot_begin(); // this was a standalone zero
3347 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3350 case ':': // :- the colon mode commands
3351 p = get_input_line(":"); // get input line- use "status line"
3352 #if ENABLE_FEATURE_VI_COLON
3353 colon(p); // execute the command
3356 p++; // move past the ':'
3360 if (strncasecmp(p, "quit", cnt) == 0
3361 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3363 if (file_modified && p[1] != '!') {
3364 psbs("No write since last change (:quit! overrides)");
3368 } else if (strncasecmp(p, "write", cnt) == 0
3369 || strncasecmp(p, "wq", cnt) == 0
3370 || strncasecmp(p, "wn", cnt) == 0
3371 || strncasecmp(p, "x", cnt) == 0
3373 cnt = file_write(current_filename, text, end - 1);
3376 psbs("Write error: %s", strerror(errno));
3379 last_file_modified = -1;
3380 psb("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3381 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3382 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3387 } else if (strncasecmp(p, "file", cnt) == 0) {
3388 last_status_cksum = 0; // force status update
3389 } else if (sscanf(p, "%d", &j) > 0) {
3390 dot = find_line(j); // go to line # j
3392 } else { // unrecognised cmd
3395 #endif /* !FEATURE_VI_COLON */
3397 case '<': // <- Left shift something
3398 case '>': // >- Right shift something
3399 cnt = count_lines(text, dot); // remember what line we are on
3400 c1 = get_one_char(); // get the type of thing to delete
3401 find_range(&p, &q, c1);
3402 yank_delete(p, q, 1, YANKONLY); // save copy before change
3405 i = count_lines(p, q); // # of lines we are shifting
3406 for ( ; i > 0; i--, p = next_line(p)) {
3408 // shift left- remove tab or 8 spaces
3410 // shrink buffer 1 char
3411 text_hole_delete(p, p);
3412 } else if (*p == ' ') {
3413 // we should be calculating columns, not just SPACE
3414 for (j = 0; *p == ' ' && j < tabstop; j++) {
3415 text_hole_delete(p, p);
3418 } else if (c == '>') {
3419 // shift right -- add tab or 8 spaces
3420 char_insert(p, '\t');
3423 dot = find_line(cnt); // what line were we on
3425 end_cmd_q(); // stop adding to q
3427 case 'A': // A- append at e-o-l
3428 dot_end(); // go to e-o-l
3429 //**** fall thru to ... 'a'
3430 case 'a': // a- append after current char
3435 case 'B': // B- back a blank-delimited Word
3436 case 'E': // E- end of a blank-delimited word
3437 case 'W': // W- forward a blank-delimited word
3444 if (c == 'W' || isspace(dot[dir])) {
3445 dot = skip_thing(dot, 1, dir, S_TO_WS);
3446 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3449 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3451 case 'C': // C- Change to e-o-l
3452 case 'D': // D- delete to e-o-l
3454 dot = dollar_line(dot); // move to before NL
3455 // copy text into a register and delete
3456 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3458 goto dc_i; // start inserting
3459 #if ENABLE_FEATURE_VI_DOT_CMD
3461 end_cmd_q(); // stop adding to q
3464 case 'G': // G- goto to a line number (default= E-O-F)
3465 dot = end - 1; // assume E-O-F
3467 dot = find_line(cmdcnt); // what line is #cmdcnt
3471 case 'H': // H- goto top line on screen
3473 if (cmdcnt > (rows - 1)) {
3474 cmdcnt = (rows - 1);
3481 case 'I': // I- insert before first non-blank
3484 //**** fall thru to ... 'i'
3485 case 'i': // i- insert before current char
3486 case VI_K_INSERT: // Cursor Key Insert
3488 cmd_mode = 1; // start insrting
3490 case 'J': // J- join current and next lines together
3494 dot_end(); // move to NL
3495 if (dot < end - 1) { // make sure not last char in text[]
3496 *dot++ = ' '; // replace NL with space
3498 while (isblank(*dot)) { // delete leading WS
3502 end_cmd_q(); // stop adding to q
3504 case 'L': // L- goto bottom line on screen
3506 if (cmdcnt > (rows - 1)) {
3507 cmdcnt = (rows - 1);
3515 case 'M': // M- goto middle line on screen
3517 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3518 dot = next_line(dot);
3520 case 'O': // O- open a empty line above
3522 p = begin_line(dot);
3523 if (p[-1] == '\n') {
3525 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3527 dot = char_insert(dot, '\n');
3530 dot = char_insert(dot, '\n'); // i\n ESC
3535 case 'R': // R- continuous Replace char
3539 case 'X': // X- delete char before dot
3540 case 'x': // x- delete the current char
3541 case 's': // s- substitute the current char
3548 if (dot[dir] != '\n') {
3550 dot--; // delete prev char
3551 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3554 goto dc_i; // start insrting
3555 end_cmd_q(); // stop adding to q
3557 case 'Z': // Z- if modified, {write}; exit
3558 // ZZ means to save file (if necessary), then exit
3559 c1 = get_one_char();
3564 if (file_modified) {
3565 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3566 psbs("\"%s\" File is read only", current_filename);
3569 cnt = file_write(current_filename, text, end - 1);
3572 psbs("Write error: %s", strerror(errno));
3573 } else if (cnt == (end - 1 - text + 1)) {
3580 case '^': // ^- move to first non-blank on line
3584 case 'b': // b- back a word
3585 case 'e': // e- end of word
3592 if ((dot + dir) < text || (dot + dir) > end - 1)
3595 if (isspace(*dot)) {
3596 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3598 if (isalnum(*dot) || *dot == '_') {
3599 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3600 } else if (ispunct(*dot)) {
3601 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3604 case 'c': // c- change something
3605 case 'd': // d- delete something
3606 #if ENABLE_FEATURE_VI_YANKMARK
3607 case 'y': // y- yank something
3608 case 'Y': // Y- Yank a line
3610 yf = YANKDEL; // assume either "c" or "d"
3611 #if ENABLE_FEATURE_VI_YANKMARK
3612 if (c == 'y' || c == 'Y')
3617 c1 = get_one_char(); // get the type of thing to delete
3618 find_range(&p, &q, c1);
3619 if (c1 == 27) { // ESC- user changed mind and wants out
3620 c = c1 = 27; // Escape- do nothing
3621 } else if (strchr("wW", c1)) {
3623 // don't include trailing WS as part of word
3624 while (isblank(*q)) {
3625 if (q <= text || q[-1] == '\n')
3630 dot = yank_delete(p, q, 0, yf); // delete word
3631 } else if (strchr("^0bBeEft$", c1)) {
3632 // single line copy text into a register and delete
3633 dot = yank_delete(p, q, 0, yf); // delete word
3634 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3635 // multiple line copy text into a register and delete
3636 dot = yank_delete(p, q, 1, yf); // delete lines
3638 dot = char_insert(dot, '\n');
3639 // on the last line of file don't move to prev line
3640 if (dot != (end-1)) {
3643 } else if (c == 'd') {
3648 // could not recognize object
3649 c = c1 = 27; // error-
3653 // if CHANGING, not deleting, start inserting after the delete
3655 strcpy(buf, "Change");
3656 goto dc_i; // start inserting
3659 strcpy(buf, "Delete");
3661 #if ENABLE_FEATURE_VI_YANKMARK
3662 if (c == 'y' || c == 'Y') {
3663 strcpy(buf, "Yank");
3667 for (cnt = 0; p <= q; p++) {
3671 psb("%s %d lines (%d chars) using [%c]",
3672 buf, cnt, strlen(reg[YDreg]), what_reg());
3674 end_cmd_q(); // stop adding to q
3677 case 'k': // k- goto prev line, same col
3678 case VI_K_UP: // cursor key Up
3683 dot = move_to_col(dot, ccol + offset); // try stay in same col
3685 case 'r': // r- replace the current char with user input
3686 c1 = get_one_char(); // get the replacement char
3689 file_modified++; // has the file been modified
3691 end_cmd_q(); // stop adding to q
3693 case 't': // t- move to char prior to next x
3694 last_forward_char = get_one_char();
3696 if (*dot == last_forward_char)
3698 last_forward_char= 0;
3700 case 'w': // w- forward a word
3704 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3705 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3706 } else if (ispunct(*dot)) { // we are on PUNCT
3707 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3710 dot++; // move over word
3711 if (isspace(*dot)) {
3712 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3716 c1 = get_one_char(); // get the replacement char
3719 cnt = (rows - 2) / 2; // put dot at center
3721 cnt = rows - 2; // put dot at bottom
3722 screenbegin = begin_line(dot); // start dot at top
3723 dot_scroll(cnt, -1);
3725 case '|': // |- move to column "cmdcnt"
3726 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3728 case '~': // ~- flip the case of letters a-z -> A-Z
3732 if (islower(*dot)) {
3733 *dot = toupper(*dot);
3734 file_modified++; // has the file been modified
3735 } else if (isupper(*dot)) {
3736 *dot = tolower(*dot);
3737 file_modified++; // has the file been modified
3740 end_cmd_q(); // stop adding to q
3742 //----- The Cursor and Function Keys -----------------------------
3743 case VI_K_HOME: // Cursor Key Home
3746 // The Fn keys could point to do_macro which could translate them
3747 case VI_K_FUN1: // Function Key F1
3748 case VI_K_FUN2: // Function Key F2
3749 case VI_K_FUN3: // Function Key F3
3750 case VI_K_FUN4: // Function Key F4
3751 case VI_K_FUN5: // Function Key F5
3752 case VI_K_FUN6: // Function Key F6
3753 case VI_K_FUN7: // Function Key F7
3754 case VI_K_FUN8: // Function Key F8
3755 case VI_K_FUN9: // Function Key F9
3756 case VI_K_FUN10: // Function Key F10
3757 case VI_K_FUN11: // Function Key F11
3758 case VI_K_FUN12: // Function Key F12
3763 // if text[] just became empty, add back an empty line
3765 char_insert(text, '\n'); // start empty buf with dummy line
3768 // it is OK for dot to exactly equal to end, otherwise check dot validity
3770 dot = bound_dot(dot); // make sure "dot" is valid
3772 #if ENABLE_FEATURE_VI_YANKMARK
3773 check_context(c); // update the current context
3777 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3778 cnt = dot - begin_line(dot);
3779 // Try to stay off of the Newline
3780 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3784 #if ENABLE_FEATURE_VI_CRASHME
3785 static int totalcmds = 0;
3786 static int Mp = 85; // Movement command Probability
3787 static int Np = 90; // Non-movement command Probability
3788 static int Dp = 96; // Delete command Probability
3789 static int Ip = 97; // Insert command Probability
3790 static int Yp = 98; // Yank command Probability
3791 static int Pp = 99; // Put command Probability
3792 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3793 const char chars[20] = "\t012345 abcdABCD-=.$";
3794 const char *const words[20] = {
3795 "this", "is", "a", "test",
3796 "broadcast", "the", "emergency", "of",
3797 "system", "quick", "brown", "fox",
3798 "jumped", "over", "lazy", "dogs",
3799 "back", "January", "Febuary", "March"
3801 const char *const lines[20] = {
3802 "You should have received a copy of the GNU General Public License\n",
3803 "char c, cm, *cmd, *cmd1;\n",
3804 "generate a command by percentages\n",
3805 "Numbers may be typed as a prefix to some commands.\n",
3806 "Quit, discarding changes!\n",
3807 "Forced write, if permission originally not valid.\n",
3808 "In general, any ex or ed command (such as substitute or delete).\n",
3809 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3810 "Please get w/ me and I will go over it with you.\n",
3811 "The following is a list of scheduled, committed changes.\n",
3812 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3813 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3814 "Any question about transactions please contact Sterling Huxley.\n",
3815 "I will try to get back to you by Friday, December 31.\n",
3816 "This Change will be implemented on Friday.\n",
3817 "Let me know if you have problems accessing this;\n",
3818 "Sterling Huxley recently added you to the access list.\n",
3819 "Would you like to go to lunch?\n",
3820 "The last command will be automatically run.\n",
3821 "This is too much english for a computer geek.\n",
3823 char *multilines[20] = {
3824 "You should have received a copy of the GNU General Public License\n",
3825 "char c, cm, *cmd, *cmd1;\n",
3826 "generate a command by percentages\n",
3827 "Numbers may be typed as a prefix to some commands.\n",
3828 "Quit, discarding changes!\n",
3829 "Forced write, if permission originally not valid.\n",
3830 "In general, any ex or ed command (such as substitute or delete).\n",
3831 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3832 "Please get w/ me and I will go over it with you.\n",
3833 "The following is a list of scheduled, committed changes.\n",
3834 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3835 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3836 "Any question about transactions please contact Sterling Huxley.\n",
3837 "I will try to get back to you by Friday, December 31.\n",
3838 "This Change will be implemented on Friday.\n",
3839 "Let me know if you have problems accessing this;\n",
3840 "Sterling Huxley recently added you to the access list.\n",
3841 "Would you like to go to lunch?\n",
3842 "The last command will be automatically run.\n",
3843 "This is too much english for a computer geek.\n",
3846 // create a random command to execute
3847 static void crash_dummy()
3849 static int sleeptime; // how long to pause between commands
3850 char c, cm, *cmd, *cmd1;
3851 int i, cnt, thing, rbi, startrbi, percent;
3853 // "dot" movement commands
3854 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3856 // is there already a command running?
3857 if (readed_for_parse > 0)
3861 sleeptime = 0; // how long to pause between commands
3862 memset(readbuffer, '\0', MAX_LINELEN); // clear the read buffer
3863 // generate a command by percentages
3864 percent = (int) lrand48() % 100; // get a number from 0-99
3865 if (percent < Mp) { // Movement commands
3866 // available commands
3869 } else if (percent < Np) { // non-movement commands
3870 cmd = "mz<>\'\""; // available commands
3872 } else if (percent < Dp) { // Delete commands
3873 cmd = "dx"; // available commands
3875 } else if (percent < Ip) { // Inset commands
3876 cmd = "iIaAsrJ"; // available commands
3878 } else if (percent < Yp) { // Yank commands
3879 cmd = "yY"; // available commands
3881 } else if (percent < Pp) { // Put commands
3882 cmd = "pP"; // available commands
3885 // We do not know how to handle this command, try again
3889 // randomly pick one of the available cmds from "cmd[]"
3890 i = (int) lrand48() % strlen(cmd);
3892 if (strchr(":\024", cm))
3893 goto cd0; // dont allow colon or ctrl-T commands
3894 readbuffer[rbi++] = cm; // put cmd into input buffer
3896 // now we have the command-
3897 // there are 1, 2, and multi char commands
3898 // find out which and generate the rest of command as necessary
3899 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3900 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3901 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3902 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3904 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3906 readbuffer[rbi++] = c; // add movement to input buffer
3908 if (strchr("iIaAsc", cm)) { // multi-char commands
3910 // change some thing
3911 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3913 readbuffer[rbi++] = c; // add movement to input buffer
3915 thing = (int) lrand48() % 4; // what thing to insert
3916 cnt = (int) lrand48() % 10; // how many to insert
3917 for (i = 0; i < cnt; i++) {
3918 if (thing == 0) { // insert chars
3919 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3920 } else if (thing == 1) { // insert words
3921 strcat(readbuffer, words[(int) lrand48() % 20]);
3922 strcat(readbuffer, " ");
3923 sleeptime = 0; // how fast to type
3924 } else if (thing == 2) { // insert lines
3925 strcat(readbuffer, lines[(int) lrand48() % 20]);
3926 sleeptime = 0; // how fast to type
3927 } else { // insert multi-lines
3928 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3929 sleeptime = 0; // how fast to type
3932 strcat(readbuffer, "\033");
3934 readed_for_parse = strlen(readbuffer);
3938 mysleep(sleeptime); // sleep 1/100 sec
3941 // test to see if there are any errors
3942 static void crash_test()
3944 static time_t oldtim;
3947 char d[2], msg[MAX_LINELEN];
3951 strcat(msg, "end<text ");
3953 if (end > textend) {
3954 strcat(msg, "end>textend ");
3957 strcat(msg, "dot<text ");
3960 strcat(msg, "dot>end ");
3962 if (screenbegin < text) {
3963 strcat(msg, "screenbegin<text ");
3965 if (screenbegin > end - 1) {
3966 strcat(msg, "screenbegin>end-1 ");
3971 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3972 totalcmds, last_input_char, msg, SOs, SOn);
3974 while (read(0, d, 1) > 0) {
3975 if (d[0] == '\n' || d[0] == '\r')
3980 tim = (time_t) time((time_t *) 0);
3981 if (tim >= (oldtim + 3)) {
3982 sprintf(status_buffer,
3983 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3984 totalcmds, M, N, I, D, Y, P, U, end - text + 1);