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[] = "\033[7m";
66 static const char SOn[] = "\033[0m";
67 /* terminal bell sequence */
68 static const char bell[] = "\007";
69 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
70 static const char Ceol[] = "\033[0K";
71 static const char Ceos [] = "\033[0J";
72 /* Cursor motion arbitrary destination ESC sequence */
73 static const char CMrc[] = "\033[%d;%dH";
74 /* Cursor motion up and down ESC sequence */
75 static const char CMup[] = "\033[A";
76 static const char CMdown[] = "\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 */
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 *cfn; // previous, current, and next 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;
139 #if ENABLE_FEATURE_VI_DOT_CMD
140 static smallint adding2q; // are we currently adding user input to q
141 static char *last_modifying_cmd; // last modifying cmd for "."
142 static char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
144 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
145 static int last_row; // where the cursor was last moved to
147 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
150 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
151 static char *modifying_cmds; // cmds that modify text[]
153 #if ENABLE_FEATURE_VI_SEARCH
154 static char *last_search_pattern; // last pattern from a '/' or '?' search
157 /* Moving biggest data to malloced space... */
159 /* many references - keep near the top of globals */
160 char *text, *end; // pointers to the user data in memory
161 char *dot; // where all the action takes place
162 #if ENABLE_FEATURE_VI_YANKMARK
163 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
164 int YDreg, Ureg; // default delete register and orig line for "U"
165 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
166 char *context_start, *context_end;
168 /* a few references only */
169 #if ENABLE_FEATURE_VI_USE_SIGNALS
170 jmp_buf restart; // catch_sig()
172 struct termios term_orig, term_vi; // remember what the cooked mode was
173 #if ENABLE_FEATURE_VI_COLON
174 char *initial_cmds[3]; // currently 2 entries, NULL terminated
177 #define G (*ptr_to_globals)
178 #define text (G.text )
182 #define YDreg (G.YDreg )
183 #define Ureg (G.Ureg )
184 #define mark (G.mark )
185 #define context_start (G.context_start )
186 #define context_end (G.context_end )
187 #define restart (G.restart )
188 #define term_orig (G.term_orig )
189 #define term_vi (G.term_vi )
190 #define initial_cmds (G.initial_cmds )
192 static void edit_file(char *); // edit one file
193 static void do_cmd(char); // execute a command
194 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
195 static char *begin_line(char *); // return pointer to cur line B-o-l
196 static char *end_line(char *); // return pointer to cur line E-o-l
197 static char *prev_line(char *); // return pointer to prev line B-o-l
198 static char *next_line(char *); // return pointer to next line B-o-l
199 static char *end_screen(void); // get pointer to last char on screen
200 static int count_lines(char *, char *); // count line from start to stop
201 static char *find_line(int); // find begining of line #li
202 static char *move_to_col(char *, int); // move "p" to column l
203 static int isblnk(char); // is the char a blank or tab
204 static void dot_left(void); // move dot left- dont leave line
205 static void dot_right(void); // move dot right- dont leave line
206 static void dot_begin(void); // move dot to B-o-l
207 static void dot_end(void); // move dot to E-o-l
208 static void dot_next(void); // move dot to next line B-o-l
209 static void dot_prev(void); // move dot to prev line B-o-l
210 static void dot_scroll(int, int); // move the screen up or down
211 static void dot_skip_over_ws(void); // move dot pat WS
212 static void dot_delete(void); // delete the char at 'dot'
213 static char *bound_dot(char *); // make sure text[0] <= P < "end"
214 static char *new_screen(int, int); // malloc virtual screen memory
215 static char *new_text(int); // malloc memory for text[] buffer
216 static char *char_insert(char *, char); // insert the char c at 'p'
217 static char *stupid_insert(char *, char); // stupidly insert the char c at 'p'
218 static char find_range(char **, char **, char); // return pointers for an object
219 static int st_test(char *, int, int, char *); // helper for skip_thing()
220 static char *skip_thing(char *, int, int, int); // skip some object
221 static char *find_pair(char *, char); // find matching pair () [] {}
222 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
223 static char *text_hole_make(char *, int); // at "p", make a 'size' byte hole
224 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
225 static void show_help(void); // display some help info
226 static void rawmode(void); // set "raw" mode on tty
227 static void cookmode(void); // return to "cooked" mode on tty
228 static int mysleep(int); // sleep for 'h' 1/100 seconds
229 static char readit(void); // read (maybe cursor) key from stdin
230 static char get_one_char(void); // read 1 char from stdin
231 static int file_size(const char *); // what is the byte size of "fn"
232 static int file_insert(char *, char *, int);
233 static int file_write(char *, char *, char *);
234 static void place_cursor(int, int, int);
235 static void screen_erase(void);
236 static void clear_to_eol(void);
237 static void clear_to_eos(void);
238 static void standout_start(void); // send "start reverse video" sequence
239 static void standout_end(void); // send "end reverse video" sequence
240 static void flash(int); // flash the terminal screen
241 static void show_status_line(void); // put a message on the bottom line
242 static void psb(const char *, ...); // Print Status Buf
243 static void psbs(const char *, ...); // Print Status Buf in standout mode
244 static void ni(const char *); // display messages
245 static int format_edit_status(void); // format file status on status line
246 static void redraw(int); // force a full screen refresh
247 static void format_line(char*, char*, int);
248 static void refresh(int); // update the terminal from screen[]
250 static void Indicate_Error(void); // use flash or beep to indicate error
251 #define indicate_error(c) Indicate_Error()
252 static void Hit_Return(void);
254 #if ENABLE_FEATURE_VI_SEARCH
255 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
256 static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase"
258 #if ENABLE_FEATURE_VI_COLON
259 static char *get_one_address(char *, int *); // get colon addr, if present
260 static char *get_address(char *, int *, int *); // get two colon addrs, if present
261 static void colon(char *); // execute the "colon" mode cmds
263 #if ENABLE_FEATURE_VI_USE_SIGNALS
264 static void winch_sig(int); // catch window size changes
265 static void suspend_sig(int); // catch ctrl-Z
266 static void catch_sig(int); // catch ctrl-C and alarm time-outs
268 #if ENABLE_FEATURE_VI_DOT_CMD
269 static void start_new_cmd_q(char); // new queue for command
270 static void end_cmd_q(void); // stop saving input chars
272 #define end_cmd_q() ((void)0)
274 #if ENABLE_FEATURE_VI_SETOPTS
275 static void showmatching(char *); // show the matching pair () [] {}
277 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
278 static char *string_insert(char *, char *); // insert the string at 'p'
280 #if ENABLE_FEATURE_VI_YANKMARK
281 static char *text_yank(char *, char *, int); // save copy of "p" into a register
282 static char what_reg(void); // what is letter of current YDreg
283 static void check_context(char); // remember context for '' command
285 #if ENABLE_FEATURE_VI_CRASHME
286 static void crash_dummy();
287 static void crash_test();
288 static int crashme = 0;
292 static void write1(const char *out)
297 int vi_main(int argc, char **argv);
298 int vi_main(int argc, char **argv)
301 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
303 #if ENABLE_FEATURE_VI_YANKMARK
306 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
310 PTR_TO_GLOBALS = xzalloc(sizeof(G));
312 #if ENABLE_FEATURE_VI_CRASHME
313 srand((long) my_pid);
316 status_buffer = STATUS_BUFFER;
317 last_status_cksum = 0;
319 #if ENABLE_FEATURE_VI_READONLY
320 vi_readonly = readonly = FALSE;
321 if (strncmp(argv[0], "view", 4) == 0) {
326 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
327 #if ENABLE_FEATURE_VI_YANKMARK
328 for (i = 0; i < 28; i++) {
330 } // init the yank regs
332 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
333 modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
336 // 1- process $HOME/.exrc file (not inplemented yet)
337 // 2- process EXINIT variable from environment
338 // 3- process command line args
339 #if ENABLE_FEATURE_VI_COLON
341 char *p = getenv("EXINIT");
343 initial_cmds[0] = xstrdup(p);
346 while ((c = getopt(argc, argv, "hCR" USE_FEATURE_VI_COLON("c:"))) != -1) {
348 #if ENABLE_FEATURE_VI_CRASHME
353 #if ENABLE_FEATURE_VI_READONLY
354 case 'R': // Read-only flag
359 //case 'r': // recover flag- ignore- we don't use tmp file
360 //case 'x': // encryption flag- ignore
361 //case 'c': // execute command first
362 #if ENABLE_FEATURE_VI_COLON
363 case 'c': // cmd line vi command
365 initial_cmds[initial_cmds[0] != 0] = xstrdup(optarg);
367 //case 'h': // help -- just use default
375 // The argv array can be used by the ":next" and ":rewind" commands
377 fn_start = optind; // remember first file name for :next and :rew
380 //----- This is the main file handling loop --------------
381 if (optind >= argc) {
382 editing = 1; // 0= exit, 1= one file, 2 = multiple files
385 for (; optind < argc; optind++) {
386 editing = 1; // 0=exit, 1=one file, 2+ = many files
388 cfn = xstrdup(argv[optind]);
392 //-----------------------------------------------------------
397 static void edit_file(char * fn)
402 #if ENABLE_FEATURE_VI_USE_SIGNALS
405 #if ENABLE_FEATURE_VI_YANKMARK
406 static char *cur_line;
413 if (ENABLE_FEATURE_VI_WIN_RESIZE)
414 get_terminal_width_height(0, &columns, &rows);
415 new_screen(rows, columns); // get memory for virtual screen
417 cnt = file_size(fn); // file size
418 size = 2 * cnt; // 200% of file size
419 new_text(size); // get a text[] buffer
420 screenbegin = dot = end = text;
422 ch = file_insert(fn, text, cnt);
425 char_insert(text, '\n'); // start empty buf with dummy line
428 last_file_modified = -1;
429 #if ENABLE_FEATURE_VI_YANKMARK
430 YDreg = 26; // default Yank/Delete reg
431 Ureg = 27; // hold orig line for "U" cmd
432 for (cnt = 0; cnt < 28; cnt++) {
435 mark[26] = mark[27] = text; // init "previous context"
438 last_forward_char = last_input_char = '\0';
442 #if ENABLE_FEATURE_VI_USE_SIGNALS
444 signal(SIGWINCH, winch_sig);
445 signal(SIGTSTP, suspend_sig);
446 sig = setjmp(restart);
448 screenbegin = dot = text;
453 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
456 offset = 0; // no horizontal offset
458 #if ENABLE_FEATURE_VI_DOT_CMD
459 free(last_modifying_cmd);
461 ioq = ioq_start = last_modifying_cmd = NULL;
464 redraw(FALSE); // dont force every col re-draw
467 #if ENABLE_FEATURE_VI_COLON
472 while ((p = initial_cmds[n])) {
482 free(initial_cmds[n]);
483 initial_cmds[n] = NULL;
488 //------This is the main Vi cmd handling loop -----------------------
489 while (editing > 0) {
490 #if ENABLE_FEATURE_VI_CRASHME
492 if ((end - text) > 1) {
493 crash_dummy(); // generate a random command
496 dot = string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
501 last_input_char = c = get_one_char(); // get a cmd from user
502 #if ENABLE_FEATURE_VI_YANKMARK
503 // save a copy of the current line- for the 'U" command
504 if (begin_line(dot) != cur_line) {
505 cur_line = begin_line(dot);
506 text_yank(begin_line(dot), end_line(dot), Ureg);
509 #if ENABLE_FEATURE_VI_DOT_CMD
510 // These are commands that change text[].
511 // Remember the input for the "." command
512 if (!adding2q && ioq_start == 0
513 && strchr(modifying_cmds, c)
518 do_cmd(c); // execute the user command
520 // poll to see if there is input already waiting. if we are
521 // not able to display output fast enough to keep up, skip
522 // the display update until we catch up with input.
523 if (mysleep(0) == 0) {
524 // no input pending- so update output
528 #if ENABLE_FEATURE_VI_CRASHME
530 crash_test(); // test editor variables
533 //-------------------------------------------------------------------
535 place_cursor(rows, 0, FALSE); // go to bottom of screen
536 clear_to_eol(); // Erase to end of line
540 //----- The Colon commands -------------------------------------
541 #if ENABLE_FEATURE_VI_COLON
542 static char *get_one_address(char * p, int *addr) // get colon addr, if present
547 #if ENABLE_FEATURE_VI_YANKMARK
550 #if ENABLE_FEATURE_VI_SEARCH
551 char *pat, buf[MAX_LINELEN];
554 *addr = -1; // assume no addr
555 if (*p == '.') { // the current line
558 *addr = count_lines(text, q);
559 #if ENABLE_FEATURE_VI_YANKMARK
560 } else if (*p == '\'') { // is this a mark addr
564 if (c >= 'a' && c <= 'z') {
567 q = mark[(unsigned char) c];
568 if (q != NULL) { // is mark valid
569 *addr = count_lines(text, q); // count lines
573 #if ENABLE_FEATURE_VI_SEARCH
574 } else if (*p == '/') { // a search pattern
582 pat = xstrdup(buf); // save copy of pattern
585 q = char_search(dot, pat, FORWARD, FULL);
587 *addr = count_lines(text, q);
591 } else if (*p == '$') { // the last line in file
593 q = begin_line(end - 1);
594 *addr = count_lines(text, q);
595 } else if (isdigit(*p)) { // specific line number
596 sscanf(p, "%d%n", addr, &st);
598 } else { // I don't reconise this
599 // unrecognised address- assume -1
605 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
607 //----- get the address' i.e., 1,3 'a,'b -----
608 // get FIRST addr, if present
610 p++; // skip over leading spaces
611 if (*p == '%') { // alias for 1,$
614 *e = count_lines(text, end-1);
617 p = get_one_address(p, b);
620 if (*p == ',') { // is there a address separator
624 // get SECOND addr, if present
625 p = get_one_address(p, e);
629 p++; // skip over trailing spaces
633 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
634 static void setops(const char *args, const char *opname, int flg_no,
635 const char *short_opname, int opt)
637 const char *a = args + flg_no;
638 int l = strlen(opname) - 1; /* opname have + ' ' */
640 if (strncasecmp(a, opname, l) == 0
641 || strncasecmp(a, short_opname, 2) == 0
651 static void colon(char * buf)
653 char c, *orig_buf, *buf1, *q, *r;
654 char *fn, cmd[MAX_LINELEN], args[MAX_LINELEN];
655 int i, l, li, ch, b, e;
656 int useforce = FALSE, forced = FALSE;
659 // :3154 // if (-e line 3154) goto it else stay put
660 // :4,33w! foo // write a portion of buffer to file "foo"
661 // :w // write all of buffer to current file
663 // :q! // quit- dont care about modified file
664 // :'a,'z!sort -u // filter block through sort
665 // :'f // goto mark "f"
666 // :'fl // list literal the mark "f" line
667 // :.r bar // read file "bar" into buffer before dot
668 // :/123/,/abc/d // delete lines from "123" line to "abc" line
669 // :/xyz/ // goto the "xyz" line
670 // :s/find/replace/ // substitute pattern "find" with "replace"
671 // :!<cmd> // run <cmd> then return
677 buf++; // move past the ':'
681 q = text; // assume 1,$ for the range
683 li = count_lines(text, end - 1);
684 fn = cfn; // default to current file
685 memset(cmd, '\0', MAX_LINELEN); // clear cmd[]
686 memset(args, '\0', MAX_LINELEN); // clear args[]
688 // look for optional address(es) :. :1 :1,9 :'q,'a :%
689 buf = get_address(buf, &b, &e);
691 // remember orig command line
694 // get the COMMAND into cmd[]
696 while (*buf != '\0') {
705 buf1 = last_char_is(cmd, '!');
708 *buf1 = '\0'; // get rid of !
711 // if there is only one addr, then the addr
712 // is the line number of the single line the
713 // user wants. So, reset the end
714 // pointer to point at end of the "b" line
715 q = find_line(b); // what line is #b
720 // we were given two addrs. change the
721 // end pointer to the addr given by user.
722 r = find_line(e); // what line is #e
726 // ------------ now look for the command ------------
728 if (i == 0) { // :123CR goto line #123
730 dot = find_line(b); // what line is #b
734 #if ENABLE_FEATURE_ALLOW_EXEC
735 else if (strncmp(cmd, "!", 1) == 0) { // run a cmd
736 // :!ls run the <cmd>
737 alarm(0); // wait for input- no alarms
738 place_cursor(rows - 1, 0, FALSE); // go to Status line
739 clear_to_eol(); // clear the line
741 system(orig_buf + 1); // run the cmd
743 Hit_Return(); // let user see results
744 alarm(3); // done waiting for input
747 else if (strncmp(cmd, "=", i) == 0) { // where is the address
748 if (b < 0) { // no addr given- use defaults
749 b = e = count_lines(text, dot);
752 } else if (strncasecmp(cmd, "delete", i) == 0) { // delete lines
753 if (b < 0) { // no addr given- use defaults
754 q = begin_line(dot); // assume .,. for the range
757 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
759 } else if (strncasecmp(cmd, "edit", i) == 0) { // Edit a file
762 // don't edit, if the current file has been modified
763 if (file_modified && ! useforce) {
764 psbs("No write since last change (:edit! overrides)");
768 // the user supplied a file name
770 } else if (cfn && cfn[0]) {
771 // no user supplied name- use the current filename
775 // no user file name, no current name- punt
776 psbs("No current filename");
780 // see if file exists- if not, its just a new file request
781 sr = stat(fn, &st_buf);
783 // This is just a request for a new file creation.
784 // The file_insert below will fail but we get
785 // an empty buffer with a file name. Then the "write"
786 // command can do the create.
788 if ((st_buf.st_mode & S_IFREG) == 0) {
789 // This is not a regular file
790 psbs("\"%s\" is not a regular file", fn);
793 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
794 // dont have any read permissions
795 psbs("\"%s\" is not readable", fn);
800 // There is a read-able regular file
801 // make this the current file
802 q = xstrdup(fn); // save the cfn
803 free(cfn); // free the old name
804 cfn = q; // remember new cfn
807 // delete all the contents of text[]
808 new_text(2 * file_size(fn));
809 screenbegin = dot = end = text;
812 ch = file_insert(fn, text, file_size(fn));
815 // start empty buf with dummy line
816 char_insert(text, '\n');
820 last_file_modified = -1;
821 #if ENABLE_FEATURE_VI_YANKMARK
822 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
823 free(reg[Ureg]); // free orig line reg- for 'U'
826 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
827 free(reg[YDreg]); // free default yank/delete register
830 for (li = 0; li < 28; li++) {
834 // how many lines in text[]?
835 li = count_lines(text, end - 1);
837 #if ENABLE_FEATURE_VI_READONLY
841 (sr < 0 ? " [New file]" : ""),
842 #if ENABLE_FEATURE_VI_READONLY
843 ((vi_readonly || readonly) ? " [Read only]" : ""),
846 } else if (strncasecmp(cmd, "file", i) == 0) { // what File is this
847 if (b != -1 || e != -1) {
848 ni("No address allowed on this command");
852 // user wants a new filename
856 // user wants file status info
857 last_status_cksum = 0; // force status update
859 } else if (strncasecmp(cmd, "features", i) == 0) { // what features are available
860 // print out values of all features
861 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
862 clear_to_eol(); // clear the line
867 } else if (strncasecmp(cmd, "list", i) == 0) { // literal print line
868 if (b < 0) { // no addr given- use defaults
869 q = begin_line(dot); // assume .,. for the range
872 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
873 clear_to_eol(); // clear the line
875 for (; q <= r; q++) {
879 c_is_no_print = (c & 0x80) && !Isprint(c);
886 } else if (c < ' ' || c == 127) {
897 #if ENABLE_FEATURE_VI_SET
901 } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
902 || strncasecmp(cmd, "next", i) == 0 // edit next file
905 // force end of argv list
912 // don't exit if the file been modified
914 psbs("No write since last change (:%s! overrides)",
915 (*cmd == 'q' ? "quit" : "next"));
918 // are there other file to edit
919 if (*cmd == 'q' && optind < save_argc - 1) {
920 psbs("%d more file to edit", (save_argc - optind - 1));
923 if (*cmd == 'n' && optind >= save_argc - 1) {
924 psbs("No more files to edit");
928 } else if (strncasecmp(cmd, "read", i) == 0) { // read file into text[]
931 psbs("No filename given");
934 if (b < 0) { // no addr given- use defaults
935 q = begin_line(dot); // assume "dot"
937 // read after current line- unless user said ":0r foo"
940 #if ENABLE_FEATURE_VI_READONLY
941 l = readonly; // remember current files' status
943 ch = file_insert(fn, q, file_size(fn));
944 #if ENABLE_FEATURE_VI_READONLY
948 goto vc1; // nothing was inserted
949 // how many lines in text[]?
950 li = count_lines(q, q + ch - 1);
952 #if ENABLE_FEATURE_VI_READONLY
956 #if ENABLE_FEATURE_VI_READONLY
957 ((vi_readonly || readonly) ? " [Read only]" : ""),
961 // if the insert is before "dot" then we need to update
966 } else if (strncasecmp(cmd, "rewind", i) == 0) { // rewind cmd line args
967 if (file_modified && ! useforce) {
968 psbs("No write since last change (:rewind! overrides)");
970 // reset the filenames to edit
971 optind = fn_start - 1;
974 #if ENABLE_FEATURE_VI_SET
975 } else if (strncasecmp(cmd, "set", i) == 0) { // set or clear features
976 #if ENABLE_FEATURE_VI_SETOPTS
979 i = 0; // offset into args
980 // only blank is regarded as args delmiter. What about tab '\t' ?
981 if (!args[0] || strcasecmp(args, "all") == 0) {
982 // print out values of all options
983 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
984 clear_to_eol(); // clear the line
985 printf("----------------------------------------\r\n");
986 #if ENABLE_FEATURE_VI_SETOPTS
989 printf("autoindent ");
995 printf("ignorecase ");
998 printf("showmatch ");
999 printf("tabstop=%d ", tabstop);
1004 #if ENABLE_FEATURE_VI_SETOPTS
1007 if (strncasecmp(argp, "no", 2) == 0)
1008 i = 2; // ":set noautoindent"
1009 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1010 setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
1011 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1012 setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
1014 if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
1015 sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
1016 if (ch > 0 && ch < columns - 1)
1019 while (*argp && *argp != ' ')
1020 argp++; // skip to arg delimiter (i.e. blank)
1021 while (*argp && *argp == ' ')
1022 argp++; // skip all delimiting blanks
1024 #endif /* FEATURE_VI_SETOPTS */
1025 #endif /* FEATURE_VI_SET */
1026 #if ENABLE_FEATURE_VI_SEARCH
1027 } else if (strncasecmp(cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1031 // F points to the "find" pattern
1032 // R points to the "replace" pattern
1033 // replace the cmd line delimiters "/" with NULLs
1034 gflag = 0; // global replace flag
1035 c = orig_buf[1]; // what is the delimiter
1036 F = orig_buf + 2; // start of "find"
1037 R = strchr(F, c); // middle delimiter
1038 if (!R) goto colon_s_fail;
1039 *R++ = '\0'; // terminate "find"
1040 buf1 = strchr(R, c);
1041 if (!buf1) goto colon_s_fail;
1042 *buf1++ = '\0'; // terminate "replace"
1043 if (*buf1 == 'g') { // :s/foo/bar/g
1045 gflag++; // turn on gflag
1048 if (b < 0) { // maybe :s/foo/bar/
1049 q = begin_line(dot); // start with cur line
1050 b = count_lines(text, q); // cur line number
1053 e = b; // maybe :.s/foo/bar/
1054 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1055 ls = q; // orig line start
1057 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1059 // we found the "find" pattern - delete it
1060 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1061 // inset the "replace" patern
1062 string_insert(buf1, R); // insert the string
1063 // check for "global" :s/foo/bar/g
1065 if ((buf1 + strlen(R)) < end_line(ls)) {
1066 q = buf1 + strlen(R);
1067 goto vc4; // don't let q move past cur line
1073 #endif /* FEATURE_VI_SEARCH */
1074 } else if (strncasecmp(cmd, "version", i) == 0) { // show software version
1075 psb("%s", BB_VER " " BB_BT);
1076 } else if (strncasecmp(cmd, "write", i) == 0 // write text to file
1077 || strncasecmp(cmd, "wq", i) == 0
1078 || strncasecmp(cmd, "wn", i) == 0
1079 || strncasecmp(cmd, "x", i) == 0
1081 // is there a file name to write to?
1085 #if ENABLE_FEATURE_VI_READONLY
1086 if ((vi_readonly || readonly) && !useforce) {
1087 psbs("\"%s\" File is read only", fn);
1091 // how many lines in text[]?
1092 li = count_lines(q, r);
1094 // see if file exists- if not, its just a new file request
1096 // if "fn" is not write-able, chmod u+w
1097 // sprintf(syscmd, "chmod u+w %s", fn);
1101 l = file_write(fn, q, r);
1102 if (useforce && forced) {
1104 // sprintf(syscmd, "chmod u-w %s", fn);
1110 psbs("Write error: %s", strerror(errno));
1112 psb("\"%s\" %dL, %dC", fn, li, l);
1113 if (q == text && r == end - 1 && l == ch) {
1115 last_file_modified = -1;
1117 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1118 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1123 #if ENABLE_FEATURE_VI_READONLY
1126 #if ENABLE_FEATURE_VI_YANKMARK
1127 } else if (strncasecmp(cmd, "yank", i) == 0) { // yank lines
1128 if (b < 0) { // no addr given- use defaults
1129 q = begin_line(dot); // assume .,. for the range
1132 text_yank(q, r, YDreg);
1133 li = count_lines(q, r);
1134 psb("Yank %d lines (%d chars) into [%c]",
1135 li, strlen(reg[YDreg]), what_reg());
1142 dot = bound_dot(dot); // make sure "dot" is valid
1144 #if ENABLE_FEATURE_VI_SEARCH
1146 psb(":s expression missing delimiters");
1150 #endif /* FEATURE_VI_COLON */
1152 static void Hit_Return(void)
1156 standout_start(); // start reverse video
1157 write1("[Hit return to continue]");
1158 standout_end(); // end reverse video
1159 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1161 redraw(TRUE); // force redraw all
1164 //----- Synchronize the cursor to Dot --------------------------
1165 static void sync_cursor(char * d, int *row, int *col)
1167 char *beg_cur; // begin and end of "d" line
1168 char *end_scr; // begin and end of screen
1172 beg_cur = begin_line(d); // first char of cur line
1174 end_scr = end_screen(); // last char of screen
1176 if (beg_cur < screenbegin) {
1177 // "d" is before top line on screen
1178 // how many lines do we have to move
1179 cnt = count_lines(beg_cur, screenbegin);
1181 screenbegin = beg_cur;
1182 if (cnt > (rows - 1) / 2) {
1183 // we moved too many lines. put "dot" in middle of screen
1184 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1185 screenbegin = prev_line(screenbegin);
1188 } else if (beg_cur > end_scr) {
1189 // "d" is after bottom line on screen
1190 // how many lines do we have to move
1191 cnt = count_lines(end_scr, beg_cur);
1192 if (cnt > (rows - 1) / 2)
1193 goto sc1; // too many lines
1194 for (ro = 0; ro < cnt - 1; ro++) {
1195 // move screen begin the same amount
1196 screenbegin = next_line(screenbegin);
1197 // now, move the end of screen
1198 end_scr = next_line(end_scr);
1199 end_scr = end_line(end_scr);
1202 // "d" is on screen- find out which row
1204 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1210 // find out what col "d" is on
1212 do { // drive "co" to correct column
1213 if (*tp == '\n' || *tp == '\0')
1217 co += ((tabstop - 1) - (co % tabstop));
1218 } else if (*tp < ' ' || *tp == 127) {
1219 co++; // display as ^X, use 2 columns
1221 } while (tp++ < d && ++co);
1223 // "co" is the column where "dot" is.
1224 // The screen has "columns" columns.
1225 // The currently displayed columns are 0+offset -- columns+ofset
1226 // |-------------------------------------------------------------|
1228 // offset | |------- columns ----------------|
1230 // If "co" is already in this range then we do not have to adjust offset
1231 // but, we do have to subtract the "offset" bias from "co".
1232 // If "co" is outside this range then we have to change "offset".
1233 // If the first char of a line is a tab the cursor will try to stay
1234 // in column 7, but we have to set offset to 0.
1236 if (co < 0 + offset) {
1239 if (co >= columns + offset) {
1240 offset = co - columns + 1;
1242 // if the first char of the line is a tab, and "dot" is sitting on it
1243 // force offset to 0.
1244 if (d == beg_cur && *d == '\t') {
1253 //----- Text Movement Routines ---------------------------------
1254 static char *begin_line(char * p) // return pointer to first char cur line
1256 while (p > text && p[-1] != '\n')
1257 p--; // go to cur line B-o-l
1261 static char *end_line(char * p) // return pointer to NL of cur line line
1263 while (p < end - 1 && *p != '\n')
1264 p++; // go to cur line E-o-l
1268 static inline char *dollar_line(char * p) // return pointer to just before NL line
1270 while (p < end - 1 && *p != '\n')
1271 p++; // go to cur line E-o-l
1272 // Try to stay off of the Newline
1273 if (*p == '\n' && (p - begin_line(p)) > 0)
1278 static char *prev_line(char * p) // return pointer first char prev line
1280 p = begin_line(p); // goto begining of cur line
1281 if (p[-1] == '\n' && p > text)
1282 p--; // step to prev line
1283 p = begin_line(p); // goto begining of prev line
1287 static char *next_line(char * p) // return pointer first char next line
1290 if (*p == '\n' && p < end - 1)
1291 p++; // step to next line
1295 //----- Text Information Routines ------------------------------
1296 static char *end_screen(void)
1301 // find new bottom line
1303 for (cnt = 0; cnt < rows - 2; cnt++)
1309 static int count_lines(char * start, char * stop) // count line from start to stop
1314 if (stop < start) { // start and stop are backwards- reverse them
1320 stop = end_line(stop); // get to end of this line
1321 for (q = start; q <= stop && q <= end - 1; q++) {
1328 static char *find_line(int li) // find begining of line #li
1332 for (q = text; li > 1; li--) {
1338 //----- Dot Movement Routines ----------------------------------
1339 static void dot_left(void)
1341 if (dot > text && dot[-1] != '\n')
1345 static void dot_right(void)
1347 if (dot < end - 1 && *dot != '\n')
1351 static void dot_begin(void)
1353 dot = begin_line(dot); // return pointer to first char cur line
1356 static void dot_end(void)
1358 dot = end_line(dot); // return pointer to last char cur line
1361 static char *move_to_col(char * p, int l)
1368 if (*p == '\n' || *p == '\0')
1372 co += ((tabstop - 1) - (co % tabstop));
1373 } else if (*p < ' ' || *p == 127) {
1374 co++; // display as ^X, use 2 columns
1376 } while (++co <= l && p++ < end);
1380 static void dot_next(void)
1382 dot = next_line(dot);
1385 static void dot_prev(void)
1387 dot = prev_line(dot);
1390 static void dot_scroll(int cnt, int dir)
1394 for (; cnt > 0; cnt--) {
1397 // ctrl-Y scroll up one line
1398 screenbegin = prev_line(screenbegin);
1401 // ctrl-E scroll down one line
1402 screenbegin = next_line(screenbegin);
1405 // make sure "dot" stays on the screen so we dont scroll off
1406 if (dot < screenbegin)
1408 q = end_screen(); // find new bottom line
1410 dot = begin_line(q); // is dot is below bottom line?
1414 static void dot_skip_over_ws(void)
1417 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1421 static void dot_delete(void) // delete the char at 'dot'
1423 text_hole_delete(dot, dot);
1426 static char *bound_dot(char * p) // make sure text[0] <= P < "end"
1428 if (p >= end && end > text) {
1430 indicate_error('1');
1434 indicate_error('2');
1439 //----- Helper Utility Routines --------------------------------
1441 //----------------------------------------------------------------
1442 //----- Char Routines --------------------------------------------
1443 /* Chars that are part of a word-
1444 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1445 * Chars that are Not part of a word (stoppers)
1446 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1447 * Chars that are WhiteSpace
1448 * TAB NEWLINE VT FF RETURN SPACE
1449 * DO NOT COUNT NEWLINE AS WHITESPACE
1452 static char *new_screen(int ro, int co)
1457 screensize = ro * co + 8;
1458 screen = xmalloc(screensize);
1459 // initialize the new screen. assume this will be a empty file.
1461 // non-existent text[] lines start with a tilde (~).
1462 for (li = 1; li < ro - 1; li++) {
1463 screen[(li * co) + 0] = '~';
1468 static char *new_text(int size)
1471 size = 10240; // have a minimum size for new files
1473 text = xmalloc(size + 8);
1474 memset(text, '\0', size); // clear new text[]
1475 //text += 4; // leave some room for "oops"
1479 #if ENABLE_FEATURE_VI_SEARCH
1480 static int mycmp(const char * s1, const char * s2, int len)
1484 i = strncmp(s1, s2, len);
1485 #if ENABLE_FEATURE_VI_SETOPTS
1487 i = strncasecmp(s1, s2, len);
1493 // search for pattern starting at p
1494 static char *char_search(char * p, const char * pat, int dir, int range)
1496 #ifndef REGEX_SEARCH
1501 if (dir == FORWARD) {
1502 stop = end - 1; // assume range is p - end-1
1503 if (range == LIMITED)
1504 stop = next_line(p); // range is to next line
1505 for (start = p; start < stop; start++) {
1506 if (mycmp(start, pat, len) == 0) {
1510 } else if (dir == BACK) {
1511 stop = text; // assume range is text - p
1512 if (range == LIMITED)
1513 stop = prev_line(p); // range is to prev line
1514 for (start = p - len; start >= stop; start--) {
1515 if (mycmp(start, pat, len) == 0) {
1520 // pattern not found
1522 #else /* REGEX_SEARCH */
1524 struct re_pattern_buffer preg;
1528 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1534 // assume a LIMITED forward search
1542 // count the number of chars to search over, forward or backward
1546 // RANGE could be negative if we are searching backwards
1549 q = re_compile_pattern(pat, strlen(pat), &preg);
1551 // The pattern was not compiled
1552 psbs("bad search pattern: \"%s\": %s", pat, q);
1553 i = 0; // return p if pattern not compiled
1563 // search for the compiled pattern, preg, in p[]
1564 // range < 0- search backward
1565 // range > 0- search forward
1567 // re_search() < 0 not found or error
1568 // re_search() > 0 index of found pattern
1569 // struct pattern char int int int struct reg
1570 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1571 i = re_search(&preg, q, size, 0, range, 0);
1574 i = 0; // return NULL if pattern not found
1577 if (dir == FORWARD) {
1583 #endif /* REGEX_SEARCH */
1585 #endif /* FEATURE_VI_SEARCH */
1587 static char *char_insert(char * p, char c) // insert the char c at 'p'
1589 if (c == 22) { // Is this an ctrl-V?
1590 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1591 p--; // backup onto ^
1592 refresh(FALSE); // show the ^
1596 file_modified++; // has the file been modified
1597 } else if (c == 27) { // Is this an ESC?
1600 end_cmd_q(); // stop adding to q
1601 last_status_cksum = 0; // force status update
1602 if ((p[-1] != '\n') && (dot > text)) {
1605 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1607 if ((p[-1] != '\n') && (dot>text)) {
1609 p = text_hole_delete(p, p); // shrink buffer 1 char
1612 // insert a char into text[]
1613 char *sp; // "save p"
1616 c = '\n'; // translate \r to \n
1617 sp = p; // remember addr of insert
1618 p = stupid_insert(p, c); // insert the char
1619 #if ENABLE_FEATURE_VI_SETOPTS
1620 if (showmatch && strchr(")]}", *sp) != NULL) {
1623 if (autoindent && c == '\n') { // auto indent the new line
1626 q = prev_line(p); // use prev line as templet
1627 for (; isblnk(*q); q++) {
1628 p = stupid_insert(p, *q); // insert the char
1636 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1638 p = text_hole_make(p, 1);
1641 file_modified++; // has the file been modified
1647 static char find_range(char ** start, char ** stop, char c)
1649 char *save_dot, *p, *q;
1655 if (strchr("cdy><", c)) {
1656 // these cmds operate on whole lines
1657 p = q = begin_line(p);
1658 for (cnt = 1; cnt < cmdcnt; cnt++) {
1662 } else if (strchr("^%$0bBeEft", c)) {
1663 // These cmds operate on char positions
1664 do_cmd(c); // execute movement cmd
1666 } else if (strchr("wW", c)) {
1667 do_cmd(c); // execute movement cmd
1668 // if we are at the next word's first char
1669 // step back one char
1670 // but check the possibilities when it is true
1671 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1672 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1673 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1674 dot--; // move back off of next word
1675 if (dot > text && *dot == '\n')
1676 dot--; // stay off NL
1678 } else if (strchr("H-k{", c)) {
1679 // these operate on multi-lines backwards
1680 q = end_line(dot); // find NL
1681 do_cmd(c); // execute movement cmd
1684 } else if (strchr("L+j}\r\n", c)) {
1685 // these operate on multi-lines forwards
1686 p = begin_line(dot);
1687 do_cmd(c); // execute movement cmd
1688 dot_end(); // find NL
1691 c = 27; // error- return an ESC char
1704 static int st_test(char * p, int type, int dir, char * tested)
1714 if (type == S_BEFORE_WS) {
1716 test = ((!isspace(c)) || c == '\n');
1718 if (type == S_TO_WS) {
1720 test = ((!isspace(c)) || c == '\n');
1722 if (type == S_OVER_WS) {
1724 test = ((isspace(c)));
1726 if (type == S_END_PUNCT) {
1728 test = ((ispunct(c)));
1730 if (type == S_END_ALNUM) {
1732 test = ((isalnum(c)) || c == '_');
1738 static char *skip_thing(char * p, int linecnt, int dir, int type)
1742 while (st_test(p, type, dir, &c)) {
1743 // make sure we limit search to correct number of lines
1744 if (c == '\n' && --linecnt < 1)
1746 if (dir >= 0 && p >= end - 1)
1748 if (dir < 0 && p <= text)
1750 p += dir; // move to next char
1755 // find matching char of pair () [] {}
1756 static char *find_pair(char * p, char c)
1763 dir = 1; // assume forward
1787 for (q = p + dir; text <= q && q < end; q += dir) {
1788 // look for match, count levels of pairs (( ))
1790 level++; // increase pair levels
1792 level--; // reduce pair level
1794 break; // found matching pair
1797 q = NULL; // indicate no match
1801 #if ENABLE_FEATURE_VI_SETOPTS
1802 // show the matching char of a pair, () [] {}
1803 static void showmatching(char * p)
1807 // we found half of a pair
1808 q = find_pair(p, *p); // get loc of matching char
1810 indicate_error('3'); // no matching char
1812 // "q" now points to matching pair
1813 save_dot = dot; // remember where we are
1814 dot = q; // go to new loc
1815 refresh(FALSE); // let the user see it
1816 mysleep(40); // give user some time
1817 dot = save_dot; // go back to old loc
1821 #endif /* FEATURE_VI_SETOPTS */
1823 // open a hole in text[]
1824 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1833 cnt = end - src; // the rest of buffer
1834 if (memmove(dest, src, cnt) != dest) {
1835 psbs("can't create room for new characters");
1837 memset(p, ' ', size); // clear new hole
1838 end = end + size; // adjust the new END
1839 file_modified++; // has the file been modified
1844 // close a hole in text[]
1845 static char *text_hole_delete(char * p, char * q) // delete "p" thru "q", inclusive
1850 // move forwards, from beginning
1854 if (q < p) { // they are backward- swap them
1858 hole_size = q - p + 1;
1860 if (src < text || src > end)
1862 if (dest < text || dest >= end)
1865 goto thd_atend; // just delete the end of the buffer
1866 if (memmove(dest, src, cnt) != dest) {
1867 psbs("can't delete the character");
1870 end = end - hole_size; // adjust the new END
1872 dest = end - 1; // make sure dest in below end-1
1874 dest = end = text; // keep pointers valid
1875 file_modified++; // has the file been modified
1880 // copy text into register, then delete text.
1881 // if dist <= 0, do not include, or go past, a NewLine
1883 static char *yank_delete(char * start, char * stop, int dist, int yf)
1887 // make sure start <= stop
1889 // they are backwards, reverse them
1895 // we cannot cross NL boundaries
1899 // dont go past a NewLine
1900 for (; p + 1 <= stop; p++) {
1902 stop = p; // "stop" just before NewLine
1908 #if ENABLE_FEATURE_VI_YANKMARK
1909 text_yank(start, stop, YDreg);
1911 if (yf == YANKDEL) {
1912 p = text_hole_delete(start, stop);
1917 static void show_help(void)
1919 puts("These features are available:"
1920 #if ENABLE_FEATURE_VI_SEARCH
1921 "\n\tPattern searches with / and ?"
1923 #if ENABLE_FEATURE_VI_DOT_CMD
1924 "\n\tLast command repeat with \'.\'"
1926 #if ENABLE_FEATURE_VI_YANKMARK
1927 "\n\tLine marking with 'x"
1928 "\n\tNamed buffers with \"x"
1930 #if ENABLE_FEATURE_VI_READONLY
1931 "\n\tReadonly if vi is called as \"view\""
1932 "\n\tReadonly with -R command line arg"
1934 #if ENABLE_FEATURE_VI_SET
1935 "\n\tSome colon mode commands with \':\'"
1937 #if ENABLE_FEATURE_VI_SETOPTS
1938 "\n\tSettable options with \":set\""
1940 #if ENABLE_FEATURE_VI_USE_SIGNALS
1941 "\n\tSignal catching- ^C"
1942 "\n\tJob suspend and resume with ^Z"
1944 #if ENABLE_FEATURE_VI_WIN_RESIZE
1945 "\n\tAdapt to window re-sizes"
1950 static inline void print_literal(char * buf, const char * s) // copy s to buf, convert unprintable
1963 c_is_no_print = (c & 0x80) && !Isprint(c);
1964 if (c_is_no_print) {
1968 if (c < ' ' || c == 127) {
1984 #if ENABLE_FEATURE_VI_DOT_CMD
1985 static void start_new_cmd_q(char c)
1988 free(last_modifying_cmd);
1989 // get buffer for new cmd
1990 last_modifying_cmd = xzalloc(MAX_LINELEN);
1991 // if there is a current cmd count put it in the buffer first
1993 sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1994 else // just save char c onto queue
1995 last_modifying_cmd[0] = c;
1999 static void end_cmd_q(void)
2001 #if ENABLE_FEATURE_VI_YANKMARK
2002 YDreg = 26; // go back to default Yank/Delete reg
2006 #endif /* FEATURE_VI_DOT_CMD */
2008 #if ENABLE_FEATURE_VI_YANKMARK \
2009 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2010 || ENABLE_FEATURE_VI_CRASHME
2011 static char *string_insert(char * p, char * s) // insert the string at 'p'
2016 p = text_hole_make(p, i);
2018 for (cnt = 0; *s != '\0'; s++) {
2022 #if ENABLE_FEATURE_VI_YANKMARK
2023 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2029 #if ENABLE_FEATURE_VI_YANKMARK
2030 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2035 if (q < p) { // they are backwards- reverse them
2042 free(t); // if already a yank register, free it
2043 t = xmalloc(cnt + 1); // get a new register
2044 memset(t, '\0', cnt + 1); // clear new text[]
2045 strncpy(t, p, cnt); // copy text[] into bufer
2050 static char what_reg(void)
2054 c = 'D'; // default to D-reg
2055 if (0 <= YDreg && YDreg <= 25)
2056 c = 'a' + (char) YDreg;
2064 static void check_context(char cmd)
2066 // A context is defined to be "modifying text"
2067 // Any modifying command establishes a new context.
2069 if (dot < context_start || dot > context_end) {
2070 if (strchr(modifying_cmds, cmd) != NULL) {
2071 // we are trying to modify text[]- make this the current context
2072 mark[27] = mark[26]; // move cur to prev
2073 mark[26] = dot; // move local to cur
2074 context_start = prev_line(prev_line(dot));
2075 context_end = next_line(next_line(dot));
2076 //loiter= start_loiter= now;
2081 static inline char *swap_context(char * p) // goto new context for '' command make this the current context
2085 // the current context is in mark[26]
2086 // the previous context is in mark[27]
2087 // only swap context if other context is valid
2088 if (text <= mark[27] && mark[27] <= end - 1) {
2090 mark[27] = mark[26];
2092 p = mark[26]; // where we are going- previous context
2093 context_start = prev_line(prev_line(prev_line(p)));
2094 context_end = next_line(next_line(next_line(p)));
2098 #endif /* FEATURE_VI_YANKMARK */
2100 static int isblnk(char c) // is the char a blank or tab
2102 return (c == ' ' || c == '\t');
2105 //----- Set terminal attributes --------------------------------
2106 static void rawmode(void)
2108 tcgetattr(0, &term_orig);
2109 term_vi = term_orig;
2110 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2111 term_vi.c_iflag &= (~IXON & ~ICRNL);
2112 term_vi.c_oflag &= (~ONLCR);
2113 term_vi.c_cc[VMIN] = 1;
2114 term_vi.c_cc[VTIME] = 0;
2115 erase_char = term_vi.c_cc[VERASE];
2116 tcsetattr(0, TCSANOW, &term_vi);
2119 static void cookmode(void)
2122 tcsetattr(0, TCSANOW, &term_orig);
2125 //----- Come here when we get a window resize signal ---------
2126 #if ENABLE_FEATURE_VI_USE_SIGNALS
2127 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2129 signal(SIGWINCH, winch_sig);
2130 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2131 get_terminal_width_height(0, &columns, &rows);
2132 new_screen(rows, columns); // get memory for virtual screen
2133 redraw(TRUE); // re-draw the screen
2136 //----- Come here when we get a continue signal -------------------
2137 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2139 rawmode(); // terminal to "raw"
2140 last_status_cksum = 0; // force status update
2141 redraw(TRUE); // re-draw the screen
2143 signal(SIGTSTP, suspend_sig);
2144 signal(SIGCONT, SIG_DFL);
2145 kill(my_pid, SIGCONT);
2148 //----- Come here when we get a Suspend signal -------------------
2149 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2151 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2152 clear_to_eol(); // Erase to end of line
2153 cookmode(); // terminal to "cooked"
2155 signal(SIGCONT, cont_sig);
2156 signal(SIGTSTP, SIG_DFL);
2157 kill(my_pid, SIGTSTP);
2160 //----- Come here when we get a signal ---------------------------
2161 static void catch_sig(int sig)
2163 signal(SIGINT, catch_sig);
2165 longjmp(restart, sig);
2167 #endif /* FEATURE_VI_USE_SIGNALS */
2169 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2174 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2179 tv.tv_usec = hund * 10000;
2180 select(1, &rfds, NULL, NULL, &tv);
2181 return FD_ISSET(0, &rfds);
2184 #define readbuffer bb_common_bufsiz1
2186 static int readed_for_parse;
2188 //----- IO Routines --------------------------------------------
2189 static char readit(void) // read (maybe cursor) key from stdin
2198 static const struct esc_cmds esccmds[] = {
2199 {"OA", VI_K_UP}, // cursor key Up
2200 {"OB", VI_K_DOWN}, // cursor key Down
2201 {"OC", VI_K_RIGHT}, // Cursor Key Right
2202 {"OD", VI_K_LEFT}, // cursor key Left
2203 {"OH", VI_K_HOME}, // Cursor Key Home
2204 {"OF", VI_K_END}, // Cursor Key End
2205 {"[A", VI_K_UP}, // cursor key Up
2206 {"[B", VI_K_DOWN}, // cursor key Down
2207 {"[C", VI_K_RIGHT}, // Cursor Key Right
2208 {"[D", VI_K_LEFT}, // cursor key Left
2209 {"[H", VI_K_HOME}, // Cursor Key Home
2210 {"[F", VI_K_END}, // Cursor Key End
2211 {"[1~", VI_K_HOME}, // Cursor Key Home
2212 {"[2~", VI_K_INSERT}, // Cursor Key Insert
2213 {"[4~", VI_K_END}, // Cursor Key End
2214 {"[5~", VI_K_PAGEUP}, // Cursor Key Page Up
2215 {"[6~", VI_K_PAGEDOWN},// Cursor Key Page Down
2216 {"OP", VI_K_FUN1}, // Function Key F1
2217 {"OQ", VI_K_FUN2}, // Function Key F2
2218 {"OR", VI_K_FUN3}, // Function Key F3
2219 {"OS", VI_K_FUN4}, // Function Key F4
2220 {"[15~", VI_K_FUN5}, // Function Key F5
2221 {"[17~", VI_K_FUN6}, // Function Key F6
2222 {"[18~", VI_K_FUN7}, // Function Key F7
2223 {"[19~", VI_K_FUN8}, // Function Key F8
2224 {"[20~", VI_K_FUN9}, // Function Key F9
2225 {"[21~", VI_K_FUN10}, // Function Key F10
2226 {"[23~", VI_K_FUN11}, // Function Key F11
2227 {"[24~", VI_K_FUN12}, // Function Key F12
2228 {"[11~", VI_K_FUN1}, // Function Key F1
2229 {"[12~", VI_K_FUN2}, // Function Key F2
2230 {"[13~", VI_K_FUN3}, // Function Key F3
2231 {"[14~", VI_K_FUN4}, // Function Key F4
2233 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2235 alarm(0); // turn alarm OFF while we wait for input
2237 n = readed_for_parse;
2238 // get input from User- are there already input chars in Q?
2241 // the Q is empty, wait for a typed char
2242 n = read(0, readbuffer, MAX_LINELEN - 1);
2245 goto ri0; // interrupted sys call
2248 if (errno == EFAULT)
2250 if (errno == EINVAL)
2258 if (readbuffer[0] == 27) {
2262 // This is an ESC char. Is this Esc sequence?
2263 // Could be bare Esc key. See if there are any
2264 // more chars to read after the ESC. This would
2265 // be a Function or Cursor Key sequence.
2269 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2271 // keep reading while there are input chars and room in buffer
2272 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (MAX_LINELEN - 5)) {
2273 // read the rest of the ESC string
2274 int r = read(0, (void *) (readbuffer + n), MAX_LINELEN - n);
2280 readed_for_parse = n;
2283 if (c == 27 && n > 1) {
2284 // Maybe cursor or function key?
2285 const struct esc_cmds *eindex;
2287 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2288 int cnt = strlen(eindex->seq);
2292 if (strncmp(eindex->seq, readbuffer + 1, cnt))
2294 // is a Cursor key- put derived value back into Q
2296 // for squeeze out the ESC sequence
2300 if (eindex == &esccmds[ESCCMDS_COUNT]) {
2301 /* defined ESC sequence not found, set only one ESC */
2307 // remove key sequence from Q
2308 readed_for_parse -= n;
2309 memmove(readbuffer, readbuffer + n, MAX_LINELEN - n);
2310 alarm(3); // we are done waiting for input, turn alarm ON
2314 //----- IO Routines --------------------------------------------
2315 static char get_one_char(void)
2319 #if ENABLE_FEATURE_VI_DOT_CMD
2320 // ! adding2q && ioq == 0 read()
2321 // ! adding2q && ioq != 0 *ioq
2322 // adding2q *last_modifying_cmd= read()
2324 // we are not adding to the q.
2325 // but, we may be reading from a q
2327 // there is no current q, read from STDIN
2328 c = readit(); // get the users input
2330 // there is a queue to get chars from first
2333 // the end of the q, read from STDIN
2335 ioq_start = ioq = 0;
2336 c = readit(); // get the users input
2340 // adding STDIN chars to q
2341 c = readit(); // get the users input
2342 if (last_modifying_cmd != 0) {
2343 int len = strlen(last_modifying_cmd);
2344 if (len >= MAX_LINELEN - 1) {
2345 psbs("last_modifying_cmd overrun");
2347 // add new char to q
2348 last_modifying_cmd[len] = c;
2353 c = readit(); // get the users input
2354 #endif /* FEATURE_VI_DOT_CMD */
2355 return c; // return the char, where ever it came from
2358 static char *get_input_line(const char * prompt) // get input line- use "status line"
2362 char buf[MAX_LINELEN];
2366 strcpy(buf, prompt);
2367 last_status_cksum = 0; // force status update
2368 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2369 clear_to_eol(); // clear the line
2370 write1(prompt); // write out the :, /, or ? prompt
2373 while (i < MAX_LINELEN) {
2374 c = get_one_char(); // read user input
2375 if (c == '\n' || c == '\r' || c == 27)
2376 break; // is this end of input
2377 if (c == erase_char || c == 8 || c == 127) {
2378 // user wants to erase prev char
2379 i--; // backup to prev char
2380 buf[i] = '\0'; // erase the char
2381 buf[i + 1] = '\0'; // null terminate buffer
2382 write1("\b \b"); // erase char on screen
2383 if (i <= 0) { // user backs up before b-o-l, exit
2387 buf[i] = c; // save char in buffer
2388 buf[i + 1] = '\0'; // make sure buffer is null terminated
2389 putchar(c); // echo the char back to user
2395 obufp = xstrdup(buf);
2399 static int file_size(const char * fn) // what is the byte size of "fn"
2407 sr = stat(fn, &st_buf); // see if file exists
2409 cnt = (int) st_buf.st_size;
2414 static int file_insert(char * fn, char * p, int size)
2419 #if ENABLE_FEATURE_VI_READONLY
2422 if (!fn || !fn[0]) {
2423 psbs("No filename given");
2427 // OK- this is just a no-op
2432 psbs("Trying to insert a negative number (%d) of characters", size);
2435 if (p < text || p > end) {
2436 psbs("Trying to insert file outside of memory");
2440 // see if we can open the file
2441 #if ENABLE_FEATURE_VI_READONLY
2442 if (vi_readonly) goto fi1; // do not try write-mode
2444 fd = open(fn, O_RDWR); // assume read & write
2446 // could not open for writing- maybe file is read only
2447 #if ENABLE_FEATURE_VI_READONLY
2450 fd = open(fn, O_RDONLY); // try read-only
2452 psbs("\"%s\" %s", fn, "cannot open file");
2455 #if ENABLE_FEATURE_VI_READONLY
2456 // got the file- read-only
2460 p = text_hole_make(p, size);
2461 cnt = read(fd, p, size);
2465 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2466 psbs("cannot read file \"%s\"", fn);
2467 } else if (cnt < size) {
2468 // There was a partial read, shrink unused space text[]
2469 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2470 psbs("cannot read all of file \"%s\"", fn);
2478 static int file_write(char * fn, char * first, char * last)
2480 int fd, cnt, charcnt;
2483 psbs("No current filename");
2487 // FIXIT- use the correct umask()
2488 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2491 cnt = last - first + 1;
2492 charcnt = write(fd, first, cnt);
2493 if (charcnt == cnt) {
2495 //file_modified = FALSE; // the file has not been modified
2503 //----- Terminal Drawing ---------------------------------------
2504 // The terminal is made up of 'rows' line of 'columns' columns.
2505 // classically this would be 24 x 80.
2506 // screen coordinates
2512 // 23,0 ... 23,79 status line
2515 //----- Move the cursor to row x col (count from 0, not 1) -------
2516 static void place_cursor(int row, int col, int opti)
2518 char cm1[MAX_LINELEN];
2520 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2521 char cm2[MAX_LINELEN];
2523 // char cm3[MAX_LINELEN];
2524 int Rrow = last_row;
2527 memset(cm1, '\0', MAX_LINELEN); // clear the buffer
2529 if (row < 0) row = 0;
2530 if (row >= rows) row = rows - 1;
2531 if (col < 0) col = 0;
2532 if (col >= columns) col = columns - 1;
2534 //----- 1. Try the standard terminal ESC sequence
2535 sprintf(cm1, CMrc, row + 1, col + 1);
2540 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2541 //----- find the minimum # of chars to move cursor -------------
2542 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2543 memset(cm2, '\0', MAX_LINELEN); // clear the buffer
2545 // move to the correct row
2546 while (row < Rrow) {
2547 // the cursor has to move up
2551 while (row > Rrow) {
2552 // the cursor has to move down
2553 strcat(cm2, CMdown);
2557 // now move to the correct column
2558 strcat(cm2, "\r"); // start at col 0
2559 // just send out orignal source char to get to correct place
2560 screenp = &screen[row * columns]; // start of screen line
2561 strncat(cm2, screenp, col);
2563 //----- 3. Try some other way of moving cursor
2564 //---------------------------------------------
2566 // pick the shortest cursor motion to send out
2568 if (strlen(cm2) < strlen(cm)) {
2570 } /* else if (strlen(cm3) < strlen(cm)) {
2573 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2575 write1(cm); // move the cursor
2578 //----- Erase from cursor to end of line -----------------------
2579 static void clear_to_eol(void)
2581 write1(Ceol); // Erase from cursor to end of line
2584 //----- Erase from cursor to end of screen -----------------------
2585 static void clear_to_eos(void)
2587 write1(Ceos); // Erase from cursor to end of screen
2590 //----- Start standout mode ------------------------------------
2591 static void standout_start(void) // send "start reverse video" sequence
2593 write1(SOs); // Start reverse video mode
2596 //----- End standout mode --------------------------------------
2597 static void standout_end(void) // send "end reverse video" sequence
2599 write1(SOn); // End reverse video mode
2602 //----- Flash the screen --------------------------------------
2603 static void flash(int h)
2605 standout_start(); // send "start reverse video" sequence
2608 standout_end(); // send "end reverse video" sequence
2612 static void Indicate_Error(void)
2614 #if ENABLE_FEATURE_VI_CRASHME
2616 return; // generate a random command
2619 write1(bell); // send out a bell character
2625 //----- Screen[] Routines --------------------------------------
2626 //----- Erase the Screen[] memory ------------------------------
2627 static void screen_erase(void)
2629 memset(screen, ' ', screensize); // clear new screen
2632 static int bufsum(char *buf, int count)
2635 char *e = buf + count;
2638 sum += (unsigned char) *buf++;
2642 //----- Draw the status line at bottom of the screen -------------
2643 static void show_status_line(void)
2645 int cnt = 0, cksum = 0;
2647 // either we already have an error or status message, or we
2649 if (!have_status_msg) {
2650 cnt = format_edit_status();
2651 cksum = bufsum(status_buffer, cnt);
2653 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2654 last_status_cksum= cksum; // remember if we have seen this line
2655 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2656 write1(status_buffer);
2658 if (have_status_msg) {
2659 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2661 have_status_msg = 0;
2664 have_status_msg = 0;
2666 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2671 //----- format the status buffer, the bottom line of screen ------
2672 // format status buffer, with STANDOUT mode
2673 static void psbs(const char *format, ...)
2677 va_start(args, format);
2678 strcpy(status_buffer, SOs); // Terminal standout mode on
2679 vsprintf(status_buffer + strlen(status_buffer), format, args);
2680 strcat(status_buffer, SOn); // Terminal standout mode off
2683 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2686 // format status buffer
2687 static void psb(const char *format, ...)
2691 va_start(args, format);
2692 vsprintf(status_buffer, format, args);
2695 have_status_msg = 1;
2698 static void ni(const char * s) // display messages
2700 char buf[MAX_LINELEN];
2702 print_literal(buf, s);
2703 psbs("\'%s\' is not implemented", buf);
2706 static int format_edit_status(void) // show file status on status line
2710 int cur, percent, ret, trunc_at;
2712 // file_modified is now a counter rather than a flag. this
2713 // helps reduce the amount of line counting we need to do.
2714 // (this will cause a mis-reporting of modified status
2715 // once every MAXINT editing operations.)
2717 // it would be nice to do a similar optimization here -- if
2718 // we haven't done a motion that could have changed which line
2719 // we're on, then we shouldn't have to do this count_lines()
2720 cur = count_lines(text, dot);
2722 // reduce counting -- the total lines can't have
2723 // changed if we haven't done any edits.
2724 if (file_modified != last_file_modified) {
2725 tot = cur + count_lines(dot, end - 1) - 1;
2726 last_file_modified = file_modified;
2729 // current line percent
2730 // ------------- ~~ ----------
2733 percent = (100 * cur) / tot;
2739 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2740 columns : STATUS_BUFFER_LEN-1;
2742 ret = snprintf(status_buffer, trunc_at+1,
2743 #if ENABLE_FEATURE_VI_READONLY
2744 "%c %s%s%s %d/%d %d%%",
2746 "%c %s%s %d/%d %d%%",
2748 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2749 (cfn != 0 ? cfn : "No file"),
2750 #if ENABLE_FEATURE_VI_READONLY
2751 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2753 (file_modified ? " [modified]" : ""),
2756 if (ret >= 0 && ret < trunc_at)
2757 return ret; /* it all fit */
2759 return trunc_at; /* had to truncate */
2762 //----- Force refresh of all Lines -----------------------------
2763 static void redraw(int full_screen)
2765 place_cursor(0, 0, FALSE); // put cursor in correct place
2766 clear_to_eos(); // tel terminal to erase display
2767 screen_erase(); // erase the internal screen buffer
2768 last_status_cksum = 0; // force status update
2769 refresh(full_screen); // this will redraw the entire display
2773 //----- Format a text[] line into a buffer ---------------------
2774 static void format_line(char *dest, char *src, int li)
2779 for (co = 0; co < MAX_SCR_COLS; co++) {
2780 c = ' '; // assume blank
2781 if (li > 0 && co == 0) {
2782 c = '~'; // not first line, assume Tilde
2784 // are there chars in text[] and have we gone past the end
2785 if (text < end && src < end) {
2790 if ((c & 0x80) && !Isprint(c)) {
2793 if ((unsigned char)(c) < ' ' || c == 0x7f) {
2797 for (; (co % tabstop) != (tabstop - 1); co++) {
2805 c += '@'; // make it visible
2808 // the co++ is done here so that the column will
2809 // not be overwritten when we blank-out the rest of line
2816 //----- Refresh the changed screen lines -----------------------
2817 // Copy the source line from text[] into the buffer and note
2818 // if the current screenline is different from the new buffer.
2819 // If they differ then that line needs redrawing on the terminal.
2821 static void refresh(int full_screen)
2823 static int old_offset;
2826 char buf[MAX_SCR_COLS];
2827 char *tp, *sp; // pointer into text[] and screen[]
2828 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2829 int last_li = -2; // last line that changed- for optimizing cursor movement
2832 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2833 get_terminal_width_height(0, &columns, &rows);
2834 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2835 tp = screenbegin; // index into text[] of top line
2837 // compare text[] to screen[] and mark screen[] lines that need updating
2838 for (li = 0; li < rows - 1; li++) {
2839 int cs, ce; // column start & end
2840 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2841 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2842 // format current text line into buf
2843 format_line(buf, tp, li);
2845 // skip to the end of the current text[] line
2846 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2848 // see if there are any changes between vitual screen and buf
2849 changed = FALSE; // assume no change
2852 sp = &screen[li * columns]; // start of screen line
2854 // force re-draw of every single column from 0 - columns-1
2857 // compare newly formatted buffer with virtual screen
2858 // look forward for first difference between buf and screen
2859 for (; cs <= ce; cs++) {
2860 if (buf[cs + offset] != sp[cs]) {
2861 changed = TRUE; // mark for redraw
2866 // look backward for last difference between buf and screen
2867 for ( ; ce >= cs; ce--) {
2868 if (buf[ce + offset] != sp[ce]) {
2869 changed = TRUE; // mark for redraw
2873 // now, cs is index of first diff, and ce is index of last diff
2875 // if horz offset has changed, force a redraw
2876 if (offset != old_offset) {
2881 // make a sanity check of columns indexes
2883 if (ce > columns-1) ce= columns-1;
2884 if (cs > ce) { cs= 0; ce= columns-1; }
2885 // is there a change between vitual screen and buf
2887 // copy changed part of buffer to virtual screen
2888 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2890 // move cursor to column of first change
2891 if (offset != old_offset) {
2892 // opti_cur_move is still too stupid
2893 // to handle offsets correctly
2894 place_cursor(li, cs, FALSE);
2896 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2897 // if this just the next line
2898 // try to optimize cursor movement
2899 // otherwise, use standard ESC sequence
2900 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2903 place_cursor(li, cs, FALSE); // use standard ESC sequence
2904 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2907 // write line out to terminal
2909 int nic = ce - cs + 1;
2910 char *out = sp + cs;
2917 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2923 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2924 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2927 place_cursor(crow, ccol, FALSE);
2930 if (offset != old_offset)
2931 old_offset = offset;
2934 //---------------------------------------------------------------------
2935 //----- the Ascii Chart -----------------------------------------------
2937 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2938 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2939 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2940 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2941 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2942 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2943 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2944 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2945 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2946 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2947 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2948 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2949 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2950 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2951 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2952 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2953 //---------------------------------------------------------------------
2955 //----- Execute a Vi Command -----------------------------------
2956 static void do_cmd(char c)
2959 char c1, *p, *q, buf[9], *save_dot;
2960 int cnt, i, j, dir, yf;
2962 c1 = c; // quiet the compiler
2963 cnt = yf = dir = 0; // quiet the compiler
2964 msg = p = q = save_dot = buf; // quiet the compiler
2965 memset(buf, '\0', 9); // clear buf
2969 /* if this is a cursor key, skip these checks */
2982 if (cmd_mode == 2) {
2983 // flip-flop Insert/Replace mode
2984 if (c == VI_K_INSERT)
2986 // we are 'R'eplacing the current *dot with new char
2988 // don't Replace past E-o-l
2989 cmd_mode = 1; // convert to insert
2991 if (1 <= c || Isprint(c)) {
2993 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2994 dot = char_insert(dot, c); // insert new char
2999 if (cmd_mode == 1) {
3000 // hitting "Insert" twice means "R" replace mode
3001 if (c == VI_K_INSERT) goto dc5;
3002 // insert the char c at "dot"
3003 if (1 <= c || Isprint(c)) {
3004 dot = char_insert(dot, c);
3019 #if ENABLE_FEATURE_VI_CRASHME
3020 case 0x14: // dc4 ctrl-T
3021 crashme = (crashme == 0) ? 1 : 0;
3052 //case 'u': // u- FIXME- there is no undo
3054 default: // unrecognised command
3063 end_cmd_q(); // stop adding to q
3064 case 0x00: // nul- ignore
3066 case 2: // ctrl-B scroll up full screen
3067 case VI_K_PAGEUP: // Cursor Key Page Up
3068 dot_scroll(rows - 2, -1);
3070 #if ENABLE_FEATURE_VI_USE_SIGNALS
3071 case 0x03: // ctrl-C interrupt
3072 longjmp(restart, 1);
3074 case 26: // ctrl-Z suspend
3075 suspend_sig(SIGTSTP);
3078 case 4: // ctrl-D scroll down half screen
3079 dot_scroll((rows - 2) / 2, 1);
3081 case 5: // ctrl-E scroll down one line
3084 case 6: // ctrl-F scroll down full screen
3085 case VI_K_PAGEDOWN: // Cursor Key Page Down
3086 dot_scroll(rows - 2, 1);
3088 case 7: // ctrl-G show current status
3089 last_status_cksum = 0; // force status update
3091 case 'h': // h- move left
3092 case VI_K_LEFT: // cursor key Left
3093 case 8: // ctrl-H- move left (This may be ERASE char)
3094 case 0x7f: // DEL- move left (This may be ERASE char)
3100 case 10: // Newline ^J
3101 case 'j': // j- goto next line, same col
3102 case VI_K_DOWN: // cursor key Down
3106 dot_next(); // go to next B-o-l
3107 dot = move_to_col(dot, ccol + offset); // try stay in same col
3109 case 12: // ctrl-L force redraw whole screen
3110 case 18: // ctrl-R force redraw
3111 place_cursor(0, 0, FALSE); // put cursor in correct place
3112 clear_to_eos(); // tel terminal to erase display
3114 screen_erase(); // erase the internal screen buffer
3115 last_status_cksum = 0; // force status update
3116 refresh(TRUE); // this will redraw the entire display
3118 case 13: // Carriage Return ^M
3119 case '+': // +- goto next line
3126 case 21: // ctrl-U scroll up half screen
3127 dot_scroll((rows - 2) / 2, -1);
3129 case 25: // ctrl-Y scroll up one line
3135 cmd_mode = 0; // stop insrting
3137 last_status_cksum = 0; // force status update
3139 case ' ': // move right
3140 case 'l': // move right
3141 case VI_K_RIGHT: // Cursor Key Right
3147 #if ENABLE_FEATURE_VI_YANKMARK
3148 case '"': // "- name a register to use for Delete/Yank
3149 c1 = get_one_char();
3157 case '\'': // '- goto a specific mark
3158 c1 = get_one_char();
3163 q = mark[(unsigned char) c1];
3164 if (text <= q && q < end) {
3166 dot_begin(); // go to B-o-l
3169 } else if (c1 == '\'') { // goto previous context
3170 dot = swap_context(dot); // swap current and previous context
3171 dot_begin(); // go to B-o-l
3177 case 'm': // m- Mark a line
3178 // this is really stupid. If there are any inserts or deletes
3179 // between text[0] and dot then this mark will not point to the
3180 // correct location! It could be off by many lines!
3181 // Well..., at least its quick and dirty.
3182 c1 = get_one_char();
3186 // remember the line
3187 mark[(int) c1] = dot;
3192 case 'P': // P- Put register before
3193 case 'p': // p- put register after
3196 psbs("Nothing in register %c", what_reg());
3199 // are we putting whole lines or strings
3200 if (strchr(p, '\n') != NULL) {
3202 dot_begin(); // putting lines- Put above
3205 // are we putting after very last line?
3206 if (end_line(dot) == (end - 1)) {
3207 dot = end; // force dot to end of text[]
3209 dot_next(); // next line, then put before
3214 dot_right(); // move to right, can move to NL
3216 dot = string_insert(dot, p); // insert the string
3217 end_cmd_q(); // stop adding to q
3219 case 'U': // U- Undo; replace current line with original version
3220 if (reg[Ureg] != 0) {
3221 p = begin_line(dot);
3223 p = text_hole_delete(p, q); // delete cur line
3224 p = string_insert(p, reg[Ureg]); // insert orig line
3229 #endif /* FEATURE_VI_YANKMARK */
3230 case '$': // $- goto end of line
3231 case VI_K_END: // Cursor Key End
3235 dot = end_line(dot);
3237 case '%': // %- find matching char of pair () [] {}
3238 for (q = dot; q < end && *q != '\n'; q++) {
3239 if (strchr("()[]{}", *q) != NULL) {
3240 // we found half of a pair
3241 p = find_pair(q, *q);
3253 case 'f': // f- forward to a user specified char
3254 last_forward_char = get_one_char(); // get the search char
3256 // dont separate these two commands. 'f' depends on ';'
3258 //**** fall thru to ... ';'
3259 case ';': // ;- look at rest of line for last forward char
3263 if (last_forward_char == 0)
3266 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3269 if (*q == last_forward_char)
3272 case '-': // -- goto prev line
3279 #if ENABLE_FEATURE_VI_DOT_CMD
3280 case '.': // .- repeat the last modifying command
3281 // Stuff the last_modifying_cmd back into stdin
3282 // and let it be re-executed.
3283 if (last_modifying_cmd != 0) {
3284 ioq = ioq_start = xstrdup(last_modifying_cmd);
3288 #if ENABLE_FEATURE_VI_SEARCH
3289 case '?': // /- search for a pattern
3290 case '/': // /- search for a pattern
3293 q = get_input_line(buf); // get input line- use "status line"
3295 goto dc3; // if no pat re-use old pat
3296 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3297 // there is a new pat
3298 free(last_search_pattern);
3299 last_search_pattern = xstrdup(q);
3300 goto dc3; // now find the pattern
3302 // user changed mind and erased the "/"- do nothing
3304 case 'N': // N- backward search for last pattern
3308 dir = BACK; // assume BACKWARD search
3310 if (last_search_pattern[0] == '?') {
3314 goto dc4; // now search for pattern
3316 case 'n': // n- repeat search for last pattern
3317 // search rest of text[] starting at next char
3318 // if search fails return orignal "p" not the "p+1" address
3323 if (last_search_pattern == 0) {
3324 msg = "No previous regular expression";
3327 if (last_search_pattern[0] == '/') {
3328 dir = FORWARD; // assume FORWARD search
3331 if (last_search_pattern[0] == '?') {
3336 q = char_search(p, last_search_pattern + 1, dir, FULL);
3338 dot = q; // good search, update "dot"
3342 // no pattern found between "dot" and "end"- continue at top
3347 q = char_search(p, last_search_pattern + 1, dir, FULL);
3348 if (q != NULL) { // found something
3349 dot = q; // found new pattern- goto it
3350 msg = "search hit BOTTOM, continuing at TOP";
3352 msg = "search hit TOP, continuing at BOTTOM";
3355 msg = "Pattern not found";
3361 case '{': // {- move backward paragraph
3362 q = char_search(dot, "\n\n", BACK, FULL);
3363 if (q != NULL) { // found blank line
3364 dot = next_line(q); // move to next blank line
3367 case '}': // }- move forward paragraph
3368 q = char_search(dot, "\n\n", FORWARD, FULL);
3369 if (q != NULL) { // found blank line
3370 dot = next_line(q); // move to next blank line
3373 #endif /* FEATURE_VI_SEARCH */
3374 case '0': // 0- goto begining of line
3384 if (c == '0' && cmdcnt < 1) {
3385 dot_begin(); // this was a standalone zero
3387 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3390 case ':': // :- the colon mode commands
3391 p = get_input_line(":"); // get input line- use "status line"
3392 #if ENABLE_FEATURE_VI_COLON
3393 colon(p); // execute the command
3396 p++; // move past the ':'
3400 if (strncasecmp(p, "quit", cnt) == 0
3401 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3403 if (file_modified && p[1] != '!') {
3404 psbs("No write since last change (:quit! overrides)");
3408 } else if (strncasecmp(p, "write", cnt) == 0
3409 || strncasecmp(p, "wq", cnt) == 0
3410 || strncasecmp(p, "wn", cnt) == 0
3411 || strncasecmp(p, "x", cnt) == 0
3413 cnt = file_write(cfn, text, end - 1);
3416 psbs("Write error: %s", strerror(errno));
3419 last_file_modified = -1;
3420 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3421 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3422 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3427 } else if (strncasecmp(p, "file", cnt) == 0) {
3428 last_status_cksum = 0; // force status update
3429 } else if (sscanf(p, "%d", &j) > 0) {
3430 dot = find_line(j); // go to line # j
3432 } else { // unrecognised cmd
3435 #endif /* !FEATURE_VI_COLON */
3437 case '<': // <- Left shift something
3438 case '>': // >- Right shift something
3439 cnt = count_lines(text, dot); // remember what line we are on
3440 c1 = get_one_char(); // get the type of thing to delete
3441 find_range(&p, &q, c1);
3442 yank_delete(p, q, 1, YANKONLY); // save copy before change
3445 i = count_lines(p, q); // # of lines we are shifting
3446 for ( ; i > 0; i--, p = next_line(p)) {
3448 // shift left- remove tab or 8 spaces
3450 // shrink buffer 1 char
3451 text_hole_delete(p, p);
3452 } else if (*p == ' ') {
3453 // we should be calculating columns, not just SPACE
3454 for (j = 0; *p == ' ' && j < tabstop; j++) {
3455 text_hole_delete(p, p);
3458 } else if (c == '>') {
3459 // shift right -- add tab or 8 spaces
3460 char_insert(p, '\t');
3463 dot = find_line(cnt); // what line were we on
3465 end_cmd_q(); // stop adding to q
3467 case 'A': // A- append at e-o-l
3468 dot_end(); // go to e-o-l
3469 //**** fall thru to ... 'a'
3470 case 'a': // a- append after current char
3475 case 'B': // B- back a blank-delimited Word
3476 case 'E': // E- end of a blank-delimited word
3477 case 'W': // W- forward a blank-delimited word
3484 if (c == 'W' || isspace(dot[dir])) {
3485 dot = skip_thing(dot, 1, dir, S_TO_WS);
3486 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3489 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3491 case 'C': // C- Change to e-o-l
3492 case 'D': // D- delete to e-o-l
3494 dot = dollar_line(dot); // move to before NL
3495 // copy text into a register and delete
3496 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3498 goto dc_i; // start inserting
3499 #if ENABLE_FEATURE_VI_DOT_CMD
3501 end_cmd_q(); // stop adding to q
3504 case 'G': // G- goto to a line number (default= E-O-F)
3505 dot = end - 1; // assume E-O-F
3507 dot = find_line(cmdcnt); // what line is #cmdcnt
3511 case 'H': // H- goto top line on screen
3513 if (cmdcnt > (rows - 1)) {
3514 cmdcnt = (rows - 1);
3521 case 'I': // I- insert before first non-blank
3524 //**** fall thru to ... 'i'
3525 case 'i': // i- insert before current char
3526 case VI_K_INSERT: // Cursor Key Insert
3528 cmd_mode = 1; // start insrting
3530 case 'J': // J- join current and next lines together
3534 dot_end(); // move to NL
3535 if (dot < end - 1) { // make sure not last char in text[]
3536 *dot++ = ' '; // replace NL with space
3538 while (isblnk(*dot)) { // delete leading WS
3542 end_cmd_q(); // stop adding to q
3544 case 'L': // L- goto bottom line on screen
3546 if (cmdcnt > (rows - 1)) {
3547 cmdcnt = (rows - 1);
3555 case 'M': // M- goto middle line on screen
3557 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3558 dot = next_line(dot);
3560 case 'O': // O- open a empty line above
3562 p = begin_line(dot);
3563 if (p[-1] == '\n') {
3565 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3567 dot = char_insert(dot, '\n');
3570 dot = char_insert(dot, '\n'); // i\n ESC
3575 case 'R': // R- continuous Replace char
3579 case 'X': // X- delete char before dot
3580 case 'x': // x- delete the current char
3581 case 's': // s- substitute the current char
3588 if (dot[dir] != '\n') {
3590 dot--; // delete prev char
3591 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3594 goto dc_i; // start insrting
3595 end_cmd_q(); // stop adding to q
3597 case 'Z': // Z- if modified, {write}; exit
3598 // ZZ means to save file (if necessary), then exit
3599 c1 = get_one_char();
3604 if (file_modified) {
3605 #if ENABLE_FEATURE_VI_READONLY
3606 if (vi_readonly || readonly) {
3607 psbs("\"%s\" File is read only", cfn);
3611 cnt = file_write(cfn, text, end - 1);
3614 psbs("Write error: %s", strerror(errno));
3615 } else if (cnt == (end - 1 - text + 1)) {
3622 case '^': // ^- move to first non-blank on line
3626 case 'b': // b- back a word
3627 case 'e': // e- end of word
3634 if ((dot + dir) < text || (dot + dir) > end - 1)
3637 if (isspace(*dot)) {
3638 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3640 if (isalnum(*dot) || *dot == '_') {
3641 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3642 } else if (ispunct(*dot)) {
3643 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3646 case 'c': // c- change something
3647 case 'd': // d- delete something
3648 #if ENABLE_FEATURE_VI_YANKMARK
3649 case 'y': // y- yank something
3650 case 'Y': // Y- Yank a line
3652 yf = YANKDEL; // assume either "c" or "d"
3653 #if ENABLE_FEATURE_VI_YANKMARK
3654 if (c == 'y' || c == 'Y')
3659 c1 = get_one_char(); // get the type of thing to delete
3660 find_range(&p, &q, c1);
3661 if (c1 == 27) { // ESC- user changed mind and wants out
3662 c = c1 = 27; // Escape- do nothing
3663 } else if (strchr("wW", c1)) {
3665 // don't include trailing WS as part of word
3666 while (isblnk(*q)) {
3667 if (q <= text || q[-1] == '\n')
3672 dot = yank_delete(p, q, 0, yf); // delete word
3673 } else if (strchr("^0bBeEft$", c1)) {
3674 // single line copy text into a register and delete
3675 dot = yank_delete(p, q, 0, yf); // delete word
3676 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3677 // multiple line copy text into a register and delete
3678 dot = yank_delete(p, q, 1, yf); // delete lines
3680 dot = char_insert(dot, '\n');
3681 // on the last line of file don't move to prev line
3682 if (dot != (end-1)) {
3685 } else if (c == 'd') {
3690 // could not recognize object
3691 c = c1 = 27; // error-
3695 // if CHANGING, not deleting, start inserting after the delete
3697 strcpy(buf, "Change");
3698 goto dc_i; // start inserting
3701 strcpy(buf, "Delete");
3703 #if ENABLE_FEATURE_VI_YANKMARK
3704 if (c == 'y' || c == 'Y') {
3705 strcpy(buf, "Yank");
3709 for (cnt = 0; p <= q; p++) {
3713 psb("%s %d lines (%d chars) using [%c]",
3714 buf, cnt, strlen(reg[YDreg]), what_reg());
3716 end_cmd_q(); // stop adding to q
3719 case 'k': // k- goto prev line, same col
3720 case VI_K_UP: // cursor key Up
3725 dot = move_to_col(dot, ccol + offset); // try stay in same col
3727 case 'r': // r- replace the current char with user input
3728 c1 = get_one_char(); // get the replacement char
3731 file_modified++; // has the file been modified
3733 end_cmd_q(); // stop adding to q
3735 case 't': // t- move to char prior to next x
3736 last_forward_char = get_one_char();
3738 if (*dot == last_forward_char)
3740 last_forward_char= 0;
3742 case 'w': // w- forward a word
3746 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3747 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3748 } else if (ispunct(*dot)) { // we are on PUNCT
3749 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3752 dot++; // move over word
3753 if (isspace(*dot)) {
3754 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3758 c1 = get_one_char(); // get the replacement char
3761 cnt = (rows - 2) / 2; // put dot at center
3763 cnt = rows - 2; // put dot at bottom
3764 screenbegin = begin_line(dot); // start dot at top
3765 dot_scroll(cnt, -1);
3767 case '|': // |- move to column "cmdcnt"
3768 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3770 case '~': // ~- flip the case of letters a-z -> A-Z
3774 if (islower(*dot)) {
3775 *dot = toupper(*dot);
3776 file_modified++; // has the file been modified
3777 } else if (isupper(*dot)) {
3778 *dot = tolower(*dot);
3779 file_modified++; // has the file been modified
3782 end_cmd_q(); // stop adding to q
3784 //----- The Cursor and Function Keys -----------------------------
3785 case VI_K_HOME: // Cursor Key Home
3788 // The Fn keys could point to do_macro which could translate them
3789 case VI_K_FUN1: // Function Key F1
3790 case VI_K_FUN2: // Function Key F2
3791 case VI_K_FUN3: // Function Key F3
3792 case VI_K_FUN4: // Function Key F4
3793 case VI_K_FUN5: // Function Key F5
3794 case VI_K_FUN6: // Function Key F6
3795 case VI_K_FUN7: // Function Key F7
3796 case VI_K_FUN8: // Function Key F8
3797 case VI_K_FUN9: // Function Key F9
3798 case VI_K_FUN10: // Function Key F10
3799 case VI_K_FUN11: // Function Key F11
3800 case VI_K_FUN12: // Function Key F12
3805 // if text[] just became empty, add back an empty line
3807 char_insert(text, '\n'); // start empty buf with dummy line
3810 // it is OK for dot to exactly equal to end, otherwise check dot validity
3812 dot = bound_dot(dot); // make sure "dot" is valid
3814 #if ENABLE_FEATURE_VI_YANKMARK
3815 check_context(c); // update the current context
3819 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3820 cnt = dot - begin_line(dot);
3821 // Try to stay off of the Newline
3822 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3826 #if ENABLE_FEATURE_VI_CRASHME
3827 static int totalcmds = 0;
3828 static int Mp = 85; // Movement command Probability
3829 static int Np = 90; // Non-movement command Probability
3830 static int Dp = 96; // Delete command Probability
3831 static int Ip = 97; // Insert command Probability
3832 static int Yp = 98; // Yank command Probability
3833 static int Pp = 99; // Put command Probability
3834 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3835 const char chars[20] = "\t012345 abcdABCD-=.$";
3836 const char *const words[20] = {
3837 "this", "is", "a", "test",
3838 "broadcast", "the", "emergency", "of",
3839 "system", "quick", "brown", "fox",
3840 "jumped", "over", "lazy", "dogs",
3841 "back", "January", "Febuary", "March"
3843 const char *const lines[20] = {
3844 "You should have received a copy of the GNU General Public License\n",
3845 "char c, cm, *cmd, *cmd1;\n",
3846 "generate a command by percentages\n",
3847 "Numbers may be typed as a prefix to some commands.\n",
3848 "Quit, discarding changes!\n",
3849 "Forced write, if permission originally not valid.\n",
3850 "In general, any ex or ed command (such as substitute or delete).\n",
3851 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3852 "Please get w/ me and I will go over it with you.\n",
3853 "The following is a list of scheduled, committed changes.\n",
3854 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3855 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3856 "Any question about transactions please contact Sterling Huxley.\n",
3857 "I will try to get back to you by Friday, December 31.\n",
3858 "This Change will be implemented on Friday.\n",
3859 "Let me know if you have problems accessing this;\n",
3860 "Sterling Huxley recently added you to the access list.\n",
3861 "Would you like to go to lunch?\n",
3862 "The last command will be automatically run.\n",
3863 "This is too much english for a computer geek.\n",
3865 char *multilines[20] = {
3866 "You should have received a copy of the GNU General Public License\n",
3867 "char c, cm, *cmd, *cmd1;\n",
3868 "generate a command by percentages\n",
3869 "Numbers may be typed as a prefix to some commands.\n",
3870 "Quit, discarding changes!\n",
3871 "Forced write, if permission originally not valid.\n",
3872 "In general, any ex or ed command (such as substitute or delete).\n",
3873 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3874 "Please get w/ me and I will go over it with you.\n",
3875 "The following is a list of scheduled, committed changes.\n",
3876 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3877 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3878 "Any question about transactions please contact Sterling Huxley.\n",
3879 "I will try to get back to you by Friday, December 31.\n",
3880 "This Change will be implemented on Friday.\n",
3881 "Let me know if you have problems accessing this;\n",
3882 "Sterling Huxley recently added you to the access list.\n",
3883 "Would you like to go to lunch?\n",
3884 "The last command will be automatically run.\n",
3885 "This is too much english for a computer geek.\n",
3888 // create a random command to execute
3889 static void crash_dummy()
3891 static int sleeptime; // how long to pause between commands
3892 char c, cm, *cmd, *cmd1;
3893 int i, cnt, thing, rbi, startrbi, percent;
3895 // "dot" movement commands
3896 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3898 // is there already a command running?
3899 if (readed_for_parse > 0)
3903 sleeptime = 0; // how long to pause between commands
3904 memset(readbuffer, '\0', MAX_LINELEN); // clear the read buffer
3905 // generate a command by percentages
3906 percent = (int) lrand48() % 100; // get a number from 0-99
3907 if (percent < Mp) { // Movement commands
3908 // available commands
3911 } else if (percent < Np) { // non-movement commands
3912 cmd = "mz<>\'\""; // available commands
3914 } else if (percent < Dp) { // Delete commands
3915 cmd = "dx"; // available commands
3917 } else if (percent < Ip) { // Inset commands
3918 cmd = "iIaAsrJ"; // available commands
3920 } else if (percent < Yp) { // Yank commands
3921 cmd = "yY"; // available commands
3923 } else if (percent < Pp) { // Put commands
3924 cmd = "pP"; // available commands
3927 // We do not know how to handle this command, try again
3931 // randomly pick one of the available cmds from "cmd[]"
3932 i = (int) lrand48() % strlen(cmd);
3934 if (strchr(":\024", cm))
3935 goto cd0; // dont allow colon or ctrl-T commands
3936 readbuffer[rbi++] = cm; // put cmd into input buffer
3938 // now we have the command-
3939 // there are 1, 2, and multi char commands
3940 // find out which and generate the rest of command as necessary
3941 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3942 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3943 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3944 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3946 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3948 readbuffer[rbi++] = c; // add movement to input buffer
3950 if (strchr("iIaAsc", cm)) { // multi-char commands
3952 // change some thing
3953 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3955 readbuffer[rbi++] = c; // add movement to input buffer
3957 thing = (int) lrand48() % 4; // what thing to insert
3958 cnt = (int) lrand48() % 10; // how many to insert
3959 for (i = 0; i < cnt; i++) {
3960 if (thing == 0) { // insert chars
3961 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3962 } else if (thing == 1) { // insert words
3963 strcat(readbuffer, words[(int) lrand48() % 20]);
3964 strcat(readbuffer, " ");
3965 sleeptime = 0; // how fast to type
3966 } else if (thing == 2) { // insert lines
3967 strcat(readbuffer, lines[(int) lrand48() % 20]);
3968 sleeptime = 0; // how fast to type
3969 } else { // insert multi-lines
3970 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3971 sleeptime = 0; // how fast to type
3974 strcat(readbuffer, "\033");
3976 readed_for_parse = strlen(readbuffer);
3980 mysleep(sleeptime); // sleep 1/100 sec
3983 // test to see if there are any errors
3984 static void crash_test()
3986 static time_t oldtim;
3989 char d[2], msg[MAX_LINELEN];
3993 strcat(msg, "end<text ");
3995 if (end > textend) {
3996 strcat(msg, "end>textend ");
3999 strcat(msg, "dot<text ");
4002 strcat(msg, "dot>end ");
4004 if (screenbegin < text) {
4005 strcat(msg, "screenbegin<text ");
4007 if (screenbegin > end - 1) {
4008 strcat(msg, "screenbegin>end-1 ");
4013 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4014 totalcmds, last_input_char, msg, SOs, SOn);
4016 while (read(0, d, 1) > 0) {
4017 if (d[0] == '\n' || d[0] == '\r')
4022 tim = (time_t) time((time_t *) 0);
4023 if (tim >= (oldtim + 3)) {
4024 sprintf(status_buffer,
4025 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4026 totalcmds, M, N, I, D, Y, P, U, end - text + 1);