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)
1166 write1("[Hit return to continue]");
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
1185 beg_cur = begin_line(d); // first char of cur line
1187 if (beg_cur < screenbegin) {
1188 // "d" is before top line on screen
1189 // how many lines do we have to move
1190 cnt = count_lines(beg_cur, screenbegin);
1192 screenbegin = beg_cur;
1193 if (cnt > (rows - 1) / 2) {
1194 // we moved too many lines. put "dot" in middle of screen
1195 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1196 screenbegin = prev_line(screenbegin);
1200 char *end_scr; // begin and end of screen
1201 end_scr = end_screen(); // last char of screen
1202 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);
1217 // "d" is on screen- find out which row
1219 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1225 // find out what col "d" is on
1227 while (tp < d) { // drive "co" to correct column
1228 if (*tp == '\n') //vda || *tp == '\0')
1231 // handle tabs like real vi
1232 if (d == tp && cmd_mode) {
1235 co = next_tabstop(co);
1237 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1238 co++; // display as ^X, use 2 columns
1244 // "co" is the column where "dot" is.
1245 // The screen has "columns" columns.
1246 // The currently displayed columns are 0+offset -- columns+ofset
1247 // |-------------------------------------------------------------|
1249 // offset | |------- columns ----------------|
1251 // If "co" is already in this range then we do not have to adjust offset
1252 // but, we do have to subtract the "offset" bias from "co".
1253 // If "co" is outside this range then we have to change "offset".
1254 // If the first char of a line is a tab the cursor will try to stay
1255 // in column 7, but we have to set offset to 0.
1257 if (co < 0 + offset) {
1260 if (co >= columns + offset) {
1261 offset = co - columns + 1;
1263 // if the first char of the line is a tab, and "dot" is sitting on it
1264 // force offset to 0.
1265 if (d == beg_cur && *d == '\t') {
1274 //----- Text Movement Routines ---------------------------------
1275 static char *begin_line(char *p) // return pointer to first char cur line
1278 p = memrchr(text, '\n', p - text);
1286 static char *end_line(char *p) // return pointer to NL of cur line line
1289 p = memchr(p, '\n', end - p - 1);
1296 static char *dollar_line(char *p) // return pointer to just before NL line
1299 // Try to stay off of the Newline
1300 if (*p == '\n' && (p - begin_line(p)) > 0)
1305 static char *prev_line(char *p) // return pointer first char prev line
1307 p = begin_line(p); // goto begining of cur line
1308 if (p[-1] == '\n' && p > text)
1309 p--; // step to prev line
1310 p = begin_line(p); // goto begining of prev line
1314 static char *next_line(char *p) // return pointer first char next line
1317 if (*p == '\n' && p < end - 1)
1318 p++; // step to next line
1322 //----- Text Information Routines ------------------------------
1323 static char *end_screen(void)
1328 // find new bottom line
1330 for (cnt = 0; cnt < rows - 2; cnt++)
1336 // count line from start to stop
1337 static int count_lines(char *start, char *stop)
1342 if (stop < start) { // start and stop are backwards- reverse them
1348 stop = end_line(stop);
1349 while (start <= stop && start <= end - 1) {
1350 start = end_line(start);
1358 static char *find_line(int li) // find begining of line #li
1362 for (q = text; li > 1; li--) {
1368 //----- Dot Movement Routines ----------------------------------
1369 static void dot_left(void)
1371 if (dot > text && dot[-1] != '\n')
1375 static void dot_right(void)
1377 if (dot < end - 1 && *dot != '\n')
1381 static void dot_begin(void)
1383 dot = begin_line(dot); // return pointer to first char cur line
1386 static void dot_end(void)
1388 dot = end_line(dot); // return pointer to last char cur line
1391 static char *move_to_col(char *p, int l)
1397 while (co < l && p < end) {
1398 if (*p == '\n') //vda || *p == '\0')
1401 co = next_tabstop(co);
1402 } else if (*p < ' ' || *p == 127) {
1403 co++; // display as ^X, use 2 columns
1411 static void dot_next(void)
1413 dot = next_line(dot);
1416 static void dot_prev(void)
1418 dot = prev_line(dot);
1421 static void dot_scroll(int cnt, int dir)
1425 for (; cnt > 0; cnt--) {
1428 // ctrl-Y scroll up one line
1429 screenbegin = prev_line(screenbegin);
1432 // ctrl-E scroll down one line
1433 screenbegin = next_line(screenbegin);
1436 // make sure "dot" stays on the screen so we dont scroll off
1437 if (dot < screenbegin)
1439 q = end_screen(); // find new bottom line
1441 dot = begin_line(q); // is dot is below bottom line?
1445 static void dot_skip_over_ws(void)
1448 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1452 static void dot_delete(void) // delete the char at 'dot'
1454 text_hole_delete(dot, dot);
1457 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1459 if (p >= end && end > text) {
1461 indicate_error('1');
1465 indicate_error('2');
1470 //----- Helper Utility Routines --------------------------------
1472 //----------------------------------------------------------------
1473 //----- Char Routines --------------------------------------------
1474 /* Chars that are part of a word-
1475 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1476 * Chars that are Not part of a word (stoppers)
1477 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1478 * Chars that are WhiteSpace
1479 * TAB NEWLINE VT FF RETURN SPACE
1480 * DO NOT COUNT NEWLINE AS WHITESPACE
1483 static char *new_screen(int ro, int co)
1488 screensize = ro * co + 8;
1489 screen = xmalloc(screensize);
1490 // initialize the new screen. assume this will be a empty file.
1492 // non-existent text[] lines start with a tilde (~).
1493 for (li = 1; li < ro - 1; li++) {
1494 screen[(li * co) + 0] = '~';
1499 #if ENABLE_FEATURE_VI_SEARCH
1500 static int mycmp(const char * s1, const char * s2, int len)
1504 i = strncmp(s1, s2, len);
1505 if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) {
1506 i = strncasecmp(s1, s2, len);
1511 // search for pattern starting at p
1512 static char *char_search(char * p, const char * pat, int dir, int range)
1514 #ifndef REGEX_SEARCH
1519 if (dir == FORWARD) {
1520 stop = end - 1; // assume range is p - end-1
1521 if (range == LIMITED)
1522 stop = next_line(p); // range is to next line
1523 for (start = p; start < stop; start++) {
1524 if (mycmp(start, pat, len) == 0) {
1528 } else if (dir == BACK) {
1529 stop = text; // assume range is text - p
1530 if (range == LIMITED)
1531 stop = prev_line(p); // range is to prev line
1532 for (start = p - len; start >= stop; start--) {
1533 if (mycmp(start, pat, len) == 0) {
1538 // pattern not found
1540 #else /* REGEX_SEARCH */
1542 struct re_pattern_buffer preg;
1546 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1552 // assume a LIMITED forward search
1560 // count the number of chars to search over, forward or backward
1564 // RANGE could be negative if we are searching backwards
1567 q = re_compile_pattern(pat, strlen(pat), &preg);
1569 // The pattern was not compiled
1570 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1571 i = 0; // return p if pattern not compiled
1581 // search for the compiled pattern, preg, in p[]
1582 // range < 0- search backward
1583 // range > 0- search forward
1585 // re_search() < 0 not found or error
1586 // re_search() > 0 index of found pattern
1587 // struct pattern char int int int struct reg
1588 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1589 i = re_search(&preg, q, size, 0, range, 0);
1592 i = 0; // return NULL if pattern not found
1595 if (dir == FORWARD) {
1601 #endif /* REGEX_SEARCH */
1603 #endif /* FEATURE_VI_SEARCH */
1605 static char *char_insert(char * p, char c) // insert the char c at 'p'
1607 if (c == 22) { // Is this an ctrl-V?
1608 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1609 p--; // backup onto ^
1610 refresh(FALSE); // show the ^
1614 file_modified++; // has the file been modified
1615 } else if (c == 27) { // Is this an ESC?
1618 end_cmd_q(); // stop adding to q
1619 last_status_cksum = 0; // force status update
1620 if ((p[-1] != '\n') && (dot > text)) {
1623 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1625 if ((p[-1] != '\n') && (dot>text)) {
1627 p = text_hole_delete(p, p); // shrink buffer 1 char
1630 // insert a char into text[]
1631 char *sp; // "save p"
1634 c = '\n'; // translate \r to \n
1635 sp = p; // remember addr of insert
1636 p = stupid_insert(p, c); // insert the char
1637 #if ENABLE_FEATURE_VI_SETOPTS
1638 if (showmatch && strchr(")]}", *sp) != NULL) {
1641 if (autoindent && c == '\n') { // auto indent the new line
1644 q = prev_line(p); // use prev line as templet
1645 for (; isblank(*q); q++) {
1646 p = stupid_insert(p, *q); // insert the char
1654 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1656 p = text_hole_make(p, 1);
1659 file_modified++; // has the file been modified
1665 static char find_range(char ** start, char ** stop, char c)
1667 char *save_dot, *p, *q;
1673 if (strchr("cdy><", c)) {
1674 // these cmds operate on whole lines
1675 p = q = begin_line(p);
1676 for (cnt = 1; cnt < cmdcnt; cnt++) {
1680 } else if (strchr("^%$0bBeEft", c)) {
1681 // These cmds operate on char positions
1682 do_cmd(c); // execute movement cmd
1684 } else if (strchr("wW", c)) {
1685 do_cmd(c); // execute movement cmd
1686 // if we are at the next word's first char
1687 // step back one char
1688 // but check the possibilities when it is true
1689 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1690 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1691 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1692 dot--; // move back off of next word
1693 if (dot > text && *dot == '\n')
1694 dot--; // stay off NL
1696 } else if (strchr("H-k{", c)) {
1697 // these operate on multi-lines backwards
1698 q = end_line(dot); // find NL
1699 do_cmd(c); // execute movement cmd
1702 } else if (strchr("L+j}\r\n", c)) {
1703 // these operate on multi-lines forwards
1704 p = begin_line(dot);
1705 do_cmd(c); // execute movement cmd
1706 dot_end(); // find NL
1709 c = 27; // error- return an ESC char
1722 static int st_test(char * p, int type, int dir, char * tested)
1732 if (type == S_BEFORE_WS) {
1734 test = ((!isspace(c)) || c == '\n');
1736 if (type == S_TO_WS) {
1738 test = ((!isspace(c)) || c == '\n');
1740 if (type == S_OVER_WS) {
1742 test = ((isspace(c)));
1744 if (type == S_END_PUNCT) {
1746 test = ((ispunct(c)));
1748 if (type == S_END_ALNUM) {
1750 test = ((isalnum(c)) || c == '_');
1756 static char *skip_thing(char * p, int linecnt, int dir, int type)
1760 while (st_test(p, type, dir, &c)) {
1761 // make sure we limit search to correct number of lines
1762 if (c == '\n' && --linecnt < 1)
1764 if (dir >= 0 && p >= end - 1)
1766 if (dir < 0 && p <= text)
1768 p += dir; // move to next char
1773 // find matching char of pair () [] {}
1774 static char *find_pair(char * p, const char c)
1781 dir = 1; // assume forward
1805 for (q = p + dir; text <= q && q < end; q += dir) {
1806 // look for match, count levels of pairs (( ))
1808 level++; // increase pair levels
1810 level--; // reduce pair level
1812 break; // found matching pair
1815 q = NULL; // indicate no match
1819 #if ENABLE_FEATURE_VI_SETOPTS
1820 // show the matching char of a pair, () [] {}
1821 static void showmatching(char *p)
1825 // we found half of a pair
1826 q = find_pair(p, *p); // get loc of matching char
1828 indicate_error('3'); // no matching char
1830 // "q" now points to matching pair
1831 save_dot = dot; // remember where we are
1832 dot = q; // go to new loc
1833 refresh(FALSE); // let the user see it
1834 mysleep(40); // give user some time
1835 dot = save_dot; // go back to old loc
1839 #endif /* FEATURE_VI_SETOPTS */
1841 // open a hole in text[]
1842 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1851 cnt = end - src; // the rest of buffer
1852 if ( ((end + size) >= (text + text_size)) // TODO: realloc here
1853 || memmove(dest, src, cnt) != dest) {
1854 status_line_bold("can't create room for new characters");
1858 memset(p, ' ', size); // clear new hole
1859 end += size; // adjust the new END
1860 file_modified++; // has the file been modified
1865 // close a hole in text[]
1866 static char *text_hole_delete(char * p, char * q) // delete "p" through "q", inclusive
1871 // move forwards, from beginning
1875 if (q < p) { // they are backward- swap them
1879 hole_size = q - p + 1;
1881 if (src < text || src > end)
1883 if (dest < text || dest >= end)
1886 goto thd_atend; // just delete the end of the buffer
1887 if (memmove(dest, src, cnt) != dest) {
1888 status_line_bold("can't delete the character");
1891 end = end - hole_size; // adjust the new END
1893 dest = end - 1; // make sure dest in below end-1
1895 dest = end = text; // keep pointers valid
1896 file_modified++; // has the file been modified
1901 // copy text into register, then delete text.
1902 // if dist <= 0, do not include, or go past, a NewLine
1904 static char *yank_delete(char * start, char * stop, int dist, int yf)
1908 // make sure start <= stop
1910 // they are backwards, reverse them
1916 // we cannot cross NL boundaries
1920 // dont go past a NewLine
1921 for (; p + 1 <= stop; p++) {
1923 stop = p; // "stop" just before NewLine
1929 #if ENABLE_FEATURE_VI_YANKMARK
1930 text_yank(start, stop, YDreg);
1932 if (yf == YANKDEL) {
1933 p = text_hole_delete(start, stop);
1938 static void show_help(void)
1940 puts("These features are available:"
1941 #if ENABLE_FEATURE_VI_SEARCH
1942 "\n\tPattern searches with / and ?"
1944 #if ENABLE_FEATURE_VI_DOT_CMD
1945 "\n\tLast command repeat with \'.\'"
1947 #if ENABLE_FEATURE_VI_YANKMARK
1948 "\n\tLine marking with 'x"
1949 "\n\tNamed buffers with \"x"
1951 #if ENABLE_FEATURE_VI_READONLY
1952 "\n\tReadonly if vi is called as \"view\""
1953 "\n\tReadonly with -R command line arg"
1955 #if ENABLE_FEATURE_VI_SET
1956 "\n\tSome colon mode commands with \':\'"
1958 #if ENABLE_FEATURE_VI_SETOPTS
1959 "\n\tSettable options with \":set\""
1961 #if ENABLE_FEATURE_VI_USE_SIGNALS
1962 "\n\tSignal catching- ^C"
1963 "\n\tJob suspend and resume with ^Z"
1965 #if ENABLE_FEATURE_VI_WIN_RESIZE
1966 "\n\tAdapt to window re-sizes"
1971 #if ENABLE_FEATURE_VI_DOT_CMD
1972 static void start_new_cmd_q(char c)
1974 // get buffer for new cmd
1975 if (!last_modifying_cmd)
1976 last_modifying_cmd = xzalloc(MAX_INPUT_LEN);
1977 // if there is a current cmd count put it in the buffer first
1979 sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1980 else { // just save char c onto queue
1981 last_modifying_cmd[0] = c;
1982 last_modifying_cmd[1] = '\0';
1987 static void end_cmd_q(void)
1989 #if ENABLE_FEATURE_VI_YANKMARK
1990 YDreg = 26; // go back to default Yank/Delete reg
1994 #endif /* FEATURE_VI_DOT_CMD */
1996 #if ENABLE_FEATURE_VI_YANKMARK \
1997 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1998 || ENABLE_FEATURE_VI_CRASHME
1999 static char *string_insert(char * p, char * s) // insert the string at 'p'
2004 if (text_hole_make(p, i)) {
2006 for (cnt = 0; *s != '\0'; s++) {
2010 #if ENABLE_FEATURE_VI_YANKMARK
2011 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2018 #if ENABLE_FEATURE_VI_YANKMARK
2019 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2024 if (q < p) { // they are backwards- reverse them
2031 free(t); // if already a yank register, free it
2032 t = xmalloc(cnt + 1); // get a new register
2033 memset(t, '\0', cnt + 1); // clear new text[]
2034 strncpy(t, p, cnt); // copy text[] into bufer
2039 static char what_reg(void)
2043 c = 'D'; // default to D-reg
2044 if (0 <= YDreg && YDreg <= 25)
2045 c = 'a' + (char) YDreg;
2053 static void check_context(char cmd)
2055 // A context is defined to be "modifying text"
2056 // Any modifying command establishes a new context.
2058 if (dot < context_start || dot > context_end) {
2059 if (strchr(modifying_cmds, cmd) != NULL) {
2060 // we are trying to modify text[]- make this the current context
2061 mark[27] = mark[26]; // move cur to prev
2062 mark[26] = dot; // move local to cur
2063 context_start = prev_line(prev_line(dot));
2064 context_end = next_line(next_line(dot));
2065 //loiter= start_loiter= now;
2070 static char *swap_context(char *p) // goto new context for '' command make this the current context
2074 // the current context is in mark[26]
2075 // the previous context is in mark[27]
2076 // only swap context if other context is valid
2077 if (text <= mark[27] && mark[27] <= end - 1) {
2079 mark[27] = mark[26];
2081 p = mark[26]; // where we are going- previous context
2082 context_start = prev_line(prev_line(prev_line(p)));
2083 context_end = next_line(next_line(next_line(p)));
2087 #endif /* FEATURE_VI_YANKMARK */
2089 //----- Set terminal attributes --------------------------------
2090 static void rawmode(void)
2092 tcgetattr(0, &term_orig);
2093 term_vi = term_orig;
2094 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2095 term_vi.c_iflag &= (~IXON & ~ICRNL);
2096 term_vi.c_oflag &= (~ONLCR);
2097 term_vi.c_cc[VMIN] = 1;
2098 term_vi.c_cc[VTIME] = 0;
2099 erase_char = term_vi.c_cc[VERASE];
2100 tcsetattr(0, TCSANOW, &term_vi);
2103 static void cookmode(void)
2106 tcsetattr(0, TCSANOW, &term_orig);
2109 //----- Come here when we get a window resize signal ---------
2110 #if ENABLE_FEATURE_VI_USE_SIGNALS
2111 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2113 // FIXME: do it in main loop!!!
2114 signal(SIGWINCH, winch_sig);
2115 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2116 get_terminal_width_height(0, &columns, &rows);
2117 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2118 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2120 new_screen(rows, columns); // get memory for virtual screen
2121 redraw(TRUE); // re-draw the screen
2124 //----- Come here when we get a continue signal -------------------
2125 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2127 rawmode(); // terminal to "raw"
2128 last_status_cksum = 0; // force status update
2129 redraw(TRUE); // re-draw the screen
2131 signal(SIGTSTP, suspend_sig);
2132 signal(SIGCONT, SIG_DFL);
2133 kill(my_pid, SIGCONT);
2136 //----- Come here when we get a Suspend signal -------------------
2137 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2139 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2140 clear_to_eol(); // Erase to end of line
2141 cookmode(); // terminal to "cooked"
2143 signal(SIGCONT, cont_sig);
2144 signal(SIGTSTP, SIG_DFL);
2145 kill(my_pid, SIGTSTP);
2148 //----- Come here when we get a signal ---------------------------
2149 static void catch_sig(int sig)
2151 signal(SIGINT, catch_sig);
2153 longjmp(restart, sig);
2155 #endif /* FEATURE_VI_USE_SIGNALS */
2157 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2159 struct pollfd pfd[1];
2162 pfd[0].events = POLLIN;
2163 return safe_poll(pfd, 1, hund*10) > 0;
2166 static int chars_to_parse;
2168 //----- IO Routines --------------------------------------------
2169 static char readit(void) // read (maybe cursor) key from stdin
2178 static const struct esc_cmds esccmds[] = {
2179 {"OA" , VI_K_UP }, // cursor key Up
2180 {"OB" , VI_K_DOWN }, // cursor key Down
2181 {"OC" , VI_K_RIGHT }, // Cursor Key Right
2182 {"OD" , VI_K_LEFT }, // cursor key Left
2183 {"OH" , VI_K_HOME }, // Cursor Key Home
2184 {"OF" , VI_K_END }, // Cursor Key End
2185 {"[A" , VI_K_UP }, // cursor key Up
2186 {"[B" , VI_K_DOWN }, // cursor key Down
2187 {"[C" , VI_K_RIGHT }, // Cursor Key Right
2188 {"[D" , VI_K_LEFT }, // cursor key Left
2189 {"[H" , VI_K_HOME }, // Cursor Key Home
2190 {"[F" , VI_K_END }, // Cursor Key End
2191 {"[1~" , VI_K_HOME }, // Cursor Key Home
2192 {"[2~" , VI_K_INSERT }, // Cursor Key Insert
2193 {"[3~" , VI_K_DELETE }, // Cursor Key Delete
2194 {"[4~" , VI_K_END }, // Cursor Key End
2195 {"[5~" , VI_K_PAGEUP }, // Cursor Key Page Up
2196 {"[6~" , VI_K_PAGEDOWN}, // Cursor Key Page Down
2197 {"OP" , VI_K_FUN1 }, // Function Key F1
2198 {"OQ" , VI_K_FUN2 }, // Function Key F2
2199 {"OR" , VI_K_FUN3 }, // Function Key F3
2200 {"OS" , VI_K_FUN4 }, // Function Key F4
2201 // careful: these have no terminating NUL!
2202 {"[11~", VI_K_FUN1 }, // Function Key F1
2203 {"[12~", VI_K_FUN2 }, // Function Key F2
2204 {"[13~", VI_K_FUN3 }, // Function Key F3
2205 {"[14~", VI_K_FUN4 }, // Function Key F4
2206 {"[15~", VI_K_FUN5 }, // Function Key F5
2207 {"[17~", VI_K_FUN6 }, // Function Key F6
2208 {"[18~", VI_K_FUN7 }, // Function Key F7
2209 {"[19~", VI_K_FUN8 }, // Function Key F8
2210 {"[20~", VI_K_FUN9 }, // Function Key F9
2211 {"[21~", VI_K_FUN10 }, // Function Key F10
2212 {"[23~", VI_K_FUN11 }, // Function Key F11
2213 {"[24~", VI_K_FUN12 }, // Function Key F12
2215 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2217 alarm(0); // turn alarm OFF while we wait for input
2220 // get input from User- are there already input chars in Q?
2222 // the Q is empty, wait for a typed char
2223 n = safe_read(0, readbuffer, sizeof(readbuffer));
2225 if (errno == EBADF || errno == EFAULT || errno == EINVAL
2227 editing = 0; // want to exit
2232 if (readbuffer[0] == 27) {
2233 // This is an ESC char. Is this Esc sequence?
2234 // Could be bare Esc key. See if there are any
2235 // more chars to read after the ESC. This would
2236 // be a Function or Cursor Key sequence.
2237 struct pollfd pfd[1];
2239 pfd[0].events = POLLIN;
2240 // keep reading while there are input chars, and room in buffer
2241 // for a complete ESC sequence (assuming 8 chars is enough)
2242 while (safe_poll(pfd, 1, 0) > 0 && n <= (sizeof(readbuffer) - 8)) {
2243 // read the rest of the ESC string
2244 int r = safe_read(0, readbuffer + n, sizeof(readbuffer) - n);
2252 if (c == 27 && n > 1) {
2253 // Maybe cursor or function key?
2254 const struct esc_cmds *eindex;
2256 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2257 int cnt = strnlen(eindex->seq, 4);
2260 if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
2262 c = eindex->val; // magic char value
2263 n = cnt + 1; // squeeze out the ESC sequence
2266 // defined ESC sequence not found
2270 // remove key sequence from Q
2271 chars_to_parse -= n;
2272 memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
2273 alarm(3); // we are done waiting for input, turn alarm ON
2277 //----- IO Routines --------------------------------------------
2278 static char get_one_char(void)
2282 #if ENABLE_FEATURE_VI_DOT_CMD
2283 // ! adding2q && ioq == 0 read()
2284 // ! adding2q && ioq != 0 *ioq
2285 // adding2q *last_modifying_cmd= read()
2287 // we are not adding to the q.
2288 // but, we may be reading from a q
2290 // there is no current q, read from STDIN
2291 c = readit(); // get the users input
2293 // there is a queue to get chars from first
2296 // the end of the q, read from STDIN
2298 ioq_start = ioq = 0;
2299 c = readit(); // get the users input
2303 // adding STDIN chars to q
2304 c = readit(); // get the users input
2305 if (last_modifying_cmd != NULL) {
2306 int len = strlen(last_modifying_cmd);
2307 if (len >= MAX_INPUT_LEN - 1) {
2308 status_line_bold("last_modifying_cmd overrun");
2310 // add new char to q
2311 last_modifying_cmd[len] = c;
2316 c = readit(); // get the users input
2317 #endif /* FEATURE_VI_DOT_CMD */
2321 // Get input line (uses "status line" area)
2322 static char *get_input_line(const char *prompt)
2324 static char *buf; // [MAX_INPUT_LEN]
2329 if (!buf) buf = xmalloc(MAX_INPUT_LEN);
2331 strcpy(buf, prompt);
2332 last_status_cksum = 0; // force status update
2333 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2334 clear_to_eol(); // clear the line
2335 write1(prompt); // write out the :, /, or ? prompt
2338 while (i < MAX_INPUT_LEN) {
2340 if (c == '\n' || c == '\r' || c == 27)
2341 break; // this is end of input
2342 if (c == erase_char || c == 8 || c == 127) {
2343 // user wants to erase prev char
2345 write1("\b \b"); // erase char on screen
2346 if (i <= 0) // user backs up before b-o-l, exit
2358 static int file_size(const char *fn) // what is the byte size of "fn"
2364 if (fn && fn[0] && stat(fn, &st_buf) == 0) // see if file exists
2365 cnt = (int) st_buf.st_size;
2369 static int file_insert(const char * fn, char *p
2370 USE_FEATURE_VI_READONLY(, int update_ro_status))
2374 struct stat statbuf;
2377 if (stat(fn, &statbuf) < 0) {
2378 status_line_bold("\"%s\" %s", fn, strerror(errno));
2381 if ((statbuf.st_mode & S_IFREG) == 0) {
2382 // This is not a regular file
2383 status_line_bold("\"%s\" Not a regular file", fn);
2386 /* // this check is done by open()
2387 if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2388 // dont have any read permissions
2389 status_line_bold("\"%s\" Not readable", fn);
2393 if (p < text || p > end) {
2394 status_line_bold("Trying to insert file outside of memory");
2398 // read file to buffer
2399 fd = open(fn, O_RDONLY);
2401 status_line_bold("\"%s\" %s", fn, strerror(errno));
2404 size = statbuf.st_size;
2405 p = text_hole_make(p, size);
2408 cnt = safe_read(fd, p, size);
2410 status_line_bold("\"%s\" %s", fn, strerror(errno));
2411 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2412 } else if (cnt < size) {
2413 // There was a partial read, shrink unused space text[]
2414 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2415 status_line_bold("cannot read all of file \"%s\"", fn);
2421 #if ENABLE_FEATURE_VI_READONLY
2422 if (update_ro_status
2423 && ((access(fn, W_OK) < 0) ||
2424 /* root will always have access()
2425 * so we check fileperms too */
2426 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2429 SET_READONLY_FILE(readonly_mode);
2436 static int file_write(char * fn, char * first, char * last)
2438 int fd, cnt, charcnt;
2441 status_line_bold("No current filename");
2445 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
2448 cnt = last - first + 1;
2449 charcnt = full_write(fd, first, cnt);
2450 if (charcnt == cnt) {
2452 //file_modified = FALSE; // the file has not been modified
2460 //----- Terminal Drawing ---------------------------------------
2461 // The terminal is made up of 'rows' line of 'columns' columns.
2462 // classically this would be 24 x 80.
2463 // screen coordinates
2469 // 23,0 ... 23,79 <- status line
2471 //----- Move the cursor to row x col (count from 0, not 1) -------
2472 static void place_cursor(int row, int col, int optimize)
2474 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2477 if (row < 0) row = 0;
2478 if (row >= rows) row = rows - 1;
2479 if (col < 0) col = 0;
2480 if (col >= columns) col = columns - 1;
2482 //----- 1. Try the standard terminal ESC sequence
2483 sprintf(cm1, CMrc, row + 1, col + 1);
2486 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2487 if (optimize && col < 16) {
2489 SZ_UP = sizeof(CMup),
2490 SZ_DN = sizeof(CMdown),
2491 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2493 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2495 int Rrow = last_row;
2496 int diff = Rrow - row;
2498 if (diff < -5 || diff > 5)
2501 //----- find the minimum # of chars to move cursor -------------
2502 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2505 // move to the correct row
2506 while (row < Rrow) {
2507 // the cursor has to move up
2511 while (row > Rrow) {
2512 // the cursor has to move down
2513 strcat(cm2, CMdown);
2517 // now move to the correct column
2518 strcat(cm2, "\r"); // start at col 0
2519 // just send out orignal source char to get to correct place
2520 screenp = &screen[row * columns]; // start of screen line
2521 strncat(cm2, screenp, col);
2523 // pick the shortest cursor motion to send out
2524 if (strlen(cm2) < strlen(cm)) {
2530 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2534 //----- Erase from cursor to end of line -----------------------
2535 static void clear_to_eol(void)
2537 write1(Ceol); // Erase from cursor to end of line
2540 //----- Erase from cursor to end of screen -----------------------
2541 static void clear_to_eos(void)
2543 write1(Ceos); // Erase from cursor to end of screen
2546 //----- Start standout mode ------------------------------------
2547 static void standout_start(void) // send "start reverse video" sequence
2549 write1(SOs); // Start reverse video mode
2552 //----- End standout mode --------------------------------------
2553 static void standout_end(void) // send "end reverse video" sequence
2555 write1(SOn); // End reverse video mode
2558 //----- Flash the screen --------------------------------------
2559 static void flash(int h)
2561 standout_start(); // send "start reverse video" sequence
2564 standout_end(); // send "end reverse video" sequence
2568 static void Indicate_Error(void)
2570 #if ENABLE_FEATURE_VI_CRASHME
2572 return; // generate a random command
2575 write1(bell); // send out a bell character
2581 //----- Screen[] Routines --------------------------------------
2582 //----- Erase the Screen[] memory ------------------------------
2583 static void screen_erase(void)
2585 memset(screen, ' ', screensize); // clear new screen
2588 static int bufsum(char *buf, int count)
2591 char *e = buf + count;
2594 sum += (unsigned char) *buf++;
2598 //----- Draw the status line at bottom of the screen -------------
2599 static void show_status_line(void)
2601 int cnt = 0, cksum = 0;
2603 // either we already have an error or status message, or we
2605 if (!have_status_msg) {
2606 cnt = format_edit_status();
2607 cksum = bufsum(status_buffer, cnt);
2609 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2610 last_status_cksum = cksum; // remember if we have seen this line
2611 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2612 write1(status_buffer);
2614 if (have_status_msg) {
2615 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2617 have_status_msg = 0;
2620 have_status_msg = 0;
2622 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2627 //----- format the status buffer, the bottom line of screen ------
2628 // format status buffer, with STANDOUT mode
2629 static void status_line_bold(const char *format, ...)
2633 va_start(args, format);
2634 strcpy(status_buffer, SOs); // Terminal standout mode on
2635 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2636 strcat(status_buffer, SOn); // Terminal standout mode off
2639 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2642 // format status buffer
2643 static void status_line(const char *format, ...)
2647 va_start(args, format);
2648 vsprintf(status_buffer, format, args);
2651 have_status_msg = 1;
2654 // copy s to buf, convert unprintable
2655 static void print_literal(char *buf, const char *s)
2668 c_is_no_print = (c & 0x80) && !Isprint(c);
2669 if (c_is_no_print) {
2673 if (c < ' ' || c == 127) {
2686 if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
2691 static void not_implemented(const char *s)
2693 char buf[MAX_INPUT_LEN];
2695 print_literal(buf, s);
2696 status_line_bold("\'%s\' is not implemented", buf);
2699 // show file status on status line
2700 static int format_edit_status(void)
2703 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2704 int cur, percent, ret, trunc_at;
2706 // file_modified is now a counter rather than a flag. this
2707 // helps reduce the amount of line counting we need to do.
2708 // (this will cause a mis-reporting of modified status
2709 // once every MAXINT editing operations.)
2711 // it would be nice to do a similar optimization here -- if
2712 // we haven't done a motion that could have changed which line
2713 // we're on, then we shouldn't have to do this count_lines()
2714 cur = count_lines(text, dot);
2716 // reduce counting -- the total lines can't have
2717 // changed if we haven't done any edits.
2718 if (file_modified != last_file_modified) {
2719 tot = cur + count_lines(dot, end - 1) - 1;
2720 last_file_modified = file_modified;
2723 // current line percent
2724 // ------------- ~~ ----------
2727 percent = (100 * cur) / tot;
2733 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2734 columns : STATUS_BUFFER_LEN-1;
2736 ret = snprintf(status_buffer, trunc_at+1,
2737 #if ENABLE_FEATURE_VI_READONLY
2738 "%c %s%s%s %d/%d %d%%",
2740 "%c %s%s %d/%d %d%%",
2742 cmd_mode_indicator[cmd_mode & 3],
2743 (current_filename != NULL ? current_filename : "No file"),
2744 #if ENABLE_FEATURE_VI_READONLY
2745 (readonly_mode ? " [Readonly]" : ""),
2747 (file_modified ? " [Modified]" : ""),
2750 if (ret >= 0 && ret < trunc_at)
2751 return ret; /* it all fit */
2753 return trunc_at; /* had to truncate */
2756 //----- Force refresh of all Lines -----------------------------
2757 static void redraw(int full_screen)
2759 place_cursor(0, 0, FALSE); // put cursor in correct place
2760 clear_to_eos(); // tel terminal to erase display
2761 screen_erase(); // erase the internal screen buffer
2762 last_status_cksum = 0; // force status update
2763 refresh(full_screen); // this will redraw the entire display
2767 //----- Format a text[] line into a buffer ---------------------
2768 static char* format_line(char *src, int li)
2773 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2775 c = '~'; // char in col 0 in non-existent lines is '~'
2777 while (co < columns + tabstop) {
2778 // have we gone past the end?
2783 if ((c & 0x80) && !Isprint(c)) {
2786 if (c < ' ' || c == 0x7f) {
2790 while ((co % tabstop) != (tabstop - 1)) {
2798 c += '@'; // Ctrl-X -> 'X'
2803 // discard scrolled-off-to-the-left portion,
2804 // in tabstop-sized pieces
2805 if (ofs >= tabstop && co >= tabstop) {
2806 memmove(dest, dest + tabstop, co);
2813 // check "short line, gigantic offset" case
2816 // discard last scrolled off part
2819 // fill the rest with spaces
2821 memset(&dest[co], ' ', columns - co);
2825 //----- Refresh the changed screen lines -----------------------
2826 // Copy the source line from text[] into the buffer and note
2827 // if the current screenline is different from the new buffer.
2828 // If they differ then that line needs redrawing on the terminal.
2830 static void refresh(int full_screen)
2832 static int old_offset;
2835 char *tp, *sp; // pointer into text[] and screen[]
2837 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2838 int c = columns, r = rows;
2839 get_terminal_width_height(0, &columns, &rows);
2840 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2841 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2842 full_screen |= (c - columns) | (r - rows);
2844 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2845 tp = screenbegin; // index into text[] of top line
2847 // compare text[] to screen[] and mark screen[] lines that need updating
2848 for (li = 0; li < rows - 1; li++) {
2849 int cs, ce; // column start & end
2851 // format current text line
2852 out_buf = format_line(tp, li);
2854 // skip to the end of the current text[] line
2856 char *t = memchr(tp, '\n', end - tp);
2857 if (!t) t = end - 1;
2861 // see if there are any changes between vitual screen and out_buf
2862 changed = FALSE; // assume no change
2865 sp = &screen[li * columns]; // start of screen line
2867 // force re-draw of every single column from 0 - columns-1
2870 // compare newly formatted buffer with virtual screen
2871 // look forward for first difference between buf and screen
2872 for (; cs <= ce; cs++) {
2873 if (out_buf[cs] != sp[cs]) {
2874 changed = TRUE; // mark for redraw
2879 // look backward for last difference between out_buf and screen
2880 for (; ce >= cs; ce--) {
2881 if (out_buf[ce] != sp[ce]) {
2882 changed = TRUE; // mark for redraw
2886 // now, cs is index of first diff, and ce is index of last diff
2888 // if horz offset has changed, force a redraw
2889 if (offset != old_offset) {
2894 // make a sanity check of columns indexes
2896 if (ce > columns - 1) ce = columns - 1;
2897 if (cs > ce) { cs = 0; ce = columns - 1; }
2898 // is there a change between vitual screen and out_buf
2900 // copy changed part of buffer to virtual screen
2901 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2903 // move cursor to column of first change
2904 //if (offset != old_offset) {
2905 // // place_cursor is still too stupid
2906 // // to handle offsets correctly
2907 // place_cursor(li, cs, FALSE);
2909 place_cursor(li, cs, TRUE);
2912 // write line out to terminal
2913 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2917 place_cursor(crow, ccol, TRUE);
2919 old_offset = offset;
2922 //---------------------------------------------------------------------
2923 //----- the Ascii Chart -----------------------------------------------
2925 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2926 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2927 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2928 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2929 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2930 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2931 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2932 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2933 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2934 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2935 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2936 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2937 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2938 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2939 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2940 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2941 //---------------------------------------------------------------------
2943 //----- Execute a Vi Command -----------------------------------
2944 static void do_cmd(char c)
2946 const char *msg = msg; // for compiler
2947 char c1, *p, *q, *save_dot;
2949 int dir = dir; // for compiler
2952 // c1 = c; // quiet the compiler
2953 // cnt = yf = 0; // quiet the compiler
2954 // msg = p = q = save_dot = buf; // quiet the compiler
2955 memset(buf, '\0', 12);
2959 /* if this is a cursor key, skip these checks */
2972 if (cmd_mode == 2) {
2973 // flip-flop Insert/Replace mode
2974 if (c == VI_K_INSERT)
2976 // we are 'R'eplacing the current *dot with new char
2978 // don't Replace past E-o-l
2979 cmd_mode = 1; // convert to insert
2981 if (1 <= c || Isprint(c)) {
2983 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2984 dot = char_insert(dot, c); // insert new char
2989 if (cmd_mode == 1) {
2990 // hitting "Insert" twice means "R" replace mode
2991 if (c == VI_K_INSERT) goto dc5;
2992 // insert the char c at "dot"
2993 if (1 <= c || Isprint(c)) {
2994 dot = char_insert(dot, c);
3009 #if ENABLE_FEATURE_VI_CRASHME
3010 case 0x14: // dc4 ctrl-T
3011 crashme = (crashme == 0) ? 1 : 0;
3040 //case 'u': // u- FIXME- there is no undo
3042 default: // unrecognised command
3050 not_implemented(buf);
3051 end_cmd_q(); // stop adding to q
3052 case 0x00: // nul- ignore
3054 case 2: // ctrl-B scroll up full screen
3055 case VI_K_PAGEUP: // Cursor Key Page Up
3056 dot_scroll(rows - 2, -1);
3058 #if ENABLE_FEATURE_VI_USE_SIGNALS
3059 case 0x03: // ctrl-C interrupt
3060 longjmp(restart, 1);
3062 case 26: // ctrl-Z suspend
3063 suspend_sig(SIGTSTP);
3066 case 4: // ctrl-D scroll down half screen
3067 dot_scroll((rows - 2) / 2, 1);
3069 case 5: // ctrl-E scroll down one line
3072 case 6: // ctrl-F scroll down full screen
3073 case VI_K_PAGEDOWN: // Cursor Key Page Down
3074 dot_scroll(rows - 2, 1);
3076 case 7: // ctrl-G show current status
3077 last_status_cksum = 0; // force status update
3079 case 'h': // h- move left
3080 case VI_K_LEFT: // cursor key Left
3081 case 8: // ctrl-H- move left (This may be ERASE char)
3082 case 0x7f: // DEL- move left (This may be ERASE char)
3088 case 10: // Newline ^J
3089 case 'j': // j- goto next line, same col
3090 case VI_K_DOWN: // cursor key Down
3094 dot_next(); // go to next B-o-l
3095 dot = move_to_col(dot, ccol + offset); // try stay in same col
3097 case 12: // ctrl-L force redraw whole screen
3098 case 18: // ctrl-R force redraw
3099 place_cursor(0, 0, FALSE); // put cursor in correct place
3100 clear_to_eos(); // tel terminal to erase display
3102 screen_erase(); // erase the internal screen buffer
3103 last_status_cksum = 0; // force status update
3104 refresh(TRUE); // this will redraw the entire display
3106 case 13: // Carriage Return ^M
3107 case '+': // +- goto next line
3114 case 21: // ctrl-U scroll up half screen
3115 dot_scroll((rows - 2) / 2, -1);
3117 case 25: // ctrl-Y scroll up one line
3123 cmd_mode = 0; // stop insrting
3125 last_status_cksum = 0; // force status update
3127 case ' ': // move right
3128 case 'l': // move right
3129 case VI_K_RIGHT: // Cursor Key Right
3135 #if ENABLE_FEATURE_VI_YANKMARK
3136 case '"': // "- name a register to use for Delete/Yank
3137 c1 = get_one_char();
3145 case '\'': // '- goto a specific mark
3146 c1 = get_one_char();
3151 q = mark[(unsigned char) c1];
3152 if (text <= q && q < end) {
3154 dot_begin(); // go to B-o-l
3157 } else if (c1 == '\'') { // goto previous context
3158 dot = swap_context(dot); // swap current and previous context
3159 dot_begin(); // go to B-o-l
3165 case 'm': // m- Mark a line
3166 // this is really stupid. If there are any inserts or deletes
3167 // between text[0] and dot then this mark will not point to the
3168 // correct location! It could be off by many lines!
3169 // Well..., at least its quick and dirty.
3170 c1 = get_one_char();
3174 // remember the line
3175 mark[(int) c1] = dot;
3180 case 'P': // P- Put register before
3181 case 'p': // p- put register after
3184 status_line_bold("Nothing in register %c", what_reg());
3187 // are we putting whole lines or strings
3188 if (strchr(p, '\n') != NULL) {
3190 dot_begin(); // putting lines- Put above
3193 // are we putting after very last line?
3194 if (end_line(dot) == (end - 1)) {
3195 dot = end; // force dot to end of text[]
3197 dot_next(); // next line, then put before
3202 dot_right(); // move to right, can move to NL
3204 dot = string_insert(dot, p); // insert the string
3205 end_cmd_q(); // stop adding to q
3207 case 'U': // U- Undo; replace current line with original version
3208 if (reg[Ureg] != 0) {
3209 p = begin_line(dot);
3211 p = text_hole_delete(p, q); // delete cur line
3212 p = string_insert(p, reg[Ureg]); // insert orig line
3217 #endif /* FEATURE_VI_YANKMARK */
3218 case '$': // $- goto end of line
3219 case VI_K_END: // Cursor Key End
3223 dot = end_line(dot);
3225 case '%': // %- find matching char of pair () [] {}
3226 for (q = dot; q < end && *q != '\n'; q++) {
3227 if (strchr("()[]{}", *q) != NULL) {
3228 // we found half of a pair
3229 p = find_pair(q, *q);
3241 case 'f': // f- forward to a user specified char
3242 last_forward_char = get_one_char(); // get the search char
3244 // dont separate these two commands. 'f' depends on ';'
3246 //**** fall through to ... ';'
3247 case ';': // ;- look at rest of line for last forward char
3251 if (last_forward_char == 0)
3254 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3257 if (*q == last_forward_char)
3260 case ',': // repeat latest 'f' in opposite direction
3264 if (last_forward_char == 0)
3267 while (q >= text && *q != '\n' && *q != last_forward_char) {
3270 if (q >= text && *q == last_forward_char)
3274 case '-': // -- goto prev line
3281 #if ENABLE_FEATURE_VI_DOT_CMD
3282 case '.': // .- repeat the last modifying command
3283 // Stuff the last_modifying_cmd back into stdin
3284 // and let it be re-executed.
3285 if (last_modifying_cmd != NULL) {
3286 ioq = ioq_start = xstrdup(last_modifying_cmd);
3290 #if ENABLE_FEATURE_VI_SEARCH
3291 case '?': // /- search for a pattern
3292 case '/': // /- search for a pattern
3295 q = get_input_line(buf); // get input line- use "status line"
3297 goto dc3; // if no pat re-use old pat
3298 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3299 // there is a new pat
3300 free(last_search_pattern);
3301 last_search_pattern = xstrdup(q);
3302 goto dc3; // now find the pattern
3304 // user changed mind and erased the "/"- do nothing
3306 case 'N': // N- backward search for last pattern
3310 dir = BACK; // assume BACKWARD search
3312 if (last_search_pattern[0] == '?') {
3316 goto dc4; // now search for pattern
3318 case 'n': // n- repeat search for last pattern
3319 // search rest of text[] starting at next char
3320 // if search fails return orignal "p" not the "p+1" address
3325 if (last_search_pattern == 0) {
3326 msg = "No previous regular expression";
3329 if (last_search_pattern[0] == '/') {
3330 dir = FORWARD; // assume FORWARD search
3333 if (last_search_pattern[0] == '?') {
3338 q = char_search(p, last_search_pattern + 1, dir, FULL);
3340 dot = q; // good search, update "dot"
3344 // no pattern found between "dot" and "end"- continue at top
3349 q = char_search(p, last_search_pattern + 1, dir, FULL);
3350 if (q != NULL) { // found something
3351 dot = q; // found new pattern- goto it
3352 msg = "search hit BOTTOM, continuing at TOP";
3354 msg = "search hit TOP, continuing at BOTTOM";
3357 msg = "Pattern not found";
3361 status_line_bold("%s", msg);
3363 case '{': // {- move backward paragraph
3364 q = char_search(dot, "\n\n", BACK, FULL);
3365 if (q != NULL) { // found blank line
3366 dot = next_line(q); // move to next blank line
3369 case '}': // }- move forward paragraph
3370 q = char_search(dot, "\n\n", FORWARD, FULL);
3371 if (q != NULL) { // found blank line
3372 dot = next_line(q); // move to next blank line
3375 #endif /* FEATURE_VI_SEARCH */
3376 case '0': // 0- goto begining of line
3386 if (c == '0' && cmdcnt < 1) {
3387 dot_begin(); // this was a standalone zero
3389 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3392 case ':': // :- the colon mode commands
3393 p = get_input_line(":"); // get input line- use "status line"
3394 #if ENABLE_FEATURE_VI_COLON
3395 colon(p); // execute the command
3398 p++; // move past the ':'
3402 if (strncasecmp(p, "quit", cnt) == 0
3403 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3405 if (file_modified && p[1] != '!') {
3406 status_line_bold("No write since last change (:quit! overrides)");
3410 } else if (strncasecmp(p, "write", cnt) == 0
3411 || strncasecmp(p, "wq", cnt) == 0
3412 || strncasecmp(p, "wn", cnt) == 0
3413 || strncasecmp(p, "x", cnt) == 0
3415 cnt = file_write(current_filename, text, end - 1);
3418 status_line_bold("Write error: %s", strerror(errno));
3421 last_file_modified = -1;
3422 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3423 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3424 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3429 } else if (strncasecmp(p, "file", cnt) == 0) {
3430 last_status_cksum = 0; // force status update
3431 } else if (sscanf(p, "%d", &j) > 0) {
3432 dot = find_line(j); // go to line # j
3434 } else { // unrecognised cmd
3437 #endif /* !FEATURE_VI_COLON */
3439 case '<': // <- Left shift something
3440 case '>': // >- Right shift something
3441 cnt = count_lines(text, dot); // remember what line we are on
3442 c1 = get_one_char(); // get the type of thing to delete
3443 find_range(&p, &q, c1);
3444 yank_delete(p, q, 1, YANKONLY); // save copy before change
3447 i = count_lines(p, q); // # of lines we are shifting
3448 for ( ; i > 0; i--, p = next_line(p)) {
3450 // shift left- remove tab or 8 spaces
3452 // shrink buffer 1 char
3453 text_hole_delete(p, p);
3454 } else if (*p == ' ') {
3455 // we should be calculating columns, not just SPACE
3456 for (j = 0; *p == ' ' && j < tabstop; j++) {
3457 text_hole_delete(p, p);
3460 } else if (c == '>') {
3461 // shift right -- add tab or 8 spaces
3462 char_insert(p, '\t');
3465 dot = find_line(cnt); // what line were we on
3467 end_cmd_q(); // stop adding to q
3469 case 'A': // A- append at e-o-l
3470 dot_end(); // go to e-o-l
3471 //**** fall through to ... 'a'
3472 case 'a': // a- append after current char
3477 case 'B': // B- back a blank-delimited Word
3478 case 'E': // E- end of a blank-delimited word
3479 case 'W': // W- forward a blank-delimited word
3486 if (c == 'W' || isspace(dot[dir])) {
3487 dot = skip_thing(dot, 1, dir, S_TO_WS);
3488 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3491 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3493 case 'C': // C- Change to e-o-l
3494 case 'D': // D- delete to e-o-l
3496 dot = dollar_line(dot); // move to before NL
3497 // copy text into a register and delete
3498 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3500 goto dc_i; // start inserting
3501 #if ENABLE_FEATURE_VI_DOT_CMD
3503 end_cmd_q(); // stop adding to q
3506 case 'g': // 'gg' goto a line number (from vim)
3507 // (default to first line in file)
3508 c1 = get_one_char();
3513 not_implemented(buf);
3519 case 'G': // G- goto to a line number (default= E-O-F)
3520 dot = end - 1; // assume E-O-F
3522 dot = find_line(cmdcnt); // what line is #cmdcnt
3526 case 'H': // H- goto top line on screen
3528 if (cmdcnt > (rows - 1)) {
3529 cmdcnt = (rows - 1);
3536 case 'I': // I- insert before first non-blank
3539 //**** fall through to ... 'i'
3540 case 'i': // i- insert before current char
3541 case VI_K_INSERT: // Cursor Key Insert
3543 cmd_mode = 1; // start insrting
3545 case 'J': // J- join current and next lines together
3549 dot_end(); // move to NL
3550 if (dot < end - 1) { // make sure not last char in text[]
3551 *dot++ = ' '; // replace NL with space
3553 while (isblank(*dot)) { // delete leading WS
3557 end_cmd_q(); // stop adding to q
3559 case 'L': // L- goto bottom line on screen
3561 if (cmdcnt > (rows - 1)) {
3562 cmdcnt = (rows - 1);
3570 case 'M': // M- goto middle line on screen
3572 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3573 dot = next_line(dot);
3575 case 'O': // O- open a empty line above
3577 p = begin_line(dot);
3578 if (p[-1] == '\n') {
3580 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3582 dot = char_insert(dot, '\n');
3585 dot = char_insert(dot, '\n'); // i\n ESC
3590 case 'R': // R- continuous Replace char
3597 case 'X': // X- delete char before dot
3598 case 'x': // x- delete the current char
3599 case 's': // s- substitute the current char
3606 if (dot[dir] != '\n') {
3608 dot--; // delete prev char
3609 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3612 goto dc_i; // start insrting
3613 end_cmd_q(); // stop adding to q
3615 case 'Z': // Z- if modified, {write}; exit
3616 // ZZ means to save file (if necessary), then exit
3617 c1 = get_one_char();
3622 if (file_modified) {
3623 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3624 status_line_bold("\"%s\" File is read only", current_filename);
3627 cnt = file_write(current_filename, text, end - 1);
3630 status_line_bold("Write error: %s", strerror(errno));
3631 } else if (cnt == (end - 1 - text + 1)) {
3638 case '^': // ^- move to first non-blank on line
3642 case 'b': // b- back a word
3643 case 'e': // e- end of word
3650 if ((dot + dir) < text || (dot + dir) > end - 1)
3653 if (isspace(*dot)) {
3654 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3656 if (isalnum(*dot) || *dot == '_') {
3657 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3658 } else if (ispunct(*dot)) {
3659 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3662 case 'c': // c- change something
3663 case 'd': // d- delete something
3664 #if ENABLE_FEATURE_VI_YANKMARK
3665 case 'y': // y- yank something
3666 case 'Y': // Y- Yank a line
3668 yf = YANKDEL; // assume either "c" or "d"
3669 #if ENABLE_FEATURE_VI_YANKMARK
3670 if (c == 'y' || c == 'Y')
3675 c1 = get_one_char(); // get the type of thing to delete
3676 find_range(&p, &q, c1);
3677 if (c1 == 27) { // ESC- user changed mind and wants out
3678 c = c1 = 27; // Escape- do nothing
3679 } else if (strchr("wW", c1)) {
3681 // don't include trailing WS as part of word
3682 while (isblank(*q)) {
3683 if (q <= text || q[-1] == '\n')
3688 dot = yank_delete(p, q, 0, yf); // delete word
3689 } else if (strchr("^0bBeEft$", c1)) {
3690 // single line copy text into a register and delete
3691 dot = yank_delete(p, q, 0, yf); // delete word
3692 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3693 // multiple line copy text into a register and delete
3694 dot = yank_delete(p, q, 1, yf); // delete lines
3696 dot = char_insert(dot, '\n');
3697 // on the last line of file don't move to prev line
3698 if (dot != (end-1)) {
3701 } else if (c == 'd') {
3706 // could not recognize object
3707 c = c1 = 27; // error-
3711 // if CHANGING, not deleting, start inserting after the delete
3713 strcpy(buf, "Change");
3714 goto dc_i; // start inserting
3717 strcpy(buf, "Delete");
3719 #if ENABLE_FEATURE_VI_YANKMARK
3720 if (c == 'y' || c == 'Y') {
3721 strcpy(buf, "Yank");
3725 for (cnt = 0; p <= q; p++) {
3729 status_line("%s %d lines (%d chars) using [%c]",
3730 buf, cnt, strlen(reg[YDreg]), what_reg());
3732 end_cmd_q(); // stop adding to q
3735 case 'k': // k- goto prev line, same col
3736 case VI_K_UP: // cursor key Up
3741 dot = move_to_col(dot, ccol + offset); // try stay in same col
3743 case 'r': // r- replace the current char with user input
3744 c1 = get_one_char(); // get the replacement char
3747 file_modified++; // has the file been modified
3749 end_cmd_q(); // stop adding to q
3751 case 't': // t- move to char prior to next x
3752 last_forward_char = get_one_char();
3754 if (*dot == last_forward_char)
3756 last_forward_char= 0;
3758 case 'w': // w- forward a word
3762 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3763 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3764 } else if (ispunct(*dot)) { // we are on PUNCT
3765 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3768 dot++; // move over word
3769 if (isspace(*dot)) {
3770 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3774 c1 = get_one_char(); // get the replacement char
3777 cnt = (rows - 2) / 2; // put dot at center
3779 cnt = rows - 2; // put dot at bottom
3780 screenbegin = begin_line(dot); // start dot at top
3781 dot_scroll(cnt, -1);
3783 case '|': // |- move to column "cmdcnt"
3784 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3786 case '~': // ~- flip the case of letters a-z -> A-Z
3790 if (islower(*dot)) {
3791 *dot = toupper(*dot);
3792 file_modified++; // has the file been modified
3793 } else if (isupper(*dot)) {
3794 *dot = tolower(*dot);
3795 file_modified++; // has the file been modified
3798 end_cmd_q(); // stop adding to q
3800 //----- The Cursor and Function Keys -----------------------------
3801 case VI_K_HOME: // Cursor Key Home
3804 // The Fn keys could point to do_macro which could translate them
3805 case VI_K_FUN1: // Function Key F1
3806 case VI_K_FUN2: // Function Key F2
3807 case VI_K_FUN3: // Function Key F3
3808 case VI_K_FUN4: // Function Key F4
3809 case VI_K_FUN5: // Function Key F5
3810 case VI_K_FUN6: // Function Key F6
3811 case VI_K_FUN7: // Function Key F7
3812 case VI_K_FUN8: // Function Key F8
3813 case VI_K_FUN9: // Function Key F9
3814 case VI_K_FUN10: // Function Key F10
3815 case VI_K_FUN11: // Function Key F11
3816 case VI_K_FUN12: // Function Key F12
3821 // if text[] just became empty, add back an empty line
3823 char_insert(text, '\n'); // start empty buf with dummy line
3826 // it is OK for dot to exactly equal to end, otherwise check dot validity
3828 dot = bound_dot(dot); // make sure "dot" is valid
3830 #if ENABLE_FEATURE_VI_YANKMARK
3831 check_context(c); // update the current context
3835 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3836 cnt = dot - begin_line(dot);
3837 // Try to stay off of the Newline
3838 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3842 #if ENABLE_FEATURE_VI_CRASHME
3843 static int totalcmds = 0;
3844 static int Mp = 85; // Movement command Probability
3845 static int Np = 90; // Non-movement command Probability
3846 static int Dp = 96; // Delete command Probability
3847 static int Ip = 97; // Insert command Probability
3848 static int Yp = 98; // Yank command Probability
3849 static int Pp = 99; // Put command Probability
3850 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3851 static const char chars[20] = "\t012345 abcdABCD-=.$";
3852 static const char *const words[20] = {
3853 "this", "is", "a", "test",
3854 "broadcast", "the", "emergency", "of",
3855 "system", "quick", "brown", "fox",
3856 "jumped", "over", "lazy", "dogs",
3857 "back", "January", "Febuary", "March"
3859 static const char *const lines[20] = {
3860 "You should have received a copy of the GNU General Public License\n",
3861 "char c, cm, *cmd, *cmd1;\n",
3862 "generate a command by percentages\n",
3863 "Numbers may be typed as a prefix to some commands.\n",
3864 "Quit, discarding changes!\n",
3865 "Forced write, if permission originally not valid.\n",
3866 "In general, any ex or ed command (such as substitute or delete).\n",
3867 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3868 "Please get w/ me and I will go over it with you.\n",
3869 "The following is a list of scheduled, committed changes.\n",
3870 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3871 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3872 "Any question about transactions please contact Sterling Huxley.\n",
3873 "I will try to get back to you by Friday, December 31.\n",
3874 "This Change will be implemented on Friday.\n",
3875 "Let me know if you have problems accessing this;\n",
3876 "Sterling Huxley recently added you to the access list.\n",
3877 "Would you like to go to lunch?\n",
3878 "The last command will be automatically run.\n",
3879 "This is too much english for a computer geek.\n",
3881 static char *multilines[20] = {
3882 "You should have received a copy of the GNU General Public License\n",
3883 "char c, cm, *cmd, *cmd1;\n",
3884 "generate a command by percentages\n",
3885 "Numbers may be typed as a prefix to some commands.\n",
3886 "Quit, discarding changes!\n",
3887 "Forced write, if permission originally not valid.\n",
3888 "In general, any ex or ed command (such as substitute or delete).\n",
3889 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3890 "Please get w/ me and I will go over it with you.\n",
3891 "The following is a list of scheduled, committed changes.\n",
3892 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3893 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3894 "Any question about transactions please contact Sterling Huxley.\n",
3895 "I will try to get back to you by Friday, December 31.\n",
3896 "This Change will be implemented on Friday.\n",
3897 "Let me know if you have problems accessing this;\n",
3898 "Sterling Huxley recently added you to the access list.\n",
3899 "Would you like to go to lunch?\n",
3900 "The last command will be automatically run.\n",
3901 "This is too much english for a computer geek.\n",
3904 // create a random command to execute
3905 static void crash_dummy()
3907 static int sleeptime; // how long to pause between commands
3908 char c, cm, *cmd, *cmd1;
3909 int i, cnt, thing, rbi, startrbi, percent;
3911 // "dot" movement commands
3912 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3914 // is there already a command running?
3915 if (chars_to_parse > 0)
3919 sleeptime = 0; // how long to pause between commands
3920 memset(readbuffer, '\0', sizeof(readbuffer));
3921 // generate a command by percentages
3922 percent = (int) lrand48() % 100; // get a number from 0-99
3923 if (percent < Mp) { // Movement commands
3924 // available commands
3927 } else if (percent < Np) { // non-movement commands
3928 cmd = "mz<>\'\""; // available commands
3930 } else if (percent < Dp) { // Delete commands
3931 cmd = "dx"; // available commands
3933 } else if (percent < Ip) { // Inset commands
3934 cmd = "iIaAsrJ"; // available commands
3936 } else if (percent < Yp) { // Yank commands
3937 cmd = "yY"; // available commands
3939 } else if (percent < Pp) { // Put commands
3940 cmd = "pP"; // available commands
3943 // We do not know how to handle this command, try again
3947 // randomly pick one of the available cmds from "cmd[]"
3948 i = (int) lrand48() % strlen(cmd);
3950 if (strchr(":\024", cm))
3951 goto cd0; // dont allow colon or ctrl-T commands
3952 readbuffer[rbi++] = cm; // put cmd into input buffer
3954 // now we have the command-
3955 // there are 1, 2, and multi char commands
3956 // find out which and generate the rest of command as necessary
3957 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3958 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3959 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3960 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3962 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3964 readbuffer[rbi++] = c; // add movement to input buffer
3966 if (strchr("iIaAsc", cm)) { // multi-char commands
3968 // change some thing
3969 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3971 readbuffer[rbi++] = c; // add movement to input buffer
3973 thing = (int) lrand48() % 4; // what thing to insert
3974 cnt = (int) lrand48() % 10; // how many to insert
3975 for (i = 0; i < cnt; i++) {
3976 if (thing == 0) { // insert chars
3977 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3978 } else if (thing == 1) { // insert words
3979 strcat(readbuffer, words[(int) lrand48() % 20]);
3980 strcat(readbuffer, " ");
3981 sleeptime = 0; // how fast to type
3982 } else if (thing == 2) { // insert lines
3983 strcat(readbuffer, lines[(int) lrand48() % 20]);
3984 sleeptime = 0; // how fast to type
3985 } else { // insert multi-lines
3986 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3987 sleeptime = 0; // how fast to type
3990 strcat(readbuffer, "\033");
3992 chars_to_parse = strlen(readbuffer);
3996 mysleep(sleeptime); // sleep 1/100 sec
3999 // test to see if there are any errors
4000 static void crash_test()
4002 static time_t oldtim;
4009 strcat(msg, "end<text ");
4011 if (end > textend) {
4012 strcat(msg, "end>textend ");
4015 strcat(msg, "dot<text ");
4018 strcat(msg, "dot>end ");
4020 if (screenbegin < text) {
4021 strcat(msg, "screenbegin<text ");
4023 if (screenbegin > end - 1) {
4024 strcat(msg, "screenbegin>end-1 ");
4029 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4030 totalcmds, last_input_char, msg, SOs, SOn);
4032 while (safe_read(0, d, 1) > 0) {
4033 if (d[0] == '\n' || d[0] == '\r')
4039 if (tim >= (oldtim + 3)) {
4040 sprintf(status_buffer,
4041 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4042 totalcmds, M, N, I, D, Y, P, U, end - text + 1);