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
2234 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2236 alarm(0); // turn alarm OFF while we wait for input
2238 n = readed_for_parse;
2239 // get input from User- are there already input chars in Q?
2242 // the Q is empty, wait for a typed char
2243 n = read(0, readbuffer, MAX_LINELEN - 1);
2246 goto ri0; // interrupted sys call
2249 if (errno == EFAULT)
2251 if (errno == EINVAL)
2259 if (readbuffer[0] == 27) {
2263 // This is an ESC char. Is this Esc sequence?
2264 // Could be bare Esc key. See if there are any
2265 // more chars to read after the ESC. This would
2266 // be a Function or Cursor Key sequence.
2270 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2272 // keep reading while there are input chars and room in buffer
2273 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (MAX_LINELEN - 5)) {
2274 // read the rest of the ESC string
2275 int r = read(0, (void *) (readbuffer + n), MAX_LINELEN - n);
2281 readed_for_parse = n;
2284 if (c == 27 && n > 1) {
2285 // Maybe cursor or function key?
2286 const struct esc_cmds *eindex;
2288 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2289 int cnt = strlen(eindex->seq);
2293 if (strncmp(eindex->seq, readbuffer + 1, cnt))
2295 // is a Cursor key- put derived value back into Q
2297 // for squeeze out the ESC sequence
2301 if (eindex == &esccmds[ESCCMDS_COUNT]) {
2302 /* defined ESC sequence not found, set only one ESC */
2308 // remove key sequence from Q
2309 readed_for_parse -= n;
2310 memmove(readbuffer, readbuffer + n, MAX_LINELEN - n);
2311 alarm(3); // we are done waiting for input, turn alarm ON
2315 //----- IO Routines --------------------------------------------
2316 static char get_one_char(void)
2320 #if ENABLE_FEATURE_VI_DOT_CMD
2321 // ! adding2q && ioq == 0 read()
2322 // ! adding2q && ioq != 0 *ioq
2323 // adding2q *last_modifying_cmd= read()
2325 // we are not adding to the q.
2326 // but, we may be reading from a q
2328 // there is no current q, read from STDIN
2329 c = readit(); // get the users input
2331 // there is a queue to get chars from first
2334 // the end of the q, read from STDIN
2336 ioq_start = ioq = 0;
2337 c = readit(); // get the users input
2341 // adding STDIN chars to q
2342 c = readit(); // get the users input
2343 if (last_modifying_cmd != 0) {
2344 int len = strlen(last_modifying_cmd);
2345 if (len >= MAX_LINELEN - 1) {
2346 psbs("last_modifying_cmd overrun");
2348 // add new char to q
2349 last_modifying_cmd[len] = c;
2354 c = readit(); // get the users input
2355 #endif /* FEATURE_VI_DOT_CMD */
2356 return c; // return the char, where ever it came from
2359 static char *get_input_line(const char * prompt) // get input line- use "status line"
2363 char buf[MAX_LINELEN];
2367 strcpy(buf, prompt);
2368 last_status_cksum = 0; // force status update
2369 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2370 clear_to_eol(); // clear the line
2371 write1(prompt); // write out the :, /, or ? prompt
2374 while (i < MAX_LINELEN) {
2375 c = get_one_char(); // read user input
2376 if (c == '\n' || c == '\r' || c == 27)
2377 break; // is this end of input
2378 if (c == erase_char || c == 8 || c == 127) {
2379 // user wants to erase prev char
2380 i--; // backup to prev char
2381 buf[i] = '\0'; // erase the char
2382 buf[i + 1] = '\0'; // null terminate buffer
2383 write1("\b \b"); // erase char on screen
2384 if (i <= 0) { // user backs up before b-o-l, exit
2388 buf[i] = c; // save char in buffer
2389 buf[i + 1] = '\0'; // make sure buffer is null terminated
2390 putchar(c); // echo the char back to user
2396 obufp = xstrdup(buf);
2400 static int file_size(const char * fn) // what is the byte size of "fn"
2408 sr = stat(fn, &st_buf); // see if file exists
2410 cnt = (int) st_buf.st_size;
2415 static int file_insert(char * fn, char * p, int size)
2420 #if ENABLE_FEATURE_VI_READONLY
2423 if (!fn || !fn[0]) {
2424 psbs("No filename given");
2428 // OK- this is just a no-op
2433 psbs("Trying to insert a negative number (%d) of characters", size);
2436 if (p < text || p > end) {
2437 psbs("Trying to insert file outside of memory");
2441 // see if we can open the file
2442 #if ENABLE_FEATURE_VI_READONLY
2443 if (vi_readonly) goto fi1; // do not try write-mode
2445 fd = open(fn, O_RDWR); // assume read & write
2447 // could not open for writing- maybe file is read only
2448 #if ENABLE_FEATURE_VI_READONLY
2451 fd = open(fn, O_RDONLY); // try read-only
2453 psbs("\"%s\" %s", fn, "cannot open file");
2456 #if ENABLE_FEATURE_VI_READONLY
2457 // got the file- read-only
2461 p = text_hole_make(p, size);
2462 cnt = read(fd, p, size);
2466 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2467 psbs("cannot read file \"%s\"", fn);
2468 } else if (cnt < size) {
2469 // There was a partial read, shrink unused space text[]
2470 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2471 psbs("cannot read all of file \"%s\"", fn);
2479 static int file_write(char * fn, char * first, char * last)
2481 int fd, cnt, charcnt;
2484 psbs("No current filename");
2488 // FIXIT- use the correct umask()
2489 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2492 cnt = last - first + 1;
2493 charcnt = write(fd, first, cnt);
2494 if (charcnt == cnt) {
2496 //file_modified = FALSE; // the file has not been modified
2504 //----- Terminal Drawing ---------------------------------------
2505 // The terminal is made up of 'rows' line of 'columns' columns.
2506 // classically this would be 24 x 80.
2507 // screen coordinates
2513 // 23,0 ... 23,79 status line
2516 //----- Move the cursor to row x col (count from 0, not 1) -------
2517 static void place_cursor(int row, int col, int opti)
2519 char cm1[MAX_LINELEN];
2521 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2522 char cm2[MAX_LINELEN];
2524 // char cm3[MAX_LINELEN];
2525 int Rrow = last_row;
2528 memset(cm1, '\0', MAX_LINELEN); // clear the buffer
2530 if (row < 0) row = 0;
2531 if (row >= rows) row = rows - 1;
2532 if (col < 0) col = 0;
2533 if (col >= columns) col = columns - 1;
2535 //----- 1. Try the standard terminal ESC sequence
2536 sprintf(cm1, CMrc, row + 1, col + 1);
2541 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2542 //----- find the minimum # of chars to move cursor -------------
2543 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2544 memset(cm2, '\0', MAX_LINELEN); // clear the buffer
2546 // move to the correct row
2547 while (row < Rrow) {
2548 // the cursor has to move up
2552 while (row > Rrow) {
2553 // the cursor has to move down
2554 strcat(cm2, CMdown);
2558 // now move to the correct column
2559 strcat(cm2, "\r"); // start at col 0
2560 // just send out orignal source char to get to correct place
2561 screenp = &screen[row * columns]; // start of screen line
2562 strncat(cm2, screenp, col);
2564 //----- 3. Try some other way of moving cursor
2565 //---------------------------------------------
2567 // pick the shortest cursor motion to send out
2569 if (strlen(cm2) < strlen(cm)) {
2571 } /* else if (strlen(cm3) < strlen(cm)) {
2574 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2576 write1(cm); // move the cursor
2579 //----- Erase from cursor to end of line -----------------------
2580 static void clear_to_eol(void)
2582 write1(Ceol); // Erase from cursor to end of line
2585 //----- Erase from cursor to end of screen -----------------------
2586 static void clear_to_eos(void)
2588 write1(Ceos); // Erase from cursor to end of screen
2591 //----- Start standout mode ------------------------------------
2592 static void standout_start(void) // send "start reverse video" sequence
2594 write1(SOs); // Start reverse video mode
2597 //----- End standout mode --------------------------------------
2598 static void standout_end(void) // send "end reverse video" sequence
2600 write1(SOn); // End reverse video mode
2603 //----- Flash the screen --------------------------------------
2604 static void flash(int h)
2606 standout_start(); // send "start reverse video" sequence
2609 standout_end(); // send "end reverse video" sequence
2613 static void Indicate_Error(void)
2615 #if ENABLE_FEATURE_VI_CRASHME
2617 return; // generate a random command
2620 write1(bell); // send out a bell character
2626 //----- Screen[] Routines --------------------------------------
2627 //----- Erase the Screen[] memory ------------------------------
2628 static void screen_erase(void)
2630 memset(screen, ' ', screensize); // clear new screen
2633 static int bufsum(char *buf, int count)
2636 char *e = buf + count;
2639 sum += (unsigned char) *buf++;
2643 //----- Draw the status line at bottom of the screen -------------
2644 static void show_status_line(void)
2646 int cnt = 0, cksum = 0;
2648 // either we already have an error or status message, or we
2650 if (!have_status_msg) {
2651 cnt = format_edit_status();
2652 cksum = bufsum(status_buffer, cnt);
2654 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2655 last_status_cksum= cksum; // remember if we have seen this line
2656 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2657 write1(status_buffer);
2659 if (have_status_msg) {
2660 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2662 have_status_msg = 0;
2665 have_status_msg = 0;
2667 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2672 //----- format the status buffer, the bottom line of screen ------
2673 // format status buffer, with STANDOUT mode
2674 static void psbs(const char *format, ...)
2678 va_start(args, format);
2679 strcpy(status_buffer, SOs); // Terminal standout mode on
2680 vsprintf(status_buffer + strlen(status_buffer), format, args);
2681 strcat(status_buffer, SOn); // Terminal standout mode off
2684 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2687 // format status buffer
2688 static void psb(const char *format, ...)
2692 va_start(args, format);
2693 vsprintf(status_buffer, format, args);
2696 have_status_msg = 1;
2699 static void ni(const char * s) // display messages
2701 char buf[MAX_LINELEN];
2703 print_literal(buf, s);
2704 psbs("\'%s\' is not implemented", buf);
2707 static int format_edit_status(void) // show file status on status line
2711 int cur, percent, ret, trunc_at;
2713 // file_modified is now a counter rather than a flag. this
2714 // helps reduce the amount of line counting we need to do.
2715 // (this will cause a mis-reporting of modified status
2716 // once every MAXINT editing operations.)
2718 // it would be nice to do a similar optimization here -- if
2719 // we haven't done a motion that could have changed which line
2720 // we're on, then we shouldn't have to do this count_lines()
2721 cur = count_lines(text, dot);
2723 // reduce counting -- the total lines can't have
2724 // changed if we haven't done any edits.
2725 if (file_modified != last_file_modified) {
2726 tot = cur + count_lines(dot, end - 1) - 1;
2727 last_file_modified = file_modified;
2730 // current line percent
2731 // ------------- ~~ ----------
2734 percent = (100 * cur) / tot;
2740 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2741 columns : STATUS_BUFFER_LEN-1;
2743 ret = snprintf(status_buffer, trunc_at+1,
2744 #if ENABLE_FEATURE_VI_READONLY
2745 "%c %s%s%s %d/%d %d%%",
2747 "%c %s%s %d/%d %d%%",
2749 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2750 (cfn != 0 ? cfn : "No file"),
2751 #if ENABLE_FEATURE_VI_READONLY
2752 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2754 (file_modified ? " [modified]" : ""),
2757 if (ret >= 0 && ret < trunc_at)
2758 return ret; /* it all fit */
2760 return trunc_at; /* had to truncate */
2763 //----- Force refresh of all Lines -----------------------------
2764 static void redraw(int full_screen)
2766 place_cursor(0, 0, FALSE); // put cursor in correct place
2767 clear_to_eos(); // tel terminal to erase display
2768 screen_erase(); // erase the internal screen buffer
2769 last_status_cksum = 0; // force status update
2770 refresh(full_screen); // this will redraw the entire display
2774 //----- Format a text[] line into a buffer ---------------------
2775 static void format_line(char *dest, char *src, int li)
2780 for (co = 0; co < MAX_SCR_COLS; co++) {
2781 c = ' '; // assume blank
2782 if (li > 0 && co == 0) {
2783 c = '~'; // not first line, assume Tilde
2785 // are there chars in text[] and have we gone past the end
2786 if (text < end && src < end) {
2791 if ((c & 0x80) && !Isprint(c)) {
2794 if ((unsigned char)(c) < ' ' || c == 0x7f) {
2798 for (; (co % tabstop) != (tabstop - 1); co++) {
2806 c += '@'; // make it visible
2809 // the co++ is done here so that the column will
2810 // not be overwritten when we blank-out the rest of line
2817 //----- Refresh the changed screen lines -----------------------
2818 // Copy the source line from text[] into the buffer and note
2819 // if the current screenline is different from the new buffer.
2820 // If they differ then that line needs redrawing on the terminal.
2822 static void refresh(int full_screen)
2824 static int old_offset;
2827 char buf[MAX_SCR_COLS];
2828 char *tp, *sp; // pointer into text[] and screen[]
2829 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2830 int last_li = -2; // last line that changed- for optimizing cursor movement
2833 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2834 get_terminal_width_height(0, &columns, &rows);
2835 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2836 tp = screenbegin; // index into text[] of top line
2838 // compare text[] to screen[] and mark screen[] lines that need updating
2839 for (li = 0; li < rows - 1; li++) {
2840 int cs, ce; // column start & end
2841 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2842 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2843 // format current text line into buf
2844 format_line(buf, tp, li);
2846 // skip to the end of the current text[] line
2847 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2849 // see if there are any changes between vitual screen and buf
2850 changed = FALSE; // assume no change
2853 sp = &screen[li * columns]; // start of screen line
2855 // force re-draw of every single column from 0 - columns-1
2858 // compare newly formatted buffer with virtual screen
2859 // look forward for first difference between buf and screen
2860 for (; cs <= ce; cs++) {
2861 if (buf[cs + offset] != sp[cs]) {
2862 changed = TRUE; // mark for redraw
2867 // look backward for last difference between buf and screen
2868 for ( ; ce >= cs; ce--) {
2869 if (buf[ce + offset] != sp[ce]) {
2870 changed = TRUE; // mark for redraw
2874 // now, cs is index of first diff, and ce is index of last diff
2876 // if horz offset has changed, force a redraw
2877 if (offset != old_offset) {
2882 // make a sanity check of columns indexes
2884 if (ce > columns-1) ce= columns-1;
2885 if (cs > ce) { cs= 0; ce= columns-1; }
2886 // is there a change between vitual screen and buf
2888 // copy changed part of buffer to virtual screen
2889 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2891 // move cursor to column of first change
2892 if (offset != old_offset) {
2893 // opti_cur_move is still too stupid
2894 // to handle offsets correctly
2895 place_cursor(li, cs, FALSE);
2897 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2898 // if this just the next line
2899 // try to optimize cursor movement
2900 // otherwise, use standard ESC sequence
2901 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2904 place_cursor(li, cs, FALSE); // use standard ESC sequence
2905 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2908 // write line out to terminal
2910 int nic = ce - cs + 1;
2911 char *out = sp + cs;
2918 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2924 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2925 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2928 place_cursor(crow, ccol, FALSE);
2931 if (offset != old_offset)
2932 old_offset = offset;
2935 //---------------------------------------------------------------------
2936 //----- the Ascii Chart -----------------------------------------------
2938 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2939 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2940 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2941 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2942 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2943 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2944 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2945 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2946 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2947 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2948 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2949 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2950 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2951 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2952 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2953 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2954 //---------------------------------------------------------------------
2956 //----- Execute a Vi Command -----------------------------------
2957 static void do_cmd(char c)
2960 char c1, *p, *q, buf[9], *save_dot;
2961 int cnt, i, j, dir, yf;
2963 c1 = c; // quiet the compiler
2964 cnt = yf = dir = 0; // quiet the compiler
2965 msg = p = q = save_dot = buf; // quiet the compiler
2966 memset(buf, '\0', 9); // clear buf
2970 /* if this is a cursor key, skip these checks */
2983 if (cmd_mode == 2) {
2984 // flip-flop Insert/Replace mode
2985 if (c == VI_K_INSERT)
2987 // we are 'R'eplacing the current *dot with new char
2989 // don't Replace past E-o-l
2990 cmd_mode = 1; // convert to insert
2992 if (1 <= c || Isprint(c)) {
2994 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2995 dot = char_insert(dot, c); // insert new char
3000 if (cmd_mode == 1) {
3001 // hitting "Insert" twice means "R" replace mode
3002 if (c == VI_K_INSERT) goto dc5;
3003 // insert the char c at "dot"
3004 if (1 <= c || Isprint(c)) {
3005 dot = char_insert(dot, c);
3020 #if ENABLE_FEATURE_VI_CRASHME
3021 case 0x14: // dc4 ctrl-T
3022 crashme = (crashme == 0) ? 1 : 0;
3053 //case 'u': // u- FIXME- there is no undo
3055 default: // unrecognised command
3064 end_cmd_q(); // stop adding to q
3065 case 0x00: // nul- ignore
3067 case 2: // ctrl-B scroll up full screen
3068 case VI_K_PAGEUP: // Cursor Key Page Up
3069 dot_scroll(rows - 2, -1);
3071 #if ENABLE_FEATURE_VI_USE_SIGNALS
3072 case 0x03: // ctrl-C interrupt
3073 longjmp(restart, 1);
3075 case 26: // ctrl-Z suspend
3076 suspend_sig(SIGTSTP);
3079 case 4: // ctrl-D scroll down half screen
3080 dot_scroll((rows - 2) / 2, 1);
3082 case 5: // ctrl-E scroll down one line
3085 case 6: // ctrl-F scroll down full screen
3086 case VI_K_PAGEDOWN: // Cursor Key Page Down
3087 dot_scroll(rows - 2, 1);
3089 case 7: // ctrl-G show current status
3090 last_status_cksum = 0; // force status update
3092 case 'h': // h- move left
3093 case VI_K_LEFT: // cursor key Left
3094 case 8: // ctrl-H- move left (This may be ERASE char)
3095 case 0x7f: // DEL- move left (This may be ERASE char)
3101 case 10: // Newline ^J
3102 case 'j': // j- goto next line, same col
3103 case VI_K_DOWN: // cursor key Down
3107 dot_next(); // go to next B-o-l
3108 dot = move_to_col(dot, ccol + offset); // try stay in same col
3110 case 12: // ctrl-L force redraw whole screen
3111 case 18: // ctrl-R force redraw
3112 place_cursor(0, 0, FALSE); // put cursor in correct place
3113 clear_to_eos(); // tel terminal to erase display
3115 screen_erase(); // erase the internal screen buffer
3116 last_status_cksum = 0; // force status update
3117 refresh(TRUE); // this will redraw the entire display
3119 case 13: // Carriage Return ^M
3120 case '+': // +- goto next line
3127 case 21: // ctrl-U scroll up half screen
3128 dot_scroll((rows - 2) / 2, -1);
3130 case 25: // ctrl-Y scroll up one line
3136 cmd_mode = 0; // stop insrting
3138 last_status_cksum = 0; // force status update
3140 case ' ': // move right
3141 case 'l': // move right
3142 case VI_K_RIGHT: // Cursor Key Right
3148 #if ENABLE_FEATURE_VI_YANKMARK
3149 case '"': // "- name a register to use for Delete/Yank
3150 c1 = get_one_char();
3158 case '\'': // '- goto a specific mark
3159 c1 = get_one_char();
3164 q = mark[(unsigned char) c1];
3165 if (text <= q && q < end) {
3167 dot_begin(); // go to B-o-l
3170 } else if (c1 == '\'') { // goto previous context
3171 dot = swap_context(dot); // swap current and previous context
3172 dot_begin(); // go to B-o-l
3178 case 'm': // m- Mark a line
3179 // this is really stupid. If there are any inserts or deletes
3180 // between text[0] and dot then this mark will not point to the
3181 // correct location! It could be off by many lines!
3182 // Well..., at least its quick and dirty.
3183 c1 = get_one_char();
3187 // remember the line
3188 mark[(int) c1] = dot;
3193 case 'P': // P- Put register before
3194 case 'p': // p- put register after
3197 psbs("Nothing in register %c", what_reg());
3200 // are we putting whole lines or strings
3201 if (strchr(p, '\n') != NULL) {
3203 dot_begin(); // putting lines- Put above
3206 // are we putting after very last line?
3207 if (end_line(dot) == (end - 1)) {
3208 dot = end; // force dot to end of text[]
3210 dot_next(); // next line, then put before
3215 dot_right(); // move to right, can move to NL
3217 dot = string_insert(dot, p); // insert the string
3218 end_cmd_q(); // stop adding to q
3220 case 'U': // U- Undo; replace current line with original version
3221 if (reg[Ureg] != 0) {
3222 p = begin_line(dot);
3224 p = text_hole_delete(p, q); // delete cur line
3225 p = string_insert(p, reg[Ureg]); // insert orig line
3230 #endif /* FEATURE_VI_YANKMARK */
3231 case '$': // $- goto end of line
3232 case VI_K_END: // Cursor Key End
3236 dot = end_line(dot);
3238 case '%': // %- find matching char of pair () [] {}
3239 for (q = dot; q < end && *q != '\n'; q++) {
3240 if (strchr("()[]{}", *q) != NULL) {
3241 // we found half of a pair
3242 p = find_pair(q, *q);
3254 case 'f': // f- forward to a user specified char
3255 last_forward_char = get_one_char(); // get the search char
3257 // dont separate these two commands. 'f' depends on ';'
3259 //**** fall thru to ... ';'
3260 case ';': // ;- look at rest of line for last forward char
3264 if (last_forward_char == 0)
3267 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3270 if (*q == last_forward_char)
3273 case '-': // -- goto prev line
3280 #if ENABLE_FEATURE_VI_DOT_CMD
3281 case '.': // .- repeat the last modifying command
3282 // Stuff the last_modifying_cmd back into stdin
3283 // and let it be re-executed.
3284 if (last_modifying_cmd != 0) {
3285 ioq = ioq_start = xstrdup(last_modifying_cmd);
3289 #if ENABLE_FEATURE_VI_SEARCH
3290 case '?': // /- search for a pattern
3291 case '/': // /- search for a pattern
3294 q = get_input_line(buf); // get input line- use "status line"
3296 goto dc3; // if no pat re-use old pat
3297 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3298 // there is a new pat
3299 free(last_search_pattern);
3300 last_search_pattern = xstrdup(q);
3301 goto dc3; // now find the pattern
3303 // user changed mind and erased the "/"- do nothing
3305 case 'N': // N- backward search for last pattern
3309 dir = BACK; // assume BACKWARD search
3311 if (last_search_pattern[0] == '?') {
3315 goto dc4; // now search for pattern
3317 case 'n': // n- repeat search for last pattern
3318 // search rest of text[] starting at next char
3319 // if search fails return orignal "p" not the "p+1" address
3324 if (last_search_pattern == 0) {
3325 msg = "No previous regular expression";
3328 if (last_search_pattern[0] == '/') {
3329 dir = FORWARD; // assume FORWARD search
3332 if (last_search_pattern[0] == '?') {
3337 q = char_search(p, last_search_pattern + 1, dir, FULL);
3339 dot = q; // good search, update "dot"
3343 // no pattern found between "dot" and "end"- continue at top
3348 q = char_search(p, last_search_pattern + 1, dir, FULL);
3349 if (q != NULL) { // found something
3350 dot = q; // found new pattern- goto it
3351 msg = "search hit BOTTOM, continuing at TOP";
3353 msg = "search hit TOP, continuing at BOTTOM";
3356 msg = "Pattern not found";
3362 case '{': // {- move backward paragraph
3363 q = char_search(dot, "\n\n", BACK, FULL);
3364 if (q != NULL) { // found blank line
3365 dot = next_line(q); // move to next blank line
3368 case '}': // }- move forward paragraph
3369 q = char_search(dot, "\n\n", FORWARD, FULL);
3370 if (q != NULL) { // found blank line
3371 dot = next_line(q); // move to next blank line
3374 #endif /* FEATURE_VI_SEARCH */
3375 case '0': // 0- goto begining of line
3385 if (c == '0' && cmdcnt < 1) {
3386 dot_begin(); // this was a standalone zero
3388 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3391 case ':': // :- the colon mode commands
3392 p = get_input_line(":"); // get input line- use "status line"
3393 #if ENABLE_FEATURE_VI_COLON
3394 colon(p); // execute the command
3397 p++; // move past the ':'
3401 if (strncasecmp(p, "quit", cnt) == 0
3402 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3404 if (file_modified && p[1] != '!') {
3405 psbs("No write since last change (:quit! overrides)");
3409 } else if (strncasecmp(p, "write", cnt) == 0
3410 || strncasecmp(p, "wq", cnt) == 0
3411 || strncasecmp(p, "wn", cnt) == 0
3412 || strncasecmp(p, "x", cnt) == 0
3414 cnt = file_write(cfn, text, end - 1);
3417 psbs("Write error: %s", strerror(errno));
3420 last_file_modified = -1;
3421 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3422 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3423 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3428 } else if (strncasecmp(p, "file", cnt) == 0) {
3429 last_status_cksum = 0; // force status update
3430 } else if (sscanf(p, "%d", &j) > 0) {
3431 dot = find_line(j); // go to line # j
3433 } else { // unrecognised cmd
3436 #endif /* !FEATURE_VI_COLON */
3438 case '<': // <- Left shift something
3439 case '>': // >- Right shift something
3440 cnt = count_lines(text, dot); // remember what line we are on
3441 c1 = get_one_char(); // get the type of thing to delete
3442 find_range(&p, &q, c1);
3443 yank_delete(p, q, 1, YANKONLY); // save copy before change
3446 i = count_lines(p, q); // # of lines we are shifting
3447 for ( ; i > 0; i--, p = next_line(p)) {
3449 // shift left- remove tab or 8 spaces
3451 // shrink buffer 1 char
3452 text_hole_delete(p, p);
3453 } else if (*p == ' ') {
3454 // we should be calculating columns, not just SPACE
3455 for (j = 0; *p == ' ' && j < tabstop; j++) {
3456 text_hole_delete(p, p);
3459 } else if (c == '>') {
3460 // shift right -- add tab or 8 spaces
3461 char_insert(p, '\t');
3464 dot = find_line(cnt); // what line were we on
3466 end_cmd_q(); // stop adding to q
3468 case 'A': // A- append at e-o-l
3469 dot_end(); // go to e-o-l
3470 //**** fall thru to ... 'a'
3471 case 'a': // a- append after current char
3476 case 'B': // B- back a blank-delimited Word
3477 case 'E': // E- end of a blank-delimited word
3478 case 'W': // W- forward a blank-delimited word
3485 if (c == 'W' || isspace(dot[dir])) {
3486 dot = skip_thing(dot, 1, dir, S_TO_WS);
3487 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3490 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3492 case 'C': // C- Change to e-o-l
3493 case 'D': // D- delete to e-o-l
3495 dot = dollar_line(dot); // move to before NL
3496 // copy text into a register and delete
3497 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3499 goto dc_i; // start inserting
3500 #if ENABLE_FEATURE_VI_DOT_CMD
3502 end_cmd_q(); // stop adding to q
3505 case 'G': // G- goto to a line number (default= E-O-F)
3506 dot = end - 1; // assume E-O-F
3508 dot = find_line(cmdcnt); // what line is #cmdcnt
3512 case 'H': // H- goto top line on screen
3514 if (cmdcnt > (rows - 1)) {
3515 cmdcnt = (rows - 1);
3522 case 'I': // I- insert before first non-blank
3525 //**** fall thru to ... 'i'
3526 case 'i': // i- insert before current char
3527 case VI_K_INSERT: // Cursor Key Insert
3529 cmd_mode = 1; // start insrting
3531 case 'J': // J- join current and next lines together
3535 dot_end(); // move to NL
3536 if (dot < end - 1) { // make sure not last char in text[]
3537 *dot++ = ' '; // replace NL with space
3539 while (isblnk(*dot)) { // delete leading WS
3543 end_cmd_q(); // stop adding to q
3545 case 'L': // L- goto bottom line on screen
3547 if (cmdcnt > (rows - 1)) {
3548 cmdcnt = (rows - 1);
3556 case 'M': // M- goto middle line on screen
3558 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3559 dot = next_line(dot);
3561 case 'O': // O- open a empty line above
3563 p = begin_line(dot);
3564 if (p[-1] == '\n') {
3566 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3568 dot = char_insert(dot, '\n');
3571 dot = char_insert(dot, '\n'); // i\n ESC
3576 case 'R': // R- continuous Replace char
3580 case 'X': // X- delete char before dot
3581 case 'x': // x- delete the current char
3582 case 's': // s- substitute the current char
3589 if (dot[dir] != '\n') {
3591 dot--; // delete prev char
3592 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3595 goto dc_i; // start insrting
3596 end_cmd_q(); // stop adding to q
3598 case 'Z': // Z- if modified, {write}; exit
3599 // ZZ means to save file (if necessary), then exit
3600 c1 = get_one_char();
3605 if (file_modified) {
3606 #if ENABLE_FEATURE_VI_READONLY
3607 if (vi_readonly || readonly) {
3608 psbs("\"%s\" File is read only", cfn);
3612 cnt = file_write(cfn, text, end - 1);
3615 psbs("Write error: %s", strerror(errno));
3616 } else if (cnt == (end - 1 - text + 1)) {
3623 case '^': // ^- move to first non-blank on line
3627 case 'b': // b- back a word
3628 case 'e': // e- end of word
3635 if ((dot + dir) < text || (dot + dir) > end - 1)
3638 if (isspace(*dot)) {
3639 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3641 if (isalnum(*dot) || *dot == '_') {
3642 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3643 } else if (ispunct(*dot)) {
3644 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3647 case 'c': // c- change something
3648 case 'd': // d- delete something
3649 #if ENABLE_FEATURE_VI_YANKMARK
3650 case 'y': // y- yank something
3651 case 'Y': // Y- Yank a line
3653 yf = YANKDEL; // assume either "c" or "d"
3654 #if ENABLE_FEATURE_VI_YANKMARK
3655 if (c == 'y' || c == 'Y')
3660 c1 = get_one_char(); // get the type of thing to delete
3661 find_range(&p, &q, c1);
3662 if (c1 == 27) { // ESC- user changed mind and wants out
3663 c = c1 = 27; // Escape- do nothing
3664 } else if (strchr("wW", c1)) {
3666 // don't include trailing WS as part of word
3667 while (isblnk(*q)) {
3668 if (q <= text || q[-1] == '\n')
3673 dot = yank_delete(p, q, 0, yf); // delete word
3674 } else if (strchr("^0bBeEft$", c1)) {
3675 // single line copy text into a register and delete
3676 dot = yank_delete(p, q, 0, yf); // delete word
3677 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3678 // multiple line copy text into a register and delete
3679 dot = yank_delete(p, q, 1, yf); // delete lines
3681 dot = char_insert(dot, '\n');
3682 // on the last line of file don't move to prev line
3683 if (dot != (end-1)) {
3686 } else if (c == 'd') {
3691 // could not recognize object
3692 c = c1 = 27; // error-
3696 // if CHANGING, not deleting, start inserting after the delete
3698 strcpy(buf, "Change");
3699 goto dc_i; // start inserting
3702 strcpy(buf, "Delete");
3704 #if ENABLE_FEATURE_VI_YANKMARK
3705 if (c == 'y' || c == 'Y') {
3706 strcpy(buf, "Yank");
3710 for (cnt = 0; p <= q; p++) {
3714 psb("%s %d lines (%d chars) using [%c]",
3715 buf, cnt, strlen(reg[YDreg]), what_reg());
3717 end_cmd_q(); // stop adding to q
3720 case 'k': // k- goto prev line, same col
3721 case VI_K_UP: // cursor key Up
3726 dot = move_to_col(dot, ccol + offset); // try stay in same col
3728 case 'r': // r- replace the current char with user input
3729 c1 = get_one_char(); // get the replacement char
3732 file_modified++; // has the file been modified
3734 end_cmd_q(); // stop adding to q
3736 case 't': // t- move to char prior to next x
3737 last_forward_char = get_one_char();
3739 if (*dot == last_forward_char)
3741 last_forward_char= 0;
3743 case 'w': // w- forward a word
3747 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3748 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3749 } else if (ispunct(*dot)) { // we are on PUNCT
3750 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3753 dot++; // move over word
3754 if (isspace(*dot)) {
3755 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3759 c1 = get_one_char(); // get the replacement char
3762 cnt = (rows - 2) / 2; // put dot at center
3764 cnt = rows - 2; // put dot at bottom
3765 screenbegin = begin_line(dot); // start dot at top
3766 dot_scroll(cnt, -1);
3768 case '|': // |- move to column "cmdcnt"
3769 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3771 case '~': // ~- flip the case of letters a-z -> A-Z
3775 if (islower(*dot)) {
3776 *dot = toupper(*dot);
3777 file_modified++; // has the file been modified
3778 } else if (isupper(*dot)) {
3779 *dot = tolower(*dot);
3780 file_modified++; // has the file been modified
3783 end_cmd_q(); // stop adding to q
3785 //----- The Cursor and Function Keys -----------------------------
3786 case VI_K_HOME: // Cursor Key Home
3789 // The Fn keys could point to do_macro which could translate them
3790 case VI_K_FUN1: // Function Key F1
3791 case VI_K_FUN2: // Function Key F2
3792 case VI_K_FUN3: // Function Key F3
3793 case VI_K_FUN4: // Function Key F4
3794 case VI_K_FUN5: // Function Key F5
3795 case VI_K_FUN6: // Function Key F6
3796 case VI_K_FUN7: // Function Key F7
3797 case VI_K_FUN8: // Function Key F8
3798 case VI_K_FUN9: // Function Key F9
3799 case VI_K_FUN10: // Function Key F10
3800 case VI_K_FUN11: // Function Key F11
3801 case VI_K_FUN12: // Function Key F12
3806 // if text[] just became empty, add back an empty line
3808 char_insert(text, '\n'); // start empty buf with dummy line
3811 // it is OK for dot to exactly equal to end, otherwise check dot validity
3813 dot = bound_dot(dot); // make sure "dot" is valid
3815 #if ENABLE_FEATURE_VI_YANKMARK
3816 check_context(c); // update the current context
3820 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3821 cnt = dot - begin_line(dot);
3822 // Try to stay off of the Newline
3823 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3827 #if ENABLE_FEATURE_VI_CRASHME
3828 static int totalcmds = 0;
3829 static int Mp = 85; // Movement command Probability
3830 static int Np = 90; // Non-movement command Probability
3831 static int Dp = 96; // Delete command Probability
3832 static int Ip = 97; // Insert command Probability
3833 static int Yp = 98; // Yank command Probability
3834 static int Pp = 99; // Put command Probability
3835 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3836 const char chars[20] = "\t012345 abcdABCD-=.$";
3837 const char *const words[20] = {
3838 "this", "is", "a", "test",
3839 "broadcast", "the", "emergency", "of",
3840 "system", "quick", "brown", "fox",
3841 "jumped", "over", "lazy", "dogs",
3842 "back", "January", "Febuary", "March"
3844 const char *const lines[20] = {
3845 "You should have received a copy of the GNU General Public License\n",
3846 "char c, cm, *cmd, *cmd1;\n",
3847 "generate a command by percentages\n",
3848 "Numbers may be typed as a prefix to some commands.\n",
3849 "Quit, discarding changes!\n",
3850 "Forced write, if permission originally not valid.\n",
3851 "In general, any ex or ed command (such as substitute or delete).\n",
3852 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3853 "Please get w/ me and I will go over it with you.\n",
3854 "The following is a list of scheduled, committed changes.\n",
3855 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3856 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3857 "Any question about transactions please contact Sterling Huxley.\n",
3858 "I will try to get back to you by Friday, December 31.\n",
3859 "This Change will be implemented on Friday.\n",
3860 "Let me know if you have problems accessing this;\n",
3861 "Sterling Huxley recently added you to the access list.\n",
3862 "Would you like to go to lunch?\n",
3863 "The last command will be automatically run.\n",
3864 "This is too much english for a computer geek.\n",
3866 char *multilines[20] = {
3867 "You should have received a copy of the GNU General Public License\n",
3868 "char c, cm, *cmd, *cmd1;\n",
3869 "generate a command by percentages\n",
3870 "Numbers may be typed as a prefix to some commands.\n",
3871 "Quit, discarding changes!\n",
3872 "Forced write, if permission originally not valid.\n",
3873 "In general, any ex or ed command (such as substitute or delete).\n",
3874 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3875 "Please get w/ me and I will go over it with you.\n",
3876 "The following is a list of scheduled, committed changes.\n",
3877 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3878 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3879 "Any question about transactions please contact Sterling Huxley.\n",
3880 "I will try to get back to you by Friday, December 31.\n",
3881 "This Change will be implemented on Friday.\n",
3882 "Let me know if you have problems accessing this;\n",
3883 "Sterling Huxley recently added you to the access list.\n",
3884 "Would you like to go to lunch?\n",
3885 "The last command will be automatically run.\n",
3886 "This is too much english for a computer geek.\n",
3889 // create a random command to execute
3890 static void crash_dummy()
3892 static int sleeptime; // how long to pause between commands
3893 char c, cm, *cmd, *cmd1;
3894 int i, cnt, thing, rbi, startrbi, percent;
3896 // "dot" movement commands
3897 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3899 // is there already a command running?
3900 if (readed_for_parse > 0)
3904 sleeptime = 0; // how long to pause between commands
3905 memset(readbuffer, '\0', MAX_LINELEN); // clear the read buffer
3906 // generate a command by percentages
3907 percent = (int) lrand48() % 100; // get a number from 0-99
3908 if (percent < Mp) { // Movement commands
3909 // available commands
3912 } else if (percent < Np) { // non-movement commands
3913 cmd = "mz<>\'\""; // available commands
3915 } else if (percent < Dp) { // Delete commands
3916 cmd = "dx"; // available commands
3918 } else if (percent < Ip) { // Inset commands
3919 cmd = "iIaAsrJ"; // available commands
3921 } else if (percent < Yp) { // Yank commands
3922 cmd = "yY"; // available commands
3924 } else if (percent < Pp) { // Put commands
3925 cmd = "pP"; // available commands
3928 // We do not know how to handle this command, try again
3932 // randomly pick one of the available cmds from "cmd[]"
3933 i = (int) lrand48() % strlen(cmd);
3935 if (strchr(":\024", cm))
3936 goto cd0; // dont allow colon or ctrl-T commands
3937 readbuffer[rbi++] = cm; // put cmd into input buffer
3939 // now we have the command-
3940 // there are 1, 2, and multi char commands
3941 // find out which and generate the rest of command as necessary
3942 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3943 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3944 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3945 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3947 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3949 readbuffer[rbi++] = c; // add movement to input buffer
3951 if (strchr("iIaAsc", cm)) { // multi-char commands
3953 // change some thing
3954 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3956 readbuffer[rbi++] = c; // add movement to input buffer
3958 thing = (int) lrand48() % 4; // what thing to insert
3959 cnt = (int) lrand48() % 10; // how many to insert
3960 for (i = 0; i < cnt; i++) {
3961 if (thing == 0) { // insert chars
3962 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3963 } else if (thing == 1) { // insert words
3964 strcat(readbuffer, words[(int) lrand48() % 20]);
3965 strcat(readbuffer, " ");
3966 sleeptime = 0; // how fast to type
3967 } else if (thing == 2) { // insert lines
3968 strcat(readbuffer, lines[(int) lrand48() % 20]);
3969 sleeptime = 0; // how fast to type
3970 } else { // insert multi-lines
3971 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3972 sleeptime = 0; // how fast to type
3975 strcat(readbuffer, "\033");
3977 readed_for_parse = strlen(readbuffer);
3981 mysleep(sleeptime); // sleep 1/100 sec
3984 // test to see if there are any errors
3985 static void crash_test()
3987 static time_t oldtim;
3990 char d[2], msg[MAX_LINELEN];
3994 strcat(msg, "end<text ");
3996 if (end > textend) {
3997 strcat(msg, "end>textend ");
4000 strcat(msg, "dot<text ");
4003 strcat(msg, "dot>end ");
4005 if (screenbegin < text) {
4006 strcat(msg, "screenbegin<text ");
4008 if (screenbegin > end - 1) {
4009 strcat(msg, "screenbegin>end-1 ");
4014 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4015 totalcmds, last_input_char, msg, SOs, SOn);
4017 while (read(0, d, 1) > 0) {
4018 if (d[0] == '\n' || d[0] == '\r')
4023 tim = (time_t) time((time_t *) 0);
4024 if (tim >= (oldtim + 3)) {
4025 sprintf(status_buffer,
4026 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4027 totalcmds, M, N, I, D, Y, P, U, end - text + 1);