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
29 #if ENABLE_LOCALE_SUPPORT
31 #if ENABLE_FEATURE_VI_8BIT
32 #define Isprint(c) isprint(c)
34 #define Isprint(c) (isprint(c) && (unsigned char)(c) < 0x7f)
39 /* 0x9b is Meta-ESC */
40 #if ENABLE_FEATURE_VI_8BIT
41 #define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
43 #define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
50 MAX_TABSTOP = 32, // sanity limit
51 // User input len. Need not be extra big.
52 // Lines in file being edited *can* be bigger than this.
54 // Sanity limits. We have only one buffer of this size.
55 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
56 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
59 // Misc. non-Ascii keys that report an escape sequence
60 #define VI_K_UP (char)128 // cursor key Up
61 #define VI_K_DOWN (char)129 // cursor key Down
62 #define VI_K_RIGHT (char)130 // Cursor Key Right
63 #define VI_K_LEFT (char)131 // cursor key Left
64 #define VI_K_HOME (char)132 // Cursor Key Home
65 #define VI_K_END (char)133 // Cursor Key End
66 #define VI_K_INSERT (char)134 // Cursor Key Insert
67 #define VI_K_DELETE (char)135 // Cursor Key Insert
68 #define VI_K_PAGEUP (char)136 // Cursor Key Page Up
69 #define VI_K_PAGEDOWN (char)137 // Cursor Key Page Down
70 #define VI_K_FUN1 (char)138 // Function Key F1
71 #define VI_K_FUN2 (char)139 // Function Key F2
72 #define VI_K_FUN3 (char)140 // Function Key F3
73 #define VI_K_FUN4 (char)141 // Function Key F4
74 #define VI_K_FUN5 (char)142 // Function Key F5
75 #define VI_K_FUN6 (char)143 // Function Key F6
76 #define VI_K_FUN7 (char)144 // Function Key F7
77 #define VI_K_FUN8 (char)145 // Function Key F8
78 #define VI_K_FUN9 (char)146 // Function Key F9
79 #define VI_K_FUN10 (char)147 // Function Key F10
80 #define VI_K_FUN11 (char)148 // Function Key F11
81 #define VI_K_FUN12 (char)149 // Function Key F12
83 /* vt102 typical ESC sequence */
84 /* terminal standout start/normal ESC sequence */
85 static const char SOs[] ALIGN1 = "\033[7m";
86 static const char SOn[] ALIGN1 = "\033[0m";
87 /* terminal bell sequence */
88 static const char bell[] ALIGN1 = "\007";
89 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
90 static const char Ceol[] ALIGN1 = "\033[0K";
91 static const char Ceos[] ALIGN1 = "\033[0J";
92 /* Cursor motion arbitrary destination ESC sequence */
93 static const char CMrc[] ALIGN1 = "\033[%d;%dH";
94 /* Cursor motion up and down ESC sequence */
95 static const char CMup[] ALIGN1 = "\033[A";
96 static const char CMdown[] ALIGN1 = "\n";
102 FORWARD = 1, // code depends on "1" for array index
103 BACK = -1, // code depends on "-1" for array index
104 LIMITED = 0, // how much of text[] in char_search
105 FULL = 1, // how much of text[] in char_search
107 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
108 S_TO_WS = 2, // used in skip_thing() for moving "dot"
109 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
110 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
111 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
114 /* vi.c expects chars to be unsigned. */
115 /* busybox build system provides that, but it's better */
116 /* to audit and fix the source */
118 static smallint vi_setops;
119 #define VI_AUTOINDENT 1
120 #define VI_SHOWMATCH 2
121 #define VI_IGNORECASE 4
122 #define VI_ERR_METHOD 8
123 #define autoindent (vi_setops & VI_AUTOINDENT)
124 #define showmatch (vi_setops & VI_SHOWMATCH )
125 #define ignorecase (vi_setops & VI_IGNORECASE)
126 /* indicate error with beep or flash */
127 #define err_method (vi_setops & VI_ERR_METHOD)
130 static smallint editing; // >0 while we are editing a file
131 // [code audit says "can be 0 or 1 only"]
132 static smallint cmd_mode; // 0=command 1=insert 2=replace
133 static smallint file_modified; // buffer contents changed
134 static smallint last_file_modified = -1;
135 static int fn_start; // index of first cmd line file name
136 static int save_argc; // how many file names on cmd line
137 static int cmdcnt; // repetition count
138 static int rows, columns; // the terminal screen is this size
139 static int crow, ccol; // cursor is on Crow x Ccol
140 static int offset; // chars scrolled off the screen to the left
141 static char *status_buffer; // mesages to the user
142 #define STATUS_BUFFER_LEN 200
143 static int have_status_msg; // is default edit status needed?
144 // [don't make smallint!]
145 static int last_status_cksum; // hash of current status line
146 static char *current_filename; // current file name
147 //static char *text, *end; // pointers to the user data in memory
148 static char *screen; // pointer to the virtual screen buffer
149 static int screensize; // and its size
150 static char *screenbegin; // index into text[], of top line on the screen
151 //static char *dot; // where all the action takes place
153 static char erase_char; // the users erase character
154 static char last_input_char; // last char read from user
155 static char last_forward_char; // last char searched for with 'f'
157 #if ENABLE_FEATURE_VI_READONLY
158 //static smallint vi_readonly, readonly;
159 static smallint readonly_mode = 0;
160 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
161 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
162 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
164 #define readonly_mode 0
165 #define SET_READONLY_FILE(flags)
166 #define SET_READONLY_MODE(flags)
167 #define UNSET_READONLY_FILE(flags)
170 #if ENABLE_FEATURE_VI_DOT_CMD
171 static smallint adding2q; // are we currently adding user input to q
172 static char *last_modifying_cmd; // [MAX_INPUT_LEN] last modifying cmd for "."
173 static char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
175 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
176 static int last_row; // where the cursor was last moved to
178 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
181 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
182 static char *modifying_cmds; // cmds that modify text[]
184 #if ENABLE_FEATURE_VI_SEARCH
185 static char *last_search_pattern; // last pattern from a '/' or '?' search
188 /* Moving biggest data to malloced space... */
190 /* many references - keep near the top of globals */
191 char *text, *end; // pointers to the user data in memory
192 char *dot; // where all the action takes place
193 int text_size; // size of the allocated buffer
194 #if ENABLE_FEATURE_VI_YANKMARK
195 int YDreg, Ureg; // default delete register and orig line for "U"
196 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
197 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
198 char *context_start, *context_end;
200 /* a few references only */
201 #if ENABLE_FEATURE_VI_USE_SIGNALS
202 jmp_buf restart; // catch_sig()
204 struct termios term_orig, term_vi; // remember what the cooked mode was
205 #if ENABLE_FEATURE_VI_COLON
206 char *initial_cmds[3]; // currently 2 entries, NULL terminated
208 // Should be just enough to hold a key sequence,
209 // but CRASME mode uses it as generated command buffer too
210 #if ENABLE_FEATURE_VI_CRASHME
211 char readbuffer[128];
216 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
218 #define G (*ptr_to_globals)
219 #define text (G.text )
220 #define text_size (G.text_size )
224 #define YDreg (G.YDreg )
225 #define Ureg (G.Ureg )
226 #define mark (G.mark )
227 #define context_start (G.context_start )
228 #define context_end (G.context_end )
229 #define restart (G.restart )
230 #define term_orig (G.term_orig )
231 #define term_vi (G.term_vi )
232 #define initial_cmds (G.initial_cmds )
233 #define readbuffer (G.readbuffer )
234 #define scr_out_buf (G.scr_out_buf )
235 #define INIT_G() do { \
236 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
239 static int init_text_buffer(char *); // init from file or create new
240 static void edit_file(char *); // edit one file
241 static void do_cmd(char); // execute a command
242 static int next_tabstop(int);
243 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
244 static char *begin_line(char *); // return pointer to cur line B-o-l
245 static char *end_line(char *); // return pointer to cur line E-o-l
246 static char *prev_line(char *); // return pointer to prev line B-o-l
247 static char *next_line(char *); // return pointer to next line B-o-l
248 static char *end_screen(void); // get pointer to last char on screen
249 static int count_lines(char *, char *); // count line from start to stop
250 static char *find_line(int); // find begining of line #li
251 static char *move_to_col(char *, int); // move "p" to column l
252 static void dot_left(void); // move dot left- dont leave line
253 static void dot_right(void); // move dot right- dont leave line
254 static void dot_begin(void); // move dot to B-o-l
255 static void dot_end(void); // move dot to E-o-l
256 static void dot_next(void); // move dot to next line B-o-l
257 static void dot_prev(void); // move dot to prev line B-o-l
258 static void dot_scroll(int, int); // move the screen up or down
259 static void dot_skip_over_ws(void); // move dot pat WS
260 static void dot_delete(void); // delete the char at 'dot'
261 static char *bound_dot(char *); // make sure text[0] <= P < "end"
262 static char *new_screen(int, int); // malloc virtual screen memory
263 static char *char_insert(char *, char); // insert the char c at 'p'
264 static char *stupid_insert(char *, char); // stupidly insert the char c at 'p'
265 static char find_range(char **, char **, char); // return pointers for an object
266 static int st_test(char *, int, int, char *); // helper for skip_thing()
267 static char *skip_thing(char *, int, int, int); // skip some object
268 static char *find_pair(char *, char); // find matching pair () [] {}
269 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
270 static char *text_hole_make(char *, int); // at "p", make a 'size' byte hole
271 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
272 static void show_help(void); // display some help info
273 static void rawmode(void); // set "raw" mode on tty
274 static void cookmode(void); // return to "cooked" mode on tty
275 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
276 static int mysleep(int);
277 static char readit(void); // read (maybe cursor) key from stdin
278 static char get_one_char(void); // read 1 char from stdin
279 static int file_size(const char *); // what is the byte size of "fn"
280 #if ENABLE_FEATURE_VI_READONLY
281 static int file_insert(const char *, char *, int);
283 static int file_insert(const char *, char *);
285 static int file_write(char *, char *, char *);
286 #if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
287 #define place_cursor(a, b, optimize) place_cursor(a, b)
289 static void place_cursor(int, int, int);
290 static void screen_erase(void);
291 static void clear_to_eol(void);
292 static void clear_to_eos(void);
293 static void standout_start(void); // send "start reverse video" sequence
294 static void standout_end(void); // send "end reverse video" sequence
295 static void flash(int); // flash the terminal screen
296 static void show_status_line(void); // put a message on the bottom line
297 static void status_line(const char *, ...); // print to status buf
298 static void status_line_bold(const char *, ...);
299 static void not_implemented(const char *); // display "Not implemented" message
300 static int format_edit_status(void); // format file status on status line
301 static void redraw(int); // force a full screen refresh
302 static char* format_line(char*, int);
303 static void refresh(int); // update the terminal from screen[]
305 static void Indicate_Error(void); // use flash or beep to indicate error
306 #define indicate_error(c) Indicate_Error()
307 static void Hit_Return(void);
309 #if ENABLE_FEATURE_VI_SEARCH
310 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
311 static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase"
313 #if ENABLE_FEATURE_VI_COLON
314 static char *get_one_address(char *, int *); // get colon addr, if present
315 static char *get_address(char *, int *, int *); // get two colon addrs, if present
316 static void colon(char *); // execute the "colon" mode cmds
318 #if ENABLE_FEATURE_VI_USE_SIGNALS
319 static void winch_sig(int); // catch window size changes
320 static void suspend_sig(int); // catch ctrl-Z
321 static void catch_sig(int); // catch ctrl-C and alarm time-outs
323 #if ENABLE_FEATURE_VI_DOT_CMD
324 static void start_new_cmd_q(char); // new queue for command
325 static void end_cmd_q(void); // stop saving input chars
327 #define end_cmd_q() ((void)0)
329 #if ENABLE_FEATURE_VI_SETOPTS
330 static void showmatching(char *); // show the matching pair () [] {}
332 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
333 static char *string_insert(char *, char *); // insert the string at 'p'
335 #if ENABLE_FEATURE_VI_YANKMARK
336 static char *text_yank(char *, char *, int); // save copy of "p" into a register
337 static char what_reg(void); // what is letter of current YDreg
338 static void check_context(char); // remember context for '' command
340 #if ENABLE_FEATURE_VI_CRASHME
341 static void crash_dummy();
342 static void crash_test();
343 static int crashme = 0;
347 static void write1(const char *out)
352 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
353 int vi_main(int argc, char **argv)
356 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
358 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
364 #if ENABLE_FEATURE_VI_CRASHME
365 srand((long) my_pid);
368 status_buffer = STATUS_BUFFER;
369 last_status_cksum = 0;
372 #ifdef NO_SUCH_APPLET_YET
373 /* If we aren't "vi", we are "view" */
374 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
375 SET_READONLY_MODE(readonly_mode);
379 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
380 #if ENABLE_FEATURE_VI_YANKMARK
381 memset(reg, 0, sizeof(reg)); // init the yank regs
383 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
384 modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
387 // 1- process $HOME/.exrc file (not inplemented yet)
388 // 2- process EXINIT variable from environment
389 // 3- process command line args
390 #if ENABLE_FEATURE_VI_COLON
392 char *p = getenv("EXINIT");
394 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
397 while ((c = getopt(argc, argv, "hCR" USE_FEATURE_VI_COLON("c:"))) != -1) {
399 #if ENABLE_FEATURE_VI_CRASHME
404 #if ENABLE_FEATURE_VI_READONLY
405 case 'R': // Read-only flag
406 SET_READONLY_MODE(readonly_mode);
409 //case 'r': // recover flag- ignore- we don't use tmp file
410 //case 'x': // encryption flag- ignore
411 //case 'c': // execute command first
412 #if ENABLE_FEATURE_VI_COLON
413 case 'c': // cmd line vi command
415 initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
417 //case 'h': // help -- just use default
425 // The argv array can be used by the ":next" and ":rewind" commands
427 fn_start = optind; // remember first file name for :next and :rew
430 //----- This is the main file handling loop --------------
431 if (optind >= argc) {
434 for (; optind < argc; optind++) {
435 edit_file(argv[optind]);
438 //-----------------------------------------------------------
443 /* read text from file or create an empty buf */
444 /* will also update current_filename */
445 static int init_text_buffer(char *fn)
448 int size = file_size(fn); // file size. -1 means does not exist.
450 /* allocate/reallocate text buffer */
452 text_size = size * 2;
453 if (text_size < 10240)
454 text_size = 10240; // have a minimum size for new files
455 screenbegin = dot = end = text = xzalloc(text_size);
457 if (fn != current_filename) {
458 free(current_filename);
459 current_filename = xstrdup(fn);
462 // file dont exist. Start empty buf with dummy line
463 char_insert(text, '\n');
466 rc = file_insert(fn, text
467 USE_FEATURE_VI_READONLY(, 1));
470 last_file_modified = -1;
471 #if ENABLE_FEATURE_VI_YANKMARK
472 /* init the marks. */
473 memset(mark, 0, sizeof(mark));
478 static void edit_file(char *fn)
483 #if ENABLE_FEATURE_VI_USE_SIGNALS
486 #if ENABLE_FEATURE_VI_YANKMARK
487 static char *cur_line;
490 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
495 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
496 get_terminal_width_height(0, &columns, &rows);
497 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
498 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
500 new_screen(rows, columns); // get memory for virtual screen
501 init_text_buffer(fn);
503 #if ENABLE_FEATURE_VI_YANKMARK
504 YDreg = 26; // default Yank/Delete reg
505 Ureg = 27; // hold orig line for "U" cmd
506 mark[26] = mark[27] = text; // init "previous context"
509 last_forward_char = last_input_char = '\0';
513 #if ENABLE_FEATURE_VI_USE_SIGNALS
515 signal(SIGWINCH, winch_sig);
516 signal(SIGTSTP, suspend_sig);
517 sig = setjmp(restart);
519 screenbegin = dot = text;
523 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
526 offset = 0; // no horizontal offset
528 #if ENABLE_FEATURE_VI_DOT_CMD
529 free(last_modifying_cmd);
531 ioq = ioq_start = last_modifying_cmd = NULL;
534 redraw(FALSE); // dont force every col re-draw
536 #if ENABLE_FEATURE_VI_COLON
541 while ((p = initial_cmds[n])) {
551 free(initial_cmds[n]);
552 initial_cmds[n] = NULL;
557 //------This is the main Vi cmd handling loop -----------------------
558 while (editing > 0) {
559 #if ENABLE_FEATURE_VI_CRASHME
561 if ((end - text) > 1) {
562 crash_dummy(); // generate a random command
565 dot = string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
570 last_input_char = c = get_one_char(); // get a cmd from user
571 #if ENABLE_FEATURE_VI_YANKMARK
572 // save a copy of the current line- for the 'U" command
573 if (begin_line(dot) != cur_line) {
574 cur_line = begin_line(dot);
575 text_yank(begin_line(dot), end_line(dot), Ureg);
578 #if ENABLE_FEATURE_VI_DOT_CMD
579 // These are commands that change text[].
580 // Remember the input for the "." command
581 if (!adding2q && ioq_start == NULL
582 && strchr(modifying_cmds, c)
587 do_cmd(c); // execute the user command
589 // poll to see if there is input already waiting. if we are
590 // not able to display output fast enough to keep up, skip
591 // the display update until we catch up with input.
592 if (mysleep(0) == 0) {
593 // no input pending- so update output
597 #if ENABLE_FEATURE_VI_CRASHME
599 crash_test(); // test editor variables
602 //-------------------------------------------------------------------
604 place_cursor(rows, 0, FALSE); // go to bottom of screen
605 clear_to_eol(); // Erase to end of line
609 //----- The Colon commands -------------------------------------
610 #if ENABLE_FEATURE_VI_COLON
611 static char *get_one_address(char *p, int *addr) // get colon addr, if present
615 USE_FEATURE_VI_YANKMARK(char c;)
616 USE_FEATURE_VI_SEARCH(char *pat;)
618 *addr = -1; // assume no addr
619 if (*p == '.') { // the current line
622 *addr = count_lines(text, q);
624 #if ENABLE_FEATURE_VI_YANKMARK
625 else if (*p == '\'') { // is this a mark addr
629 if (c >= 'a' && c <= 'z') {
632 q = mark[(unsigned char) c];
633 if (q != NULL) { // is mark valid
634 *addr = count_lines(text, q); // count lines
639 #if ENABLE_FEATURE_VI_SEARCH
640 else if (*p == '/') { // a search pattern
641 q = strchrnul(++p, '/');
642 pat = xstrndup(p, q - p); // save copy of pattern
646 q = char_search(dot, pat, FORWARD, FULL);
648 *addr = count_lines(text, q);
653 else if (*p == '$') { // the last line in file
655 q = begin_line(end - 1);
656 *addr = count_lines(text, q);
657 } else if (isdigit(*p)) { // specific line number
658 sscanf(p, "%d%n", addr, &st);
661 // unrecognised address - assume -1
667 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
669 //----- get the address' i.e., 1,3 'a,'b -----
670 // get FIRST addr, if present
672 p++; // skip over leading spaces
673 if (*p == '%') { // alias for 1,$
676 *e = count_lines(text, end-1);
679 p = get_one_address(p, b);
682 if (*p == ',') { // is there a address separator
686 // get SECOND addr, if present
687 p = get_one_address(p, e);
691 p++; // skip over trailing spaces
695 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
696 static void setops(const char *args, const char *opname, int flg_no,
697 const char *short_opname, int opt)
699 const char *a = args + flg_no;
700 int l = strlen(opname) - 1; /* opname have + ' ' */
702 if (strncasecmp(a, opname, l) == 0
703 || strncasecmp(a, short_opname, 2) == 0
713 // buf must be no longer than MAX_INPUT_LEN!
714 static void colon(char *buf)
716 char c, *orig_buf, *buf1, *q, *r;
717 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
718 int i, l, li, ch, b, e;
719 int useforce, forced = FALSE;
721 // :3154 // if (-e line 3154) goto it else stay put
722 // :4,33w! foo // write a portion of buffer to file "foo"
723 // :w // write all of buffer to current file
725 // :q! // quit- dont care about modified file
726 // :'a,'z!sort -u // filter block through sort
727 // :'f // goto mark "f"
728 // :'fl // list literal the mark "f" line
729 // :.r bar // read file "bar" into buffer before dot
730 // :/123/,/abc/d // delete lines from "123" line to "abc" line
731 // :/xyz/ // goto the "xyz" line
732 // :s/find/replace/ // substitute pattern "find" with "replace"
733 // :!<cmd> // run <cmd> then return
739 buf++; // move past the ':'
743 q = text; // assume 1,$ for the range
745 li = count_lines(text, end - 1);
746 fn = current_filename;
748 // look for optional address(es) :. :1 :1,9 :'q,'a :%
749 buf = get_address(buf, &b, &e);
751 // remember orig command line
754 // get the COMMAND into cmd[]
756 while (*buf != '\0') {
763 while (isblank(*buf))
767 buf1 = last_char_is(cmd, '!');
770 *buf1 = '\0'; // get rid of !
773 // if there is only one addr, then the addr
774 // is the line number of the single line the
775 // user wants. So, reset the end
776 // pointer to point at end of the "b" line
777 q = find_line(b); // what line is #b
782 // we were given two addrs. change the
783 // end pointer to the addr given by user.
784 r = find_line(e); // what line is #e
788 // ------------ now look for the command ------------
790 if (i == 0) { // :123CR goto line #123
792 dot = find_line(b); // what line is #b
796 #if ENABLE_FEATURE_ALLOW_EXEC
797 else if (strncmp(cmd, "!", 1) == 0) { // run a cmd
799 // :!ls run the <cmd>
800 alarm(0); // wait for input- no alarms
801 place_cursor(rows - 1, 0, FALSE); // go to Status line
802 clear_to_eol(); // clear the line
804 retcode = system(orig_buf + 1); // run the cmd
806 printf("\nshell returned %i\n\n", retcode);
808 Hit_Return(); // let user see results
809 alarm(3); // done waiting for input
812 else if (strncmp(cmd, "=", i) == 0) { // where is the address
813 if (b < 0) { // no addr given- use defaults
814 b = e = count_lines(text, dot);
816 status_line("%d", b);
817 } else if (strncasecmp(cmd, "delete", i) == 0) { // delete lines
818 if (b < 0) { // no addr given- use defaults
819 q = begin_line(dot); // assume .,. for the range
822 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
824 } else if (strncasecmp(cmd, "edit", i) == 0) { // Edit a file
825 // don't edit, if the current file has been modified
826 if (file_modified && !useforce) {
827 status_line_bold("No write since last change (:edit! overrides)");
831 // the user supplied a file name
833 } else if (current_filename && current_filename[0]) {
834 // no user supplied name- use the current filename
835 // fn = current_filename; was set by default
837 // no user file name, no current name- punt
838 status_line_bold("No current filename");
842 if (init_text_buffer(fn) < 0)
845 #if ENABLE_FEATURE_VI_YANKMARK
846 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
847 free(reg[Ureg]); // free orig line reg- for 'U'
850 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
851 free(reg[YDreg]); // free default yank/delete register
855 // how many lines in text[]?
856 li = count_lines(text, end - 1);
857 status_line("\"%s\"%s"
858 USE_FEATURE_VI_READONLY("%s")
859 " %dL, %dC", current_filename,
860 (file_size(fn) < 0 ? " [New file]" : ""),
861 USE_FEATURE_VI_READONLY(
862 ((readonly_mode) ? " [Readonly]" : ""),
865 } else if (strncasecmp(cmd, "file", i) == 0) { // what File is this
866 if (b != -1 || e != -1) {
867 not_implemented("No address allowed on this command");
871 // user wants a new filename
872 free(current_filename);
873 current_filename = xstrdup(args);
875 // user wants file status info
876 last_status_cksum = 0; // force status update
878 } else if (strncasecmp(cmd, "features", i) == 0) { // what features are available
879 // print out values of all features
880 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
881 clear_to_eol(); // clear the line
886 } else if (strncasecmp(cmd, "list", i) == 0) { // literal print line
887 if (b < 0) { // no addr given- use defaults
888 q = begin_line(dot); // assume .,. for the range
891 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
892 clear_to_eol(); // clear the line
894 for (; q <= r; q++) {
898 c_is_no_print = (c & 0x80) && !Isprint(c);
905 } else if (c < ' ' || c == 127) {
916 #if ENABLE_FEATURE_VI_SET
920 } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
921 || strncasecmp(cmd, "next", i) == 0 // edit next file
924 // force end of argv list
931 // don't exit if the file been modified
933 status_line_bold("No write since last change (:%s! overrides)",
934 (*cmd == 'q' ? "quit" : "next"));
937 // are there other file to edit
938 if (*cmd == 'q' && optind < save_argc - 1) {
939 status_line_bold("%d more file to edit", (save_argc - optind - 1));
942 if (*cmd == 'n' && optind >= save_argc - 1) {
943 status_line_bold("No more files to edit");
947 } else if (strncasecmp(cmd, "read", i) == 0) { // read file into text[]
950 status_line_bold("No filename given");
953 if (b < 0) { // no addr given- use defaults
954 q = begin_line(dot); // assume "dot"
956 // read after current line- unless user said ":0r foo"
959 ch = file_insert(fn, q USE_FEATURE_VI_READONLY(, 0));
961 goto vc1; // nothing was inserted
962 // how many lines in text[]?
963 li = count_lines(q, q + ch - 1);
965 USE_FEATURE_VI_READONLY("%s")
967 USE_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
970 // if the insert is before "dot" then we need to update
975 } else if (strncasecmp(cmd, "rewind", i) == 0) { // rewind cmd line args
976 if (file_modified && !useforce) {
977 status_line_bold("No write since last change (:rewind! overrides)");
979 // reset the filenames to edit
980 optind = fn_start - 1;
983 #if ENABLE_FEATURE_VI_SET
984 } else if (strncasecmp(cmd, "set", i) == 0) { // set or clear features
985 #if ENABLE_FEATURE_VI_SETOPTS
988 i = 0; // offset into args
989 // only blank is regarded as args delmiter. What about tab '\t' ?
990 if (!args[0] || strcasecmp(args, "all") == 0) {
991 // print out values of all options
992 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
993 clear_to_eol(); // clear the line
994 printf("----------------------------------------\r\n");
995 #if ENABLE_FEATURE_VI_SETOPTS
998 printf("autoindent ");
1004 printf("ignorecase ");
1007 printf("showmatch ");
1008 printf("tabstop=%d ", tabstop);
1013 #if ENABLE_FEATURE_VI_SETOPTS
1016 if (strncasecmp(argp, "no", 2) == 0)
1017 i = 2; // ":set noautoindent"
1018 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1019 setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
1020 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1021 setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
1023 if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
1024 sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
1025 if (ch > 0 && ch <= MAX_TABSTOP)
1028 while (*argp && *argp != ' ')
1029 argp++; // skip to arg delimiter (i.e. blank)
1030 while (*argp && *argp == ' ')
1031 argp++; // skip all delimiting blanks
1033 #endif /* FEATURE_VI_SETOPTS */
1034 #endif /* FEATURE_VI_SET */
1035 #if ENABLE_FEATURE_VI_SEARCH
1036 } else if (strncasecmp(cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1040 // F points to the "find" pattern
1041 // R points to the "replace" pattern
1042 // replace the cmd line delimiters "/" with NULLs
1043 gflag = 0; // global replace flag
1044 c = orig_buf[1]; // what is the delimiter
1045 F = orig_buf + 2; // start of "find"
1046 R = strchr(F, c); // middle delimiter
1047 if (!R) goto colon_s_fail;
1048 *R++ = '\0'; // terminate "find"
1049 buf1 = strchr(R, c);
1050 if (!buf1) goto colon_s_fail;
1051 *buf1++ = '\0'; // terminate "replace"
1052 if (*buf1 == 'g') { // :s/foo/bar/g
1054 gflag++; // turn on gflag
1057 if (b < 0) { // maybe :s/foo/bar/
1058 q = begin_line(dot); // start with cur line
1059 b = count_lines(text, q); // cur line number
1062 e = b; // maybe :.s/foo/bar/
1063 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1064 ls = q; // orig line start
1066 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1068 // we found the "find" pattern - delete it
1069 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1070 // inset the "replace" patern
1071 string_insert(buf1, R); // insert the string
1072 // check for "global" :s/foo/bar/g
1074 if ((buf1 + strlen(R)) < end_line(ls)) {
1075 q = buf1 + strlen(R);
1076 goto vc4; // don't let q move past cur line
1082 #endif /* FEATURE_VI_SEARCH */
1083 } else if (strncasecmp(cmd, "version", i) == 0) { // show software version
1084 status_line("%s", BB_VER " " BB_BT);
1085 } else if (strncasecmp(cmd, "write", i) == 0 // write text to file
1086 || strncasecmp(cmd, "wq", i) == 0
1087 || strncasecmp(cmd, "wn", i) == 0
1088 || strncasecmp(cmd, "x", i) == 0
1090 // is there a file name to write to?
1094 #if ENABLE_FEATURE_VI_READONLY
1095 if (readonly_mode && !useforce) {
1096 status_line_bold("\"%s\" File is read only", fn);
1100 // how many lines in text[]?
1101 li = count_lines(q, r);
1103 // see if file exists- if not, its just a new file request
1105 // if "fn" is not write-able, chmod u+w
1106 // sprintf(syscmd, "chmod u+w %s", fn);
1110 l = file_write(fn, q, r);
1111 if (useforce && forced) {
1113 // sprintf(syscmd, "chmod u-w %s", fn);
1119 status_line_bold("\"%s\" %s", fn, strerror(errno));
1121 status_line("\"%s\" %dL, %dC", fn, li, l);
1122 if (q == text && r == end - 1 && l == ch) {
1124 last_file_modified = -1;
1126 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1127 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1132 #if ENABLE_FEATURE_VI_READONLY
1135 #if ENABLE_FEATURE_VI_YANKMARK
1136 } else if (strncasecmp(cmd, "yank", i) == 0) { // yank lines
1137 if (b < 0) { // no addr given- use defaults
1138 q = begin_line(dot); // assume .,. for the range
1141 text_yank(q, r, YDreg);
1142 li = count_lines(q, r);
1143 status_line("Yank %d lines (%d chars) into [%c]",
1144 li, strlen(reg[YDreg]), what_reg());
1148 not_implemented(cmd);
1151 dot = bound_dot(dot); // make sure "dot" is valid
1153 #if ENABLE_FEATURE_VI_SEARCH
1155 status_line(":s expression missing delimiters");
1159 #endif /* FEATURE_VI_COLON */
1161 static void Hit_Return(void)
1165 standout_start(); // start reverse video
1166 write1("[Hit return to continue]");
1167 standout_end(); // end reverse video
1168 while ((c = get_one_char()) != '\n' && c != '\r')
1170 redraw(TRUE); // force redraw all
1173 static int next_tabstop(int col)
1175 return col + ((tabstop - 1) - (col % tabstop));
1178 //----- Synchronize the cursor to Dot --------------------------
1179 static void sync_cursor(char *d, int *row, int *col)
1181 char *beg_cur; // begin and end of "d" line
1182 char *end_scr; // begin and end of screen
1186 beg_cur = begin_line(d); // first char of cur line
1188 end_scr = end_screen(); // last char of screen
1190 if (beg_cur < screenbegin) {
1191 // "d" is before top line on screen
1192 // how many lines do we have to move
1193 cnt = count_lines(beg_cur, screenbegin);
1195 screenbegin = beg_cur;
1196 if (cnt > (rows - 1) / 2) {
1197 // we moved too many lines. put "dot" in middle of screen
1198 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1199 screenbegin = prev_line(screenbegin);
1202 } else if (beg_cur > end_scr) {
1203 // "d" is after bottom line on screen
1204 // how many lines do we have to move
1205 cnt = count_lines(end_scr, beg_cur);
1206 if (cnt > (rows - 1) / 2)
1207 goto sc1; // too many lines
1208 for (ro = 0; ro < cnt - 1; ro++) {
1209 // move screen begin the same amount
1210 screenbegin = next_line(screenbegin);
1211 // now, move the end of screen
1212 end_scr = next_line(end_scr);
1213 end_scr = end_line(end_scr);
1216 // "d" is on screen- find out which row
1218 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1224 // find out what col "d" is on
1226 while (tp < d) { // drive "co" to correct column
1227 if (*tp == '\n') //vda || *tp == '\0')
1230 // handle tabs like real vi
1231 if (d == tp && cmd_mode) {
1234 co = next_tabstop(co);
1236 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1237 co++; // display as ^X, use 2 columns
1243 // "co" is the column where "dot" is.
1244 // The screen has "columns" columns.
1245 // The currently displayed columns are 0+offset -- columns+ofset
1246 // |-------------------------------------------------------------|
1248 // offset | |------- columns ----------------|
1250 // If "co" is already in this range then we do not have to adjust offset
1251 // but, we do have to subtract the "offset" bias from "co".
1252 // If "co" is outside this range then we have to change "offset".
1253 // If the first char of a line is a tab the cursor will try to stay
1254 // in column 7, but we have to set offset to 0.
1256 if (co < 0 + offset) {
1259 if (co >= columns + offset) {
1260 offset = co - columns + 1;
1262 // if the first char of the line is a tab, and "dot" is sitting on it
1263 // force offset to 0.
1264 if (d == beg_cur && *d == '\t') {
1273 //----- Text Movement Routines ---------------------------------
1274 static char *begin_line(char * p) // return pointer to first char cur line
1276 while (p > text && p[-1] != '\n')
1277 p--; // go to cur line B-o-l
1281 static char *end_line(char * p) // return pointer to NL of cur line line
1283 while (p < end - 1 && *p != '\n')
1284 p++; // go to cur line E-o-l
1288 static char *dollar_line(char * p) // return pointer to just before NL line
1290 while (p < end - 1 && *p != '\n')
1291 p++; // go to cur line E-o-l
1292 // Try to stay off of the Newline
1293 if (*p == '\n' && (p - begin_line(p)) > 0)
1298 static char *prev_line(char * p) // return pointer first char prev line
1300 p = begin_line(p); // goto begining of cur line
1301 if (p[-1] == '\n' && p > text)
1302 p--; // step to prev line
1303 p = begin_line(p); // goto begining of prev line
1307 static char *next_line(char * p) // return pointer first char next line
1310 if (*p == '\n' && p < end - 1)
1311 p++; // step to next line
1315 //----- Text Information Routines ------------------------------
1316 static char *end_screen(void)
1321 // find new bottom line
1323 for (cnt = 0; cnt < rows - 2; cnt++)
1329 static int count_lines(char * start, char * stop) // count line from start to stop
1334 if (stop < start) { // start and stop are backwards- reverse them
1340 stop = end_line(stop); // get to end of this line
1341 for (q = start; q <= stop && q <= end - 1; q++) {
1348 static char *find_line(int li) // find begining of line #li
1352 for (q = text; li > 1; li--) {
1358 //----- Dot Movement Routines ----------------------------------
1359 static void dot_left(void)
1361 if (dot > text && dot[-1] != '\n')
1365 static void dot_right(void)
1367 if (dot < end - 1 && *dot != '\n')
1371 static void dot_begin(void)
1373 dot = begin_line(dot); // return pointer to first char cur line
1376 static void dot_end(void)
1378 dot = end_line(dot); // return pointer to last char cur line
1381 static char *move_to_col(char *p, int l)
1387 while (co < l && p < end) {
1388 if (*p == '\n') //vda || *p == '\0')
1391 co = next_tabstop(co);
1392 } else if (*p < ' ' || *p == 127) {
1393 co++; // display as ^X, use 2 columns
1401 static void dot_next(void)
1403 dot = next_line(dot);
1406 static void dot_prev(void)
1408 dot = prev_line(dot);
1411 static void dot_scroll(int cnt, int dir)
1415 for (; cnt > 0; cnt--) {
1418 // ctrl-Y scroll up one line
1419 screenbegin = prev_line(screenbegin);
1422 // ctrl-E scroll down one line
1423 screenbegin = next_line(screenbegin);
1426 // make sure "dot" stays on the screen so we dont scroll off
1427 if (dot < screenbegin)
1429 q = end_screen(); // find new bottom line
1431 dot = begin_line(q); // is dot is below bottom line?
1435 static void dot_skip_over_ws(void)
1438 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1442 static void dot_delete(void) // delete the char at 'dot'
1444 text_hole_delete(dot, dot);
1447 static char *bound_dot(char * p) // make sure text[0] <= P < "end"
1449 if (p >= end && end > text) {
1451 indicate_error('1');
1455 indicate_error('2');
1460 //----- Helper Utility Routines --------------------------------
1462 //----------------------------------------------------------------
1463 //----- Char Routines --------------------------------------------
1464 /* Chars that are part of a word-
1465 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1466 * Chars that are Not part of a word (stoppers)
1467 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1468 * Chars that are WhiteSpace
1469 * TAB NEWLINE VT FF RETURN SPACE
1470 * DO NOT COUNT NEWLINE AS WHITESPACE
1473 static char *new_screen(int ro, int co)
1478 screensize = ro * co + 8;
1479 screen = xmalloc(screensize);
1480 // initialize the new screen. assume this will be a empty file.
1482 // non-existent text[] lines start with a tilde (~).
1483 for (li = 1; li < ro - 1; li++) {
1484 screen[(li * co) + 0] = '~';
1489 #if ENABLE_FEATURE_VI_SEARCH
1490 static int mycmp(const char * s1, const char * s2, int len)
1494 i = strncmp(s1, s2, len);
1495 if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) {
1496 i = strncasecmp(s1, s2, len);
1501 // search for pattern starting at p
1502 static char *char_search(char * p, const char * pat, int dir, int range)
1504 #ifndef REGEX_SEARCH
1509 if (dir == FORWARD) {
1510 stop = end - 1; // assume range is p - end-1
1511 if (range == LIMITED)
1512 stop = next_line(p); // range is to next line
1513 for (start = p; start < stop; start++) {
1514 if (mycmp(start, pat, len) == 0) {
1518 } else if (dir == BACK) {
1519 stop = text; // assume range is text - p
1520 if (range == LIMITED)
1521 stop = prev_line(p); // range is to prev line
1522 for (start = p - len; start >= stop; start--) {
1523 if (mycmp(start, pat, len) == 0) {
1528 // pattern not found
1530 #else /* REGEX_SEARCH */
1532 struct re_pattern_buffer preg;
1536 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1542 // assume a LIMITED forward search
1550 // count the number of chars to search over, forward or backward
1554 // RANGE could be negative if we are searching backwards
1557 q = re_compile_pattern(pat, strlen(pat), &preg);
1559 // The pattern was not compiled
1560 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1561 i = 0; // return p if pattern not compiled
1571 // search for the compiled pattern, preg, in p[]
1572 // range < 0- search backward
1573 // range > 0- search forward
1575 // re_search() < 0 not found or error
1576 // re_search() > 0 index of found pattern
1577 // struct pattern char int int int struct reg
1578 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1579 i = re_search(&preg, q, size, 0, range, 0);
1582 i = 0; // return NULL if pattern not found
1585 if (dir == FORWARD) {
1591 #endif /* REGEX_SEARCH */
1593 #endif /* FEATURE_VI_SEARCH */
1595 static char *char_insert(char * p, char c) // insert the char c at 'p'
1597 if (c == 22) { // Is this an ctrl-V?
1598 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1599 p--; // backup onto ^
1600 refresh(FALSE); // show the ^
1604 file_modified++; // has the file been modified
1605 } else if (c == 27) { // Is this an ESC?
1608 end_cmd_q(); // stop adding to q
1609 last_status_cksum = 0; // force status update
1610 if ((p[-1] != '\n') && (dot > text)) {
1613 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1615 if ((p[-1] != '\n') && (dot>text)) {
1617 p = text_hole_delete(p, p); // shrink buffer 1 char
1620 // insert a char into text[]
1621 char *sp; // "save p"
1624 c = '\n'; // translate \r to \n
1625 sp = p; // remember addr of insert
1626 p = stupid_insert(p, c); // insert the char
1627 #if ENABLE_FEATURE_VI_SETOPTS
1628 if (showmatch && strchr(")]}", *sp) != NULL) {
1631 if (autoindent && c == '\n') { // auto indent the new line
1634 q = prev_line(p); // use prev line as templet
1635 for (; isblank(*q); q++) {
1636 p = stupid_insert(p, *q); // insert the char
1644 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1646 p = text_hole_make(p, 1);
1649 file_modified++; // has the file been modified
1655 static char find_range(char ** start, char ** stop, char c)
1657 char *save_dot, *p, *q;
1663 if (strchr("cdy><", c)) {
1664 // these cmds operate on whole lines
1665 p = q = begin_line(p);
1666 for (cnt = 1; cnt < cmdcnt; cnt++) {
1670 } else if (strchr("^%$0bBeEft", c)) {
1671 // These cmds operate on char positions
1672 do_cmd(c); // execute movement cmd
1674 } else if (strchr("wW", c)) {
1675 do_cmd(c); // execute movement cmd
1676 // if we are at the next word's first char
1677 // step back one char
1678 // but check the possibilities when it is true
1679 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1680 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1681 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1682 dot--; // move back off of next word
1683 if (dot > text && *dot == '\n')
1684 dot--; // stay off NL
1686 } else if (strchr("H-k{", c)) {
1687 // these operate on multi-lines backwards
1688 q = end_line(dot); // find NL
1689 do_cmd(c); // execute movement cmd
1692 } else if (strchr("L+j}\r\n", c)) {
1693 // these operate on multi-lines forwards
1694 p = begin_line(dot);
1695 do_cmd(c); // execute movement cmd
1696 dot_end(); // find NL
1699 c = 27; // error- return an ESC char
1712 static int st_test(char * p, int type, int dir, char * tested)
1722 if (type == S_BEFORE_WS) {
1724 test = ((!isspace(c)) || c == '\n');
1726 if (type == S_TO_WS) {
1728 test = ((!isspace(c)) || c == '\n');
1730 if (type == S_OVER_WS) {
1732 test = ((isspace(c)));
1734 if (type == S_END_PUNCT) {
1736 test = ((ispunct(c)));
1738 if (type == S_END_ALNUM) {
1740 test = ((isalnum(c)) || c == '_');
1746 static char *skip_thing(char * p, int linecnt, int dir, int type)
1750 while (st_test(p, type, dir, &c)) {
1751 // make sure we limit search to correct number of lines
1752 if (c == '\n' && --linecnt < 1)
1754 if (dir >= 0 && p >= end - 1)
1756 if (dir < 0 && p <= text)
1758 p += dir; // move to next char
1763 // find matching char of pair () [] {}
1764 static char *find_pair(char * p, const char c)
1771 dir = 1; // assume forward
1795 for (q = p + dir; text <= q && q < end; q += dir) {
1796 // look for match, count levels of pairs (( ))
1798 level++; // increase pair levels
1800 level--; // reduce pair level
1802 break; // found matching pair
1805 q = NULL; // indicate no match
1809 #if ENABLE_FEATURE_VI_SETOPTS
1810 // show the matching char of a pair, () [] {}
1811 static void showmatching(char * p)
1815 // we found half of a pair
1816 q = find_pair(p, *p); // get loc of matching char
1818 indicate_error('3'); // no matching char
1820 // "q" now points to matching pair
1821 save_dot = dot; // remember where we are
1822 dot = q; // go to new loc
1823 refresh(FALSE); // let the user see it
1824 mysleep(40); // give user some time
1825 dot = save_dot; // go back to old loc
1829 #endif /* FEATURE_VI_SETOPTS */
1831 // open a hole in text[]
1832 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1841 cnt = end - src; // the rest of buffer
1842 if ( ((end + size) >= (text + text_size)) // TODO: realloc here
1843 || memmove(dest, src, cnt) != dest) {
1844 status_line_bold("can't create room for new characters");
1848 memset(p, ' ', size); // clear new hole
1849 end += size; // adjust the new END
1850 file_modified++; // has the file been modified
1855 // close a hole in text[]
1856 static char *text_hole_delete(char * p, char * q) // delete "p" thru "q", inclusive
1861 // move forwards, from beginning
1865 if (q < p) { // they are backward- swap them
1869 hole_size = q - p + 1;
1871 if (src < text || src > end)
1873 if (dest < text || dest >= end)
1876 goto thd_atend; // just delete the end of the buffer
1877 if (memmove(dest, src, cnt) != dest) {
1878 status_line_bold("can't delete the character");
1881 end = end - hole_size; // adjust the new END
1883 dest = end - 1; // make sure dest in below end-1
1885 dest = end = text; // keep pointers valid
1886 file_modified++; // has the file been modified
1891 // copy text into register, then delete text.
1892 // if dist <= 0, do not include, or go past, a NewLine
1894 static char *yank_delete(char * start, char * stop, int dist, int yf)
1898 // make sure start <= stop
1900 // they are backwards, reverse them
1906 // we cannot cross NL boundaries
1910 // dont go past a NewLine
1911 for (; p + 1 <= stop; p++) {
1913 stop = p; // "stop" just before NewLine
1919 #if ENABLE_FEATURE_VI_YANKMARK
1920 text_yank(start, stop, YDreg);
1922 if (yf == YANKDEL) {
1923 p = text_hole_delete(start, stop);
1928 static void show_help(void)
1930 puts("These features are available:"
1931 #if ENABLE_FEATURE_VI_SEARCH
1932 "\n\tPattern searches with / and ?"
1934 #if ENABLE_FEATURE_VI_DOT_CMD
1935 "\n\tLast command repeat with \'.\'"
1937 #if ENABLE_FEATURE_VI_YANKMARK
1938 "\n\tLine marking with 'x"
1939 "\n\tNamed buffers with \"x"
1941 #if ENABLE_FEATURE_VI_READONLY
1942 "\n\tReadonly if vi is called as \"view\""
1943 "\n\tReadonly with -R command line arg"
1945 #if ENABLE_FEATURE_VI_SET
1946 "\n\tSome colon mode commands with \':\'"
1948 #if ENABLE_FEATURE_VI_SETOPTS
1949 "\n\tSettable options with \":set\""
1951 #if ENABLE_FEATURE_VI_USE_SIGNALS
1952 "\n\tSignal catching- ^C"
1953 "\n\tJob suspend and resume with ^Z"
1955 #if ENABLE_FEATURE_VI_WIN_RESIZE
1956 "\n\tAdapt to window re-sizes"
1961 #if ENABLE_FEATURE_VI_DOT_CMD
1962 static void start_new_cmd_q(char c)
1964 // get buffer for new cmd
1965 if (!last_modifying_cmd)
1966 last_modifying_cmd = xzalloc(MAX_INPUT_LEN);
1967 // if there is a current cmd count put it in the buffer first
1969 sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1970 else { // just save char c onto queue
1971 last_modifying_cmd[0] = c;
1972 last_modifying_cmd[1] = '\0';
1977 static void end_cmd_q(void)
1979 #if ENABLE_FEATURE_VI_YANKMARK
1980 YDreg = 26; // go back to default Yank/Delete reg
1984 #endif /* FEATURE_VI_DOT_CMD */
1986 #if ENABLE_FEATURE_VI_YANKMARK \
1987 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1988 || ENABLE_FEATURE_VI_CRASHME
1989 static char *string_insert(char * p, char * s) // insert the string at 'p'
1994 if (text_hole_make(p, i)) {
1996 for (cnt = 0; *s != '\0'; s++) {
2000 #if ENABLE_FEATURE_VI_YANKMARK
2001 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2008 #if ENABLE_FEATURE_VI_YANKMARK
2009 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2014 if (q < p) { // they are backwards- reverse them
2021 free(t); // if already a yank register, free it
2022 t = xmalloc(cnt + 1); // get a new register
2023 memset(t, '\0', cnt + 1); // clear new text[]
2024 strncpy(t, p, cnt); // copy text[] into bufer
2029 static char what_reg(void)
2033 c = 'D'; // default to D-reg
2034 if (0 <= YDreg && YDreg <= 25)
2035 c = 'a' + (char) YDreg;
2043 static void check_context(char cmd)
2045 // A context is defined to be "modifying text"
2046 // Any modifying command establishes a new context.
2048 if (dot < context_start || dot > context_end) {
2049 if (strchr(modifying_cmds, cmd) != NULL) {
2050 // we are trying to modify text[]- make this the current context
2051 mark[27] = mark[26]; // move cur to prev
2052 mark[26] = dot; // move local to cur
2053 context_start = prev_line(prev_line(dot));
2054 context_end = next_line(next_line(dot));
2055 //loiter= start_loiter= now;
2060 static char *swap_context(char * p) // goto new context for '' command make this the current context
2064 // the current context is in mark[26]
2065 // the previous context is in mark[27]
2066 // only swap context if other context is valid
2067 if (text <= mark[27] && mark[27] <= end - 1) {
2069 mark[27] = mark[26];
2071 p = mark[26]; // where we are going- previous context
2072 context_start = prev_line(prev_line(prev_line(p)));
2073 context_end = next_line(next_line(next_line(p)));
2077 #endif /* FEATURE_VI_YANKMARK */
2079 //----- Set terminal attributes --------------------------------
2080 static void rawmode(void)
2082 tcgetattr(0, &term_orig);
2083 term_vi = term_orig;
2084 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2085 term_vi.c_iflag &= (~IXON & ~ICRNL);
2086 term_vi.c_oflag &= (~ONLCR);
2087 term_vi.c_cc[VMIN] = 1;
2088 term_vi.c_cc[VTIME] = 0;
2089 erase_char = term_vi.c_cc[VERASE];
2090 tcsetattr(0, TCSANOW, &term_vi);
2093 static void cookmode(void)
2096 tcsetattr(0, TCSANOW, &term_orig);
2099 //----- Come here when we get a window resize signal ---------
2100 #if ENABLE_FEATURE_VI_USE_SIGNALS
2101 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2103 // FIXME: do it in main loop!!!
2104 signal(SIGWINCH, winch_sig);
2105 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2106 get_terminal_width_height(0, &columns, &rows);
2107 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2108 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2110 new_screen(rows, columns); // get memory for virtual screen
2111 redraw(TRUE); // re-draw the screen
2114 //----- Come here when we get a continue signal -------------------
2115 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2117 rawmode(); // terminal to "raw"
2118 last_status_cksum = 0; // force status update
2119 redraw(TRUE); // re-draw the screen
2121 signal(SIGTSTP, suspend_sig);
2122 signal(SIGCONT, SIG_DFL);
2123 kill(my_pid, SIGCONT);
2126 //----- Come here when we get a Suspend signal -------------------
2127 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2129 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2130 clear_to_eol(); // Erase to end of line
2131 cookmode(); // terminal to "cooked"
2133 signal(SIGCONT, cont_sig);
2134 signal(SIGTSTP, SIG_DFL);
2135 kill(my_pid, SIGTSTP);
2138 //----- Come here when we get a signal ---------------------------
2139 static void catch_sig(int sig)
2141 signal(SIGINT, catch_sig);
2143 longjmp(restart, sig);
2145 #endif /* FEATURE_VI_USE_SIGNALS */
2147 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2149 struct pollfd pfd[1];
2152 pfd[0].events = POLLIN;
2153 return safe_poll(pfd, 1, hund*10) > 0;
2156 static int chars_to_parse;
2158 //----- IO Routines --------------------------------------------
2159 static char readit(void) // read (maybe cursor) key from stdin
2168 static const struct esc_cmds esccmds[] = {
2169 {"OA" , VI_K_UP }, // cursor key Up
2170 {"OB" , VI_K_DOWN }, // cursor key Down
2171 {"OC" , VI_K_RIGHT }, // Cursor Key Right
2172 {"OD" , VI_K_LEFT }, // cursor key Left
2173 {"OH" , VI_K_HOME }, // Cursor Key Home
2174 {"OF" , VI_K_END }, // Cursor Key End
2175 {"[A" , VI_K_UP }, // cursor key Up
2176 {"[B" , VI_K_DOWN }, // cursor key Down
2177 {"[C" , VI_K_RIGHT }, // Cursor Key Right
2178 {"[D" , VI_K_LEFT }, // cursor key Left
2179 {"[H" , VI_K_HOME }, // Cursor Key Home
2180 {"[F" , VI_K_END }, // Cursor Key End
2181 {"[1~" , VI_K_HOME }, // Cursor Key Home
2182 {"[2~" , VI_K_INSERT }, // Cursor Key Insert
2183 {"[3~" , VI_K_DELETE }, // Cursor Key Delete
2184 {"[4~" , VI_K_END }, // Cursor Key End
2185 {"[5~" , VI_K_PAGEUP }, // Cursor Key Page Up
2186 {"[6~" , VI_K_PAGEDOWN}, // Cursor Key Page Down
2187 {"OP" , VI_K_FUN1 }, // Function Key F1
2188 {"OQ" , VI_K_FUN2 }, // Function Key F2
2189 {"OR" , VI_K_FUN3 }, // Function Key F3
2190 {"OS" , VI_K_FUN4 }, // Function Key F4
2191 // careful: these have no terminating NUL!
2192 {"[11~", VI_K_FUN1 }, // Function Key F1
2193 {"[12~", VI_K_FUN2 }, // Function Key F2
2194 {"[13~", VI_K_FUN3 }, // Function Key F3
2195 {"[14~", VI_K_FUN4 }, // Function Key F4
2196 {"[15~", VI_K_FUN5 }, // Function Key F5
2197 {"[17~", VI_K_FUN6 }, // Function Key F6
2198 {"[18~", VI_K_FUN7 }, // Function Key F7
2199 {"[19~", VI_K_FUN8 }, // Function Key F8
2200 {"[20~", VI_K_FUN9 }, // Function Key F9
2201 {"[21~", VI_K_FUN10 }, // Function Key F10
2202 {"[23~", VI_K_FUN11 }, // Function Key F11
2203 {"[24~", VI_K_FUN12 }, // Function Key F12
2205 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2207 alarm(0); // turn alarm OFF while we wait for input
2210 // get input from User- are there already input chars in Q?
2212 // the Q is empty, wait for a typed char
2213 n = safe_read(0, readbuffer, sizeof(readbuffer));
2215 if (errno == EBADF || errno == EFAULT || errno == EINVAL
2217 editing = 0; // want to exit
2222 if (readbuffer[0] == 27) {
2223 // This is an ESC char. Is this Esc sequence?
2224 // Could be bare Esc key. See if there are any
2225 // more chars to read after the ESC. This would
2226 // be a Function or Cursor Key sequence.
2227 struct pollfd pfd[1];
2229 pfd[0].events = POLLIN;
2230 // keep reading while there are input chars, and room in buffer
2231 // for a complete ESC sequence (assuming 8 chars is enough)
2232 while (safe_poll(pfd, 1, 0) > 0 && n <= (sizeof(readbuffer) - 8)) {
2233 // read the rest of the ESC string
2234 int r = safe_read(0, readbuffer + n, sizeof(readbuffer) - n);
2242 if (c == 27 && n > 1) {
2243 // Maybe cursor or function key?
2244 const struct esc_cmds *eindex;
2246 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2247 int cnt = strnlen(eindex->seq, 4);
2250 if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
2252 c = eindex->val; // magic char value
2253 n = cnt + 1; // squeeze out the ESC sequence
2256 // defined ESC sequence not found
2260 // remove key sequence from Q
2261 chars_to_parse -= n;
2262 memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
2263 alarm(3); // we are done waiting for input, turn alarm ON
2267 //----- IO Routines --------------------------------------------
2268 static char get_one_char(void)
2272 #if ENABLE_FEATURE_VI_DOT_CMD
2273 // ! adding2q && ioq == 0 read()
2274 // ! adding2q && ioq != 0 *ioq
2275 // adding2q *last_modifying_cmd= read()
2277 // we are not adding to the q.
2278 // but, we may be reading from a q
2280 // there is no current q, read from STDIN
2281 c = readit(); // get the users input
2283 // there is a queue to get chars from first
2286 // the end of the q, read from STDIN
2288 ioq_start = ioq = 0;
2289 c = readit(); // get the users input
2293 // adding STDIN chars to q
2294 c = readit(); // get the users input
2295 if (last_modifying_cmd != NULL) {
2296 int len = strlen(last_modifying_cmd);
2297 if (len >= MAX_INPUT_LEN - 1) {
2298 status_line_bold("last_modifying_cmd overrun");
2300 // add new char to q
2301 last_modifying_cmd[len] = c;
2306 c = readit(); // get the users input
2307 #endif /* FEATURE_VI_DOT_CMD */
2311 // Get input line (uses "status line" area)
2312 static char *get_input_line(const char *prompt)
2314 static char *buf; // [MAX_INPUT_LEN]
2319 if (!buf) buf = xmalloc(MAX_INPUT_LEN);
2321 strcpy(buf, prompt);
2322 last_status_cksum = 0; // force status update
2323 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2324 clear_to_eol(); // clear the line
2325 write1(prompt); // write out the :, /, or ? prompt
2328 while (i < MAX_INPUT_LEN) {
2330 if (c == '\n' || c == '\r' || c == 27)
2331 break; // this is end of input
2332 if (c == erase_char || c == 8 || c == 127) {
2333 // user wants to erase prev char
2335 write1("\b \b"); // erase char on screen
2336 if (i <= 0) // user backs up before b-o-l, exit
2348 static int file_size(const char *fn) // what is the byte size of "fn"
2354 if (fn && fn[0] && stat(fn, &st_buf) == 0) // see if file exists
2355 cnt = (int) st_buf.st_size;
2359 static int file_insert(const char * fn, char *p
2360 USE_FEATURE_VI_READONLY(, int update_ro_status))
2364 struct stat statbuf;
2367 if (stat(fn, &statbuf) < 0) {
2368 status_line_bold("\"%s\" %s", fn, strerror(errno));
2371 if ((statbuf.st_mode & S_IFREG) == 0) {
2372 // This is not a regular file
2373 status_line_bold("\"%s\" Not a regular file", fn);
2376 /* // this check is done by open()
2377 if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2378 // dont have any read permissions
2379 status_line_bold("\"%s\" Not readable", fn);
2383 if (p < text || p > end) {
2384 status_line_bold("Trying to insert file outside of memory");
2388 // read file to buffer
2389 fd = open(fn, O_RDONLY);
2391 status_line_bold("\"%s\" %s", fn, strerror(errno));
2394 size = statbuf.st_size;
2395 p = text_hole_make(p, size);
2398 cnt = safe_read(fd, p, size);
2400 status_line_bold("\"%s\" %s", fn, strerror(errno));
2401 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2402 } else if (cnt < size) {
2403 // There was a partial read, shrink unused space text[]
2404 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2405 status_line_bold("cannot read all of file \"%s\"", fn);
2411 #if ENABLE_FEATURE_VI_READONLY
2412 if (update_ro_status
2413 && ((access(fn, W_OK) < 0) ||
2414 /* root will always have access()
2415 * so we check fileperms too */
2416 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2419 SET_READONLY_FILE(readonly_mode);
2426 static int file_write(char * fn, char * first, char * last)
2428 int fd, cnt, charcnt;
2431 status_line_bold("No current filename");
2435 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
2438 cnt = last - first + 1;
2439 charcnt = full_write(fd, first, cnt);
2440 if (charcnt == cnt) {
2442 //file_modified = FALSE; // the file has not been modified
2450 //----- Terminal Drawing ---------------------------------------
2451 // The terminal is made up of 'rows' line of 'columns' columns.
2452 // classically this would be 24 x 80.
2453 // screen coordinates
2459 // 23,0 ... 23,79 <- status line
2461 //----- Move the cursor to row x col (count from 0, not 1) -------
2462 static void place_cursor(int row, int col, int optimize)
2464 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2467 if (row < 0) row = 0;
2468 if (row >= rows) row = rows - 1;
2469 if (col < 0) col = 0;
2470 if (col >= columns) col = columns - 1;
2472 //----- 1. Try the standard terminal ESC sequence
2473 sprintf(cm1, CMrc, row + 1, col + 1);
2476 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2477 if (optimize && col < 16) {
2479 SZ_UP = sizeof(CMup),
2480 SZ_DN = sizeof(CMdown),
2481 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2483 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2485 int Rrow = last_row;
2486 int diff = Rrow - row;
2488 if (diff < -5 || diff > 5)
2491 //----- find the minimum # of chars to move cursor -------------
2492 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2495 // move to the correct row
2496 while (row < Rrow) {
2497 // the cursor has to move up
2501 while (row > Rrow) {
2502 // the cursor has to move down
2503 strcat(cm2, CMdown);
2507 // now move to the correct column
2508 strcat(cm2, "\r"); // start at col 0
2509 // just send out orignal source char to get to correct place
2510 screenp = &screen[row * columns]; // start of screen line
2511 strncat(cm2, screenp, col);
2513 // pick the shortest cursor motion to send out
2514 if (strlen(cm2) < strlen(cm)) {
2520 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2524 //----- Erase from cursor to end of line -----------------------
2525 static void clear_to_eol(void)
2527 write1(Ceol); // Erase from cursor to end of line
2530 //----- Erase from cursor to end of screen -----------------------
2531 static void clear_to_eos(void)
2533 write1(Ceos); // Erase from cursor to end of screen
2536 //----- Start standout mode ------------------------------------
2537 static void standout_start(void) // send "start reverse video" sequence
2539 write1(SOs); // Start reverse video mode
2542 //----- End standout mode --------------------------------------
2543 static void standout_end(void) // send "end reverse video" sequence
2545 write1(SOn); // End reverse video mode
2548 //----- Flash the screen --------------------------------------
2549 static void flash(int h)
2551 standout_start(); // send "start reverse video" sequence
2554 standout_end(); // send "end reverse video" sequence
2558 static void Indicate_Error(void)
2560 #if ENABLE_FEATURE_VI_CRASHME
2562 return; // generate a random command
2565 write1(bell); // send out a bell character
2571 //----- Screen[] Routines --------------------------------------
2572 //----- Erase the Screen[] memory ------------------------------
2573 static void screen_erase(void)
2575 memset(screen, ' ', screensize); // clear new screen
2578 static int bufsum(char *buf, int count)
2581 char *e = buf + count;
2584 sum += (unsigned char) *buf++;
2588 //----- Draw the status line at bottom of the screen -------------
2589 static void show_status_line(void)
2591 int cnt = 0, cksum = 0;
2593 // either we already have an error or status message, or we
2595 if (!have_status_msg) {
2596 cnt = format_edit_status();
2597 cksum = bufsum(status_buffer, cnt);
2599 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2600 last_status_cksum = cksum; // remember if we have seen this line
2601 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2602 write1(status_buffer);
2604 if (have_status_msg) {
2605 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2607 have_status_msg = 0;
2610 have_status_msg = 0;
2612 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2617 //----- format the status buffer, the bottom line of screen ------
2618 // format status buffer, with STANDOUT mode
2619 static void status_line_bold(const char *format, ...)
2623 va_start(args, format);
2624 strcpy(status_buffer, SOs); // Terminal standout mode on
2625 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2626 strcat(status_buffer, SOn); // Terminal standout mode off
2629 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2632 // format status buffer
2633 static void status_line(const char *format, ...)
2637 va_start(args, format);
2638 vsprintf(status_buffer, format, args);
2641 have_status_msg = 1;
2644 // copy s to buf, convert unprintable
2645 static void print_literal(char *buf, const char *s)
2658 c_is_no_print = (c & 0x80) && !Isprint(c);
2659 if (c_is_no_print) {
2663 if (c < ' ' || c == 127) {
2676 if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
2681 static void not_implemented(const char *s)
2683 char buf[MAX_INPUT_LEN];
2685 print_literal(buf, s);
2686 status_line_bold("\'%s\' is not implemented", buf);
2689 // show file status on status line
2690 static int format_edit_status(void)
2693 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2694 int cur, percent, ret, trunc_at;
2696 // file_modified is now a counter rather than a flag. this
2697 // helps reduce the amount of line counting we need to do.
2698 // (this will cause a mis-reporting of modified status
2699 // once every MAXINT editing operations.)
2701 // it would be nice to do a similar optimization here -- if
2702 // we haven't done a motion that could have changed which line
2703 // we're on, then we shouldn't have to do this count_lines()
2704 cur = count_lines(text, dot);
2706 // reduce counting -- the total lines can't have
2707 // changed if we haven't done any edits.
2708 if (file_modified != last_file_modified) {
2709 tot = cur + count_lines(dot, end - 1) - 1;
2710 last_file_modified = file_modified;
2713 // current line percent
2714 // ------------- ~~ ----------
2717 percent = (100 * cur) / tot;
2723 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2724 columns : STATUS_BUFFER_LEN-1;
2726 ret = snprintf(status_buffer, trunc_at+1,
2727 #if ENABLE_FEATURE_VI_READONLY
2728 "%c %s%s%s %d/%d %d%%",
2730 "%c %s%s %d/%d %d%%",
2732 cmd_mode_indicator[cmd_mode & 3],
2733 (current_filename != NULL ? current_filename : "No file"),
2734 #if ENABLE_FEATURE_VI_READONLY
2735 (readonly_mode ? " [Readonly]" : ""),
2737 (file_modified ? " [Modified]" : ""),
2740 if (ret >= 0 && ret < trunc_at)
2741 return ret; /* it all fit */
2743 return trunc_at; /* had to truncate */
2746 //----- Force refresh of all Lines -----------------------------
2747 static void redraw(int full_screen)
2749 place_cursor(0, 0, FALSE); // put cursor in correct place
2750 clear_to_eos(); // tel terminal to erase display
2751 screen_erase(); // erase the internal screen buffer
2752 last_status_cksum = 0; // force status update
2753 refresh(full_screen); // this will redraw the entire display
2757 //----- Format a text[] line into a buffer ---------------------
2758 static char* format_line(char *src, int li)
2763 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2765 c = '~'; // char in col 0 in non-existent lines is '~'
2767 while (co < columns + tabstop) {
2768 // have we gone past the end?
2773 if ((c & 0x80) && !Isprint(c)) {
2776 if (c < ' ' || c == 0x7f) {
2780 while ((co % tabstop) != (tabstop - 1)) {
2788 c += '@'; // Ctrl-X -> 'X'
2793 // discard scrolled-off-to-the-left portion,
2794 // in tabstop-sized pieces
2795 if (ofs >= tabstop && co >= tabstop) {
2796 memmove(dest, dest + tabstop, co);
2803 // check "short line, gigantic offset" case
2806 // discard last scrolled off part
2809 // fill the rest with spaces
2811 memset(&dest[co], ' ', columns - co);
2815 //----- Refresh the changed screen lines -----------------------
2816 // Copy the source line from text[] into the buffer and note
2817 // if the current screenline is different from the new buffer.
2818 // If they differ then that line needs redrawing on the terminal.
2820 static void refresh(int full_screen)
2822 static int old_offset;
2825 char *tp, *sp; // pointer into text[] and screen[]
2827 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2828 int c = columns, r = rows;
2829 get_terminal_width_height(0, &columns, &rows);
2830 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2831 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2832 full_screen |= (c - columns) | (r - rows);
2834 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2835 tp = screenbegin; // index into text[] of top line
2837 // compare text[] to screen[] and mark screen[] lines that need updating
2838 for (li = 0; li < rows - 1; li++) {
2839 int cs, ce; // column start & end
2840 // format current text line
2841 char *out_buf = format_line(tp, li);
2843 // skip to the end of the current text[] line
2844 while (tp < end && *tp++ != '\n')
2847 // see if there are any changes between vitual screen and out_buf
2848 changed = FALSE; // assume no change
2851 sp = &screen[li * columns]; // start of screen line
2853 // force re-draw of every single column from 0 - columns-1
2856 // compare newly formatted buffer with virtual screen
2857 // look forward for first difference between buf and screen
2858 for (; cs <= ce; cs++) {
2859 if (out_buf[cs] != sp[cs]) {
2860 changed = TRUE; // mark for redraw
2865 // look backward for last difference between out_buf and screen
2866 for (; ce >= cs; ce--) {
2867 if (out_buf[ce] != sp[ce]) {
2868 changed = TRUE; // mark for redraw
2872 // now, cs is index of first diff, and ce is index of last diff
2874 // if horz offset has changed, force a redraw
2875 if (offset != old_offset) {
2880 // make a sanity check of columns indexes
2882 if (ce > columns - 1) ce = columns - 1;
2883 if (cs > ce) { cs = 0; ce = columns - 1; }
2884 // is there a change between vitual screen and out_buf
2886 // copy changed part of buffer to virtual screen
2887 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2889 // move cursor to column of first change
2890 //if (offset != old_offset) {
2891 // // place_cursor is still too stupid
2892 // // to handle offsets correctly
2893 // place_cursor(li, cs, FALSE);
2895 place_cursor(li, cs, TRUE);
2898 // write line out to terminal
2899 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2903 place_cursor(crow, ccol, TRUE);
2905 old_offset = offset;
2908 //---------------------------------------------------------------------
2909 //----- the Ascii Chart -----------------------------------------------
2911 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2912 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2913 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2914 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2915 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2916 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2917 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2918 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2919 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2920 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2921 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2922 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2923 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2924 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2925 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2926 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2927 //---------------------------------------------------------------------
2929 //----- Execute a Vi Command -----------------------------------
2930 static void do_cmd(char c)
2932 const char *msg = msg; // for compiler
2933 char c1, *p, *q, *save_dot;
2935 int dir = dir; // for compiler
2938 // c1 = c; // quiet the compiler
2939 // cnt = yf = 0; // quiet the compiler
2940 // msg = p = q = save_dot = buf; // quiet the compiler
2941 memset(buf, '\0', 12);
2945 /* if this is a cursor key, skip these checks */
2958 if (cmd_mode == 2) {
2959 // flip-flop Insert/Replace mode
2960 if (c == VI_K_INSERT)
2962 // we are 'R'eplacing the current *dot with new char
2964 // don't Replace past E-o-l
2965 cmd_mode = 1; // convert to insert
2967 if (1 <= c || Isprint(c)) {
2969 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2970 dot = char_insert(dot, c); // insert new char
2975 if (cmd_mode == 1) {
2976 // hitting "Insert" twice means "R" replace mode
2977 if (c == VI_K_INSERT) goto dc5;
2978 // insert the char c at "dot"
2979 if (1 <= c || Isprint(c)) {
2980 dot = char_insert(dot, c);
2995 #if ENABLE_FEATURE_VI_CRASHME
2996 case 0x14: // dc4 ctrl-T
2997 crashme = (crashme == 0) ? 1 : 0;
3028 //case 'u': // u- FIXME- there is no undo
3030 default: // unrecognised command
3038 not_implemented(buf);
3039 end_cmd_q(); // stop adding to q
3040 case 0x00: // nul- ignore
3042 case 2: // ctrl-B scroll up full screen
3043 case VI_K_PAGEUP: // Cursor Key Page Up
3044 dot_scroll(rows - 2, -1);
3046 #if ENABLE_FEATURE_VI_USE_SIGNALS
3047 case 0x03: // ctrl-C interrupt
3048 longjmp(restart, 1);
3050 case 26: // ctrl-Z suspend
3051 suspend_sig(SIGTSTP);
3054 case 4: // ctrl-D scroll down half screen
3055 dot_scroll((rows - 2) / 2, 1);
3057 case 5: // ctrl-E scroll down one line
3060 case 6: // ctrl-F scroll down full screen
3061 case VI_K_PAGEDOWN: // Cursor Key Page Down
3062 dot_scroll(rows - 2, 1);
3064 case 7: // ctrl-G show current status
3065 last_status_cksum = 0; // force status update
3067 case 'h': // h- move left
3068 case VI_K_LEFT: // cursor key Left
3069 case 8: // ctrl-H- move left (This may be ERASE char)
3070 case 0x7f: // DEL- move left (This may be ERASE char)
3076 case 10: // Newline ^J
3077 case 'j': // j- goto next line, same col
3078 case VI_K_DOWN: // cursor key Down
3082 dot_next(); // go to next B-o-l
3083 dot = move_to_col(dot, ccol + offset); // try stay in same col
3085 case 12: // ctrl-L force redraw whole screen
3086 case 18: // ctrl-R force redraw
3087 place_cursor(0, 0, FALSE); // put cursor in correct place
3088 clear_to_eos(); // tel terminal to erase display
3090 screen_erase(); // erase the internal screen buffer
3091 last_status_cksum = 0; // force status update
3092 refresh(TRUE); // this will redraw the entire display
3094 case 13: // Carriage Return ^M
3095 case '+': // +- goto next line
3102 case 21: // ctrl-U scroll up half screen
3103 dot_scroll((rows - 2) / 2, -1);
3105 case 25: // ctrl-Y scroll up one line
3111 cmd_mode = 0; // stop insrting
3113 last_status_cksum = 0; // force status update
3115 case ' ': // move right
3116 case 'l': // move right
3117 case VI_K_RIGHT: // Cursor Key Right
3123 #if ENABLE_FEATURE_VI_YANKMARK
3124 case '"': // "- name a register to use for Delete/Yank
3125 c1 = get_one_char();
3133 case '\'': // '- goto a specific mark
3134 c1 = get_one_char();
3139 q = mark[(unsigned char) c1];
3140 if (text <= q && q < end) {
3142 dot_begin(); // go to B-o-l
3145 } else if (c1 == '\'') { // goto previous context
3146 dot = swap_context(dot); // swap current and previous context
3147 dot_begin(); // go to B-o-l
3153 case 'm': // m- Mark a line
3154 // this is really stupid. If there are any inserts or deletes
3155 // between text[0] and dot then this mark will not point to the
3156 // correct location! It could be off by many lines!
3157 // Well..., at least its quick and dirty.
3158 c1 = get_one_char();
3162 // remember the line
3163 mark[(int) c1] = dot;
3168 case 'P': // P- Put register before
3169 case 'p': // p- put register after
3172 status_line_bold("Nothing in register %c", what_reg());
3175 // are we putting whole lines or strings
3176 if (strchr(p, '\n') != NULL) {
3178 dot_begin(); // putting lines- Put above
3181 // are we putting after very last line?
3182 if (end_line(dot) == (end - 1)) {
3183 dot = end; // force dot to end of text[]
3185 dot_next(); // next line, then put before
3190 dot_right(); // move to right, can move to NL
3192 dot = string_insert(dot, p); // insert the string
3193 end_cmd_q(); // stop adding to q
3195 case 'U': // U- Undo; replace current line with original version
3196 if (reg[Ureg] != 0) {
3197 p = begin_line(dot);
3199 p = text_hole_delete(p, q); // delete cur line
3200 p = string_insert(p, reg[Ureg]); // insert orig line
3205 #endif /* FEATURE_VI_YANKMARK */
3206 case '$': // $- goto end of line
3207 case VI_K_END: // Cursor Key End
3211 dot = end_line(dot);
3213 case '%': // %- find matching char of pair () [] {}
3214 for (q = dot; q < end && *q != '\n'; q++) {
3215 if (strchr("()[]{}", *q) != NULL) {
3216 // we found half of a pair
3217 p = find_pair(q, *q);
3229 case 'f': // f- forward to a user specified char
3230 last_forward_char = get_one_char(); // get the search char
3232 // dont separate these two commands. 'f' depends on ';'
3234 //**** fall thru to ... ';'
3235 case ';': // ;- look at rest of line for last forward char
3239 if (last_forward_char == 0)
3242 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3245 if (*q == last_forward_char)
3248 case '-': // -- goto prev line
3255 #if ENABLE_FEATURE_VI_DOT_CMD
3256 case '.': // .- repeat the last modifying command
3257 // Stuff the last_modifying_cmd back into stdin
3258 // and let it be re-executed.
3259 if (last_modifying_cmd != NULL) {
3260 ioq = ioq_start = xstrdup(last_modifying_cmd);
3264 #if ENABLE_FEATURE_VI_SEARCH
3265 case '?': // /- search for a pattern
3266 case '/': // /- search for a pattern
3269 q = get_input_line(buf); // get input line- use "status line"
3271 goto dc3; // if no pat re-use old pat
3272 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3273 // there is a new pat
3274 free(last_search_pattern);
3275 last_search_pattern = xstrdup(q);
3276 goto dc3; // now find the pattern
3278 // user changed mind and erased the "/"- do nothing
3280 case 'N': // N- backward search for last pattern
3284 dir = BACK; // assume BACKWARD search
3286 if (last_search_pattern[0] == '?') {
3290 goto dc4; // now search for pattern
3292 case 'n': // n- repeat search for last pattern
3293 // search rest of text[] starting at next char
3294 // if search fails return orignal "p" not the "p+1" address
3299 if (last_search_pattern == 0) {
3300 msg = "No previous regular expression";
3303 if (last_search_pattern[0] == '/') {
3304 dir = FORWARD; // assume FORWARD search
3307 if (last_search_pattern[0] == '?') {
3312 q = char_search(p, last_search_pattern + 1, dir, FULL);
3314 dot = q; // good search, update "dot"
3318 // no pattern found between "dot" and "end"- continue at top
3323 q = char_search(p, last_search_pattern + 1, dir, FULL);
3324 if (q != NULL) { // found something
3325 dot = q; // found new pattern- goto it
3326 msg = "search hit BOTTOM, continuing at TOP";
3328 msg = "search hit TOP, continuing at BOTTOM";
3331 msg = "Pattern not found";
3335 status_line_bold("%s", msg);
3337 case '{': // {- move backward paragraph
3338 q = char_search(dot, "\n\n", BACK, FULL);
3339 if (q != NULL) { // found blank line
3340 dot = next_line(q); // move to next blank line
3343 case '}': // }- move forward paragraph
3344 q = char_search(dot, "\n\n", FORWARD, FULL);
3345 if (q != NULL) { // found blank line
3346 dot = next_line(q); // move to next blank line
3349 #endif /* FEATURE_VI_SEARCH */
3350 case '0': // 0- goto begining of line
3360 if (c == '0' && cmdcnt < 1) {
3361 dot_begin(); // this was a standalone zero
3363 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3366 case ':': // :- the colon mode commands
3367 p = get_input_line(":"); // get input line- use "status line"
3368 #if ENABLE_FEATURE_VI_COLON
3369 colon(p); // execute the command
3372 p++; // move past the ':'
3376 if (strncasecmp(p, "quit", cnt) == 0
3377 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3379 if (file_modified && p[1] != '!') {
3380 status_line_bold("No write since last change (:quit! overrides)");
3384 } else if (strncasecmp(p, "write", cnt) == 0
3385 || strncasecmp(p, "wq", cnt) == 0
3386 || strncasecmp(p, "wn", cnt) == 0
3387 || strncasecmp(p, "x", cnt) == 0
3389 cnt = file_write(current_filename, text, end - 1);
3392 status_line_bold("Write error: %s", strerror(errno));
3395 last_file_modified = -1;
3396 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3397 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3398 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3403 } else if (strncasecmp(p, "file", cnt) == 0) {
3404 last_status_cksum = 0; // force status update
3405 } else if (sscanf(p, "%d", &j) > 0) {
3406 dot = find_line(j); // go to line # j
3408 } else { // unrecognised cmd
3411 #endif /* !FEATURE_VI_COLON */
3413 case '<': // <- Left shift something
3414 case '>': // >- Right shift something
3415 cnt = count_lines(text, dot); // remember what line we are on
3416 c1 = get_one_char(); // get the type of thing to delete
3417 find_range(&p, &q, c1);
3418 yank_delete(p, q, 1, YANKONLY); // save copy before change
3421 i = count_lines(p, q); // # of lines we are shifting
3422 for ( ; i > 0; i--, p = next_line(p)) {
3424 // shift left- remove tab or 8 spaces
3426 // shrink buffer 1 char
3427 text_hole_delete(p, p);
3428 } else if (*p == ' ') {
3429 // we should be calculating columns, not just SPACE
3430 for (j = 0; *p == ' ' && j < tabstop; j++) {
3431 text_hole_delete(p, p);
3434 } else if (c == '>') {
3435 // shift right -- add tab or 8 spaces
3436 char_insert(p, '\t');
3439 dot = find_line(cnt); // what line were we on
3441 end_cmd_q(); // stop adding to q
3443 case 'A': // A- append at e-o-l
3444 dot_end(); // go to e-o-l
3445 //**** fall thru to ... 'a'
3446 case 'a': // a- append after current char
3451 case 'B': // B- back a blank-delimited Word
3452 case 'E': // E- end of a blank-delimited word
3453 case 'W': // W- forward a blank-delimited word
3460 if (c == 'W' || isspace(dot[dir])) {
3461 dot = skip_thing(dot, 1, dir, S_TO_WS);
3462 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3465 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3467 case 'C': // C- Change to e-o-l
3468 case 'D': // D- delete to e-o-l
3470 dot = dollar_line(dot); // move to before NL
3471 // copy text into a register and delete
3472 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3474 goto dc_i; // start inserting
3475 #if ENABLE_FEATURE_VI_DOT_CMD
3477 end_cmd_q(); // stop adding to q
3480 case 'G': // G- goto to a line number (default= E-O-F)
3481 dot = end - 1; // assume E-O-F
3483 dot = find_line(cmdcnt); // what line is #cmdcnt
3487 case 'H': // H- goto top line on screen
3489 if (cmdcnt > (rows - 1)) {
3490 cmdcnt = (rows - 1);
3497 case 'I': // I- insert before first non-blank
3500 //**** fall thru to ... 'i'
3501 case 'i': // i- insert before current char
3502 case VI_K_INSERT: // Cursor Key Insert
3504 cmd_mode = 1; // start insrting
3506 case 'J': // J- join current and next lines together
3510 dot_end(); // move to NL
3511 if (dot < end - 1) { // make sure not last char in text[]
3512 *dot++ = ' '; // replace NL with space
3514 while (isblank(*dot)) { // delete leading WS
3518 end_cmd_q(); // stop adding to q
3520 case 'L': // L- goto bottom line on screen
3522 if (cmdcnt > (rows - 1)) {
3523 cmdcnt = (rows - 1);
3531 case 'M': // M- goto middle line on screen
3533 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3534 dot = next_line(dot);
3536 case 'O': // O- open a empty line above
3538 p = begin_line(dot);
3539 if (p[-1] == '\n') {
3541 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3543 dot = char_insert(dot, '\n');
3546 dot = char_insert(dot, '\n'); // i\n ESC
3551 case 'R': // R- continuous Replace char
3558 case 'X': // X- delete char before dot
3559 case 'x': // x- delete the current char
3560 case 's': // s- substitute the current char
3567 if (dot[dir] != '\n') {
3569 dot--; // delete prev char
3570 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3573 goto dc_i; // start insrting
3574 end_cmd_q(); // stop adding to q
3576 case 'Z': // Z- if modified, {write}; exit
3577 // ZZ means to save file (if necessary), then exit
3578 c1 = get_one_char();
3583 if (file_modified) {
3584 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3585 status_line_bold("\"%s\" File is read only", current_filename);
3588 cnt = file_write(current_filename, text, end - 1);
3591 status_line_bold("Write error: %s", strerror(errno));
3592 } else if (cnt == (end - 1 - text + 1)) {
3599 case '^': // ^- move to first non-blank on line
3603 case 'b': // b- back a word
3604 case 'e': // e- end of word
3611 if ((dot + dir) < text || (dot + dir) > end - 1)
3614 if (isspace(*dot)) {
3615 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3617 if (isalnum(*dot) || *dot == '_') {
3618 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3619 } else if (ispunct(*dot)) {
3620 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3623 case 'c': // c- change something
3624 case 'd': // d- delete something
3625 #if ENABLE_FEATURE_VI_YANKMARK
3626 case 'y': // y- yank something
3627 case 'Y': // Y- Yank a line
3629 yf = YANKDEL; // assume either "c" or "d"
3630 #if ENABLE_FEATURE_VI_YANKMARK
3631 if (c == 'y' || c == 'Y')
3636 c1 = get_one_char(); // get the type of thing to delete
3637 find_range(&p, &q, c1);
3638 if (c1 == 27) { // ESC- user changed mind and wants out
3639 c = c1 = 27; // Escape- do nothing
3640 } else if (strchr("wW", c1)) {
3642 // don't include trailing WS as part of word
3643 while (isblank(*q)) {
3644 if (q <= text || q[-1] == '\n')
3649 dot = yank_delete(p, q, 0, yf); // delete word
3650 } else if (strchr("^0bBeEft$", c1)) {
3651 // single line copy text into a register and delete
3652 dot = yank_delete(p, q, 0, yf); // delete word
3653 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3654 // multiple line copy text into a register and delete
3655 dot = yank_delete(p, q, 1, yf); // delete lines
3657 dot = char_insert(dot, '\n');
3658 // on the last line of file don't move to prev line
3659 if (dot != (end-1)) {
3662 } else if (c == 'd') {
3667 // could not recognize object
3668 c = c1 = 27; // error-
3672 // if CHANGING, not deleting, start inserting after the delete
3674 strcpy(buf, "Change");
3675 goto dc_i; // start inserting
3678 strcpy(buf, "Delete");
3680 #if ENABLE_FEATURE_VI_YANKMARK
3681 if (c == 'y' || c == 'Y') {
3682 strcpy(buf, "Yank");
3686 for (cnt = 0; p <= q; p++) {
3690 status_line("%s %d lines (%d chars) using [%c]",
3691 buf, cnt, strlen(reg[YDreg]), what_reg());
3693 end_cmd_q(); // stop adding to q
3696 case 'k': // k- goto prev line, same col
3697 case VI_K_UP: // cursor key Up
3702 dot = move_to_col(dot, ccol + offset); // try stay in same col
3704 case 'r': // r- replace the current char with user input
3705 c1 = get_one_char(); // get the replacement char
3708 file_modified++; // has the file been modified
3710 end_cmd_q(); // stop adding to q
3712 case 't': // t- move to char prior to next x
3713 last_forward_char = get_one_char();
3715 if (*dot == last_forward_char)
3717 last_forward_char= 0;
3719 case 'w': // w- forward a word
3723 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3724 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3725 } else if (ispunct(*dot)) { // we are on PUNCT
3726 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3729 dot++; // move over word
3730 if (isspace(*dot)) {
3731 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3735 c1 = get_one_char(); // get the replacement char
3738 cnt = (rows - 2) / 2; // put dot at center
3740 cnt = rows - 2; // put dot at bottom
3741 screenbegin = begin_line(dot); // start dot at top
3742 dot_scroll(cnt, -1);
3744 case '|': // |- move to column "cmdcnt"
3745 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3747 case '~': // ~- flip the case of letters a-z -> A-Z
3751 if (islower(*dot)) {
3752 *dot = toupper(*dot);
3753 file_modified++; // has the file been modified
3754 } else if (isupper(*dot)) {
3755 *dot = tolower(*dot);
3756 file_modified++; // has the file been modified
3759 end_cmd_q(); // stop adding to q
3761 //----- The Cursor and Function Keys -----------------------------
3762 case VI_K_HOME: // Cursor Key Home
3765 // The Fn keys could point to do_macro which could translate them
3766 case VI_K_FUN1: // Function Key F1
3767 case VI_K_FUN2: // Function Key F2
3768 case VI_K_FUN3: // Function Key F3
3769 case VI_K_FUN4: // Function Key F4
3770 case VI_K_FUN5: // Function Key F5
3771 case VI_K_FUN6: // Function Key F6
3772 case VI_K_FUN7: // Function Key F7
3773 case VI_K_FUN8: // Function Key F8
3774 case VI_K_FUN9: // Function Key F9
3775 case VI_K_FUN10: // Function Key F10
3776 case VI_K_FUN11: // Function Key F11
3777 case VI_K_FUN12: // Function Key F12
3782 // if text[] just became empty, add back an empty line
3784 char_insert(text, '\n'); // start empty buf with dummy line
3787 // it is OK for dot to exactly equal to end, otherwise check dot validity
3789 dot = bound_dot(dot); // make sure "dot" is valid
3791 #if ENABLE_FEATURE_VI_YANKMARK
3792 check_context(c); // update the current context
3796 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3797 cnt = dot - begin_line(dot);
3798 // Try to stay off of the Newline
3799 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3803 #if ENABLE_FEATURE_VI_CRASHME
3804 static int totalcmds = 0;
3805 static int Mp = 85; // Movement command Probability
3806 static int Np = 90; // Non-movement command Probability
3807 static int Dp = 96; // Delete command Probability
3808 static int Ip = 97; // Insert command Probability
3809 static int Yp = 98; // Yank command Probability
3810 static int Pp = 99; // Put command Probability
3811 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3812 static const char chars[20] = "\t012345 abcdABCD-=.$";
3813 static const char *const words[20] = {
3814 "this", "is", "a", "test",
3815 "broadcast", "the", "emergency", "of",
3816 "system", "quick", "brown", "fox",
3817 "jumped", "over", "lazy", "dogs",
3818 "back", "January", "Febuary", "March"
3820 static const char *const lines[20] = {
3821 "You should have received a copy of the GNU General Public License\n",
3822 "char c, cm, *cmd, *cmd1;\n",
3823 "generate a command by percentages\n",
3824 "Numbers may be typed as a prefix to some commands.\n",
3825 "Quit, discarding changes!\n",
3826 "Forced write, if permission originally not valid.\n",
3827 "In general, any ex or ed command (such as substitute or delete).\n",
3828 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3829 "Please get w/ me and I will go over it with you.\n",
3830 "The following is a list of scheduled, committed changes.\n",
3831 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3832 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3833 "Any question about transactions please contact Sterling Huxley.\n",
3834 "I will try to get back to you by Friday, December 31.\n",
3835 "This Change will be implemented on Friday.\n",
3836 "Let me know if you have problems accessing this;\n",
3837 "Sterling Huxley recently added you to the access list.\n",
3838 "Would you like to go to lunch?\n",
3839 "The last command will be automatically run.\n",
3840 "This is too much english for a computer geek.\n",
3842 static char *multilines[20] = {
3843 "You should have received a copy of the GNU General Public License\n",
3844 "char c, cm, *cmd, *cmd1;\n",
3845 "generate a command by percentages\n",
3846 "Numbers may be typed as a prefix to some commands.\n",
3847 "Quit, discarding changes!\n",
3848 "Forced write, if permission originally not valid.\n",
3849 "In general, any ex or ed command (such as substitute or delete).\n",
3850 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3851 "Please get w/ me and I will go over it with you.\n",
3852 "The following is a list of scheduled, committed changes.\n",
3853 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3854 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3855 "Any question about transactions please contact Sterling Huxley.\n",
3856 "I will try to get back to you by Friday, December 31.\n",
3857 "This Change will be implemented on Friday.\n",
3858 "Let me know if you have problems accessing this;\n",
3859 "Sterling Huxley recently added you to the access list.\n",
3860 "Would you like to go to lunch?\n",
3861 "The last command will be automatically run.\n",
3862 "This is too much english for a computer geek.\n",
3865 // create a random command to execute
3866 static void crash_dummy()
3868 static int sleeptime; // how long to pause between commands
3869 char c, cm, *cmd, *cmd1;
3870 int i, cnt, thing, rbi, startrbi, percent;
3872 // "dot" movement commands
3873 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3875 // is there already a command running?
3876 if (chars_to_parse > 0)
3880 sleeptime = 0; // how long to pause between commands
3881 memset(readbuffer, '\0', sizeof(readbuffer));
3882 // generate a command by percentages
3883 percent = (int) lrand48() % 100; // get a number from 0-99
3884 if (percent < Mp) { // Movement commands
3885 // available commands
3888 } else if (percent < Np) { // non-movement commands
3889 cmd = "mz<>\'\""; // available commands
3891 } else if (percent < Dp) { // Delete commands
3892 cmd = "dx"; // available commands
3894 } else if (percent < Ip) { // Inset commands
3895 cmd = "iIaAsrJ"; // available commands
3897 } else if (percent < Yp) { // Yank commands
3898 cmd = "yY"; // available commands
3900 } else if (percent < Pp) { // Put commands
3901 cmd = "pP"; // available commands
3904 // We do not know how to handle this command, try again
3908 // randomly pick one of the available cmds from "cmd[]"
3909 i = (int) lrand48() % strlen(cmd);
3911 if (strchr(":\024", cm))
3912 goto cd0; // dont allow colon or ctrl-T commands
3913 readbuffer[rbi++] = cm; // put cmd into input buffer
3915 // now we have the command-
3916 // there are 1, 2, and multi char commands
3917 // find out which and generate the rest of command as necessary
3918 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3919 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3920 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3921 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3923 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3925 readbuffer[rbi++] = c; // add movement to input buffer
3927 if (strchr("iIaAsc", cm)) { // multi-char commands
3929 // change some thing
3930 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3932 readbuffer[rbi++] = c; // add movement to input buffer
3934 thing = (int) lrand48() % 4; // what thing to insert
3935 cnt = (int) lrand48() % 10; // how many to insert
3936 for (i = 0; i < cnt; i++) {
3937 if (thing == 0) { // insert chars
3938 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3939 } else if (thing == 1) { // insert words
3940 strcat(readbuffer, words[(int) lrand48() % 20]);
3941 strcat(readbuffer, " ");
3942 sleeptime = 0; // how fast to type
3943 } else if (thing == 2) { // insert lines
3944 strcat(readbuffer, lines[(int) lrand48() % 20]);
3945 sleeptime = 0; // how fast to type
3946 } else { // insert multi-lines
3947 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3948 sleeptime = 0; // how fast to type
3951 strcat(readbuffer, "\033");
3953 chars_to_parse = strlen(readbuffer);
3957 mysleep(sleeptime); // sleep 1/100 sec
3960 // test to see if there are any errors
3961 static void crash_test()
3963 static time_t oldtim;
3970 strcat(msg, "end<text ");
3972 if (end > textend) {
3973 strcat(msg, "end>textend ");
3976 strcat(msg, "dot<text ");
3979 strcat(msg, "dot>end ");
3981 if (screenbegin < text) {
3982 strcat(msg, "screenbegin<text ");
3984 if (screenbegin > end - 1) {
3985 strcat(msg, "screenbegin>end-1 ");
3990 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3991 totalcmds, last_input_char, msg, SOs, SOn);
3993 while (safe_read(0, d, 1) > 0) {
3994 if (d[0] == '\n' || d[0] == '\r')
4000 if (tim >= (oldtim + 3)) {
4001 sprintf(status_buffer,
4002 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4003 totalcmds, M, N, I, D, Y, P, U, end - text + 1);