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 /* the CRASHME code is unmaintained, and doesn't currently build */
27 #define ENABLE_FEATURE_VI_CRASHME 0
30 #if ENABLE_LOCALE_SUPPORT
32 #if ENABLE_FEATURE_VI_8BIT
33 #define Isprint(c) isprint(c)
35 #define Isprint(c) (isprint(c) && (unsigned char)(c) < 0x7f)
40 /* 0x9b is Meta-ESC */
41 #if ENABLE_FEATURE_VI_8BIT
42 #define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
44 #define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
51 MAX_TABSTOP = 32, // sanity limit
52 // User input len. Need not be extra big.
53 // Lines in file being edited *can* be bigger than this.
55 // Sanity limits. We have only one buffer of this size.
56 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
57 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
60 // Misc. non-Ascii keys that report an escape sequence
61 #define VI_K_UP (char)128 // cursor key Up
62 #define VI_K_DOWN (char)129 // cursor key Down
63 #define VI_K_RIGHT (char)130 // Cursor Key Right
64 #define VI_K_LEFT (char)131 // cursor key Left
65 #define VI_K_HOME (char)132 // Cursor Key Home
66 #define VI_K_END (char)133 // Cursor Key End
67 #define VI_K_INSERT (char)134 // Cursor Key Insert
68 #define VI_K_DELETE (char)135 // Cursor Key Insert
69 #define VI_K_PAGEUP (char)136 // Cursor Key Page Up
70 #define VI_K_PAGEDOWN (char)137 // Cursor Key Page Down
71 #define VI_K_FUN1 (char)138 // Function Key F1
72 #define VI_K_FUN2 (char)139 // Function Key F2
73 #define VI_K_FUN3 (char)140 // Function Key F3
74 #define VI_K_FUN4 (char)141 // Function Key F4
75 #define VI_K_FUN5 (char)142 // Function Key F5
76 #define VI_K_FUN6 (char)143 // Function Key F6
77 #define VI_K_FUN7 (char)144 // Function Key F7
78 #define VI_K_FUN8 (char)145 // Function Key F8
79 #define VI_K_FUN9 (char)146 // Function Key F9
80 #define VI_K_FUN10 (char)147 // Function Key F10
81 #define VI_K_FUN11 (char)148 // Function Key F11
82 #define VI_K_FUN12 (char)149 // Function Key F12
84 /* vt102 typical ESC sequence */
85 /* terminal standout start/normal ESC sequence */
86 static const char SOs[] ALIGN1 = "\033[7m";
87 static const char SOn[] ALIGN1 = "\033[0m";
88 /* terminal bell sequence */
89 static const char bell[] ALIGN1 = "\007";
90 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
91 static const char Ceol[] ALIGN1 = "\033[0K";
92 static const char Ceos[] ALIGN1 = "\033[0J";
93 /* Cursor motion arbitrary destination ESC sequence */
94 static const char CMrc[] ALIGN1 = "\033[%d;%dH";
95 /* Cursor motion up and down ESC sequence */
96 static const char CMup[] ALIGN1 = "\033[A";
97 static const char CMdown[] ALIGN1 = "\n";
103 FORWARD = 1, // code depends on "1" for array index
104 BACK = -1, // code depends on "-1" for array index
105 LIMITED = 0, // how much of text[] in char_search
106 FULL = 1, // how much of text[] in char_search
108 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
109 S_TO_WS = 2, // used in skip_thing() for moving "dot"
110 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
111 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
112 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
115 /* vi.c expects chars to be unsigned. */
116 /* busybox build system provides that, but it's better */
117 /* to audit and fix the source */
119 static smallint vi_setops;
120 #define VI_AUTOINDENT 1
121 #define VI_SHOWMATCH 2
122 #define VI_IGNORECASE 4
123 #define VI_ERR_METHOD 8
124 #define autoindent (vi_setops & VI_AUTOINDENT)
125 #define showmatch (vi_setops & VI_SHOWMATCH )
126 #define ignorecase (vi_setops & VI_IGNORECASE)
127 /* indicate error with beep or flash */
128 #define err_method (vi_setops & VI_ERR_METHOD)
131 static smallint editing; // >0 while we are editing a file
132 // [code audit says "can be 0 or 1 only"]
133 static smallint cmd_mode; // 0=command 1=insert 2=replace
134 static smallint file_modified; // buffer contents changed
135 static smallint last_file_modified = -1;
136 static int fn_start; // index of first cmd line file name
137 static int save_argc; // how many file names on cmd line
138 static int cmdcnt; // repetition count
139 static unsigned rows, columns; // the terminal screen is this size
140 static int crow, ccol; // cursor is on Crow x Ccol
141 static int offset; // chars scrolled off the screen to the left
142 static char *status_buffer; // mesages to the user
143 #define STATUS_BUFFER_LEN 200
144 static int have_status_msg; // is default edit status needed?
145 // [don't make smallint!]
146 static int last_status_cksum; // hash of current status line
147 static char *current_filename; // current file name
148 //static char *text, *end; // pointers to the user data in memory
149 static char *screen; // pointer to the virtual screen buffer
150 static int screensize; // and its size
151 static char *screenbegin; // index into text[], of top line on the screen
152 //static char *dot; // where all the action takes place
154 static char erase_char; // the users erase character
155 static char last_input_char; // last char read from user
156 static char last_forward_char; // last char searched for with 'f'
158 #if ENABLE_FEATURE_VI_READONLY
159 //static smallint vi_readonly, readonly;
160 static smallint readonly_mode = 0;
161 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
162 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
163 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
165 #define readonly_mode 0
166 #define SET_READONLY_FILE(flags)
167 #define SET_READONLY_MODE(flags)
168 #define UNSET_READONLY_FILE(flags)
171 #if ENABLE_FEATURE_VI_DOT_CMD
172 static smallint adding2q; // are we currently adding user input to q
173 static char *last_modifying_cmd; // [MAX_INPUT_LEN] last modifying cmd for "."
174 static smallint lmc_len; // length of last_modifying_cmd
175 static char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
177 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
178 static int last_row; // where the cursor was last moved to
180 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
183 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
184 static char *modifying_cmds; // cmds that modify text[]
186 #if ENABLE_FEATURE_VI_SEARCH
187 static char *last_search_pattern; // last pattern from a '/' or '?' search
190 /* Moving biggest data to malloced space... */
192 /* many references - keep near the top of globals */
193 char *text, *end; // pointers to the user data in memory
194 char *dot; // where all the action takes place
195 int text_size; // size of the allocated buffer
196 #if ENABLE_FEATURE_VI_YANKMARK
197 int YDreg, Ureg; // default delete register and orig line for "U"
198 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
199 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
200 char *context_start, *context_end;
202 /* a few references only */
203 #if ENABLE_FEATURE_VI_USE_SIGNALS
204 sigjmp_buf restart; // catch_sig()
206 struct termios term_orig, term_vi; // remember what the cooked mode was
207 #if ENABLE_FEATURE_VI_COLON
208 char *initial_cmds[3]; // currently 2 entries, NULL terminated
210 // Should be just enough to hold a key sequence,
211 // but CRASME mode uses it as generated command buffer too
212 #if ENABLE_FEATURE_VI_CRASHME
213 char readbuffer[128];
218 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
220 #define G (*ptr_to_globals)
221 #define text (G.text )
222 #define text_size (G.text_size )
226 #define YDreg (G.YDreg )
227 #define Ureg (G.Ureg )
228 #define mark (G.mark )
229 #define context_start (G.context_start )
230 #define context_end (G.context_end )
231 #define restart (G.restart )
232 #define term_orig (G.term_orig )
233 #define term_vi (G.term_vi )
234 #define initial_cmds (G.initial_cmds )
235 #define readbuffer (G.readbuffer )
236 #define scr_out_buf (G.scr_out_buf )
237 #define INIT_G() do { \
238 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
241 static int init_text_buffer(char *); // init from file or create new
242 static void edit_file(char *); // edit one file
243 static void do_cmd(char); // execute a command
244 static int next_tabstop(int);
245 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
246 static char *begin_line(char *); // return pointer to cur line B-o-l
247 static char *end_line(char *); // return pointer to cur line E-o-l
248 static char *prev_line(char *); // return pointer to prev line B-o-l
249 static char *next_line(char *); // return pointer to next line B-o-l
250 static char *end_screen(void); // get pointer to last char on screen
251 static int count_lines(char *, char *); // count line from start to stop
252 static char *find_line(int); // find begining of line #li
253 static char *move_to_col(char *, int); // move "p" to column l
254 static void dot_left(void); // move dot left- dont leave line
255 static void dot_right(void); // move dot right- dont leave line
256 static void dot_begin(void); // move dot to B-o-l
257 static void dot_end(void); // move dot to E-o-l
258 static void dot_next(void); // move dot to next line B-o-l
259 static void dot_prev(void); // move dot to prev line B-o-l
260 static void dot_scroll(int, int); // move the screen up or down
261 static void dot_skip_over_ws(void); // move dot pat WS
262 static void dot_delete(void); // delete the char at 'dot'
263 static char *bound_dot(char *); // make sure text[0] <= P < "end"
264 static char *new_screen(int, int); // malloc virtual screen memory
265 static char *char_insert(char *, char); // insert the char c at 'p'
266 static char *stupid_insert(char *, char); // stupidly insert the char c at 'p'
267 static int find_range(char **, char **, char); // return pointers for an object
268 static int st_test(char *, int, int, char *); // helper for skip_thing()
269 static char *skip_thing(char *, int, int, int); // skip some object
270 static char *find_pair(char *, char); // find matching pair () [] {}
271 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
272 static char *text_hole_make(char *, int); // at "p", make a 'size' byte hole
273 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
274 static void show_help(void); // display some help info
275 static void rawmode(void); // set "raw" mode on tty
276 static void cookmode(void); // return to "cooked" mode on tty
277 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
278 static int mysleep(int);
279 static char readit(void); // read (maybe cursor) key from stdin
280 static char get_one_char(void); // read 1 char from stdin
281 static int file_size(const char *); // what is the byte size of "fn"
282 #if ENABLE_FEATURE_VI_READONLY
283 static int file_insert(const char *, char *, int);
285 static int file_insert(const char *, char *);
287 static int file_write(char *, char *, char *);
288 #if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
289 #define place_cursor(a, b, optimize) place_cursor(a, b)
291 static void place_cursor(int, int, int);
292 static void screen_erase(void);
293 static void clear_to_eol(void);
294 static void clear_to_eos(void);
295 static void standout_start(void); // send "start reverse video" sequence
296 static void standout_end(void); // send "end reverse video" sequence
297 static void flash(int); // flash the terminal screen
298 static void show_status_line(void); // put a message on the bottom line
299 static void status_line(const char *, ...); // print to status buf
300 static void status_line_bold(const char *, ...);
301 static void not_implemented(const char *); // display "Not implemented" message
302 static int format_edit_status(void); // format file status on status line
303 static void redraw(int); // force a full screen refresh
304 static char* format_line(char* /*, int*/);
305 static void refresh(int); // update the terminal from screen[]
307 static void Indicate_Error(void); // use flash or beep to indicate error
308 #define indicate_error(c) Indicate_Error()
309 static void Hit_Return(void);
311 #if ENABLE_FEATURE_VI_SEARCH
312 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
313 static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase"
315 #if ENABLE_FEATURE_VI_COLON
316 static char *get_one_address(char *, int *); // get colon addr, if present
317 static char *get_address(char *, int *, int *); // get two colon addrs, if present
318 static void colon(char *); // execute the "colon" mode cmds
320 #if ENABLE_FEATURE_VI_USE_SIGNALS
321 static void winch_sig(int); // catch window size changes
322 static void suspend_sig(int); // catch ctrl-Z
323 static void catch_sig(int); // catch ctrl-C and alarm time-outs
325 #if ENABLE_FEATURE_VI_DOT_CMD
326 static void start_new_cmd_q(char); // new queue for command
327 static void end_cmd_q(void); // stop saving input chars
329 #define end_cmd_q() ((void)0)
331 #if ENABLE_FEATURE_VI_SETOPTS
332 static void showmatching(char *); // show the matching pair () [] {}
334 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
335 static char *string_insert(char *, char *); // insert the string at 'p'
337 #if ENABLE_FEATURE_VI_YANKMARK
338 static char *text_yank(char *, char *, int); // save copy of "p" into a register
339 static char what_reg(void); // what is letter of current YDreg
340 static void check_context(char); // remember context for '' command
342 #if ENABLE_FEATURE_VI_CRASHME
343 static void crash_dummy();
344 static void crash_test();
345 static int crashme = 0;
349 static void write1(const char *out)
354 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
355 int vi_main(int argc, char **argv)
358 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
360 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
366 #if ENABLE_FEATURE_VI_CRASHME
367 srand((long) my_pid);
370 status_buffer = STATUS_BUFFER;
371 last_status_cksum = 0;
374 #ifdef NO_SUCH_APPLET_YET
375 /* If we aren't "vi", we are "view" */
376 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
377 SET_READONLY_MODE(readonly_mode);
381 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
382 #if ENABLE_FEATURE_VI_YANKMARK
383 memset(reg, 0, sizeof(reg)); // init the yank regs
385 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
386 modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
389 // 1- process $HOME/.exrc file (not inplemented yet)
390 // 2- process EXINIT variable from environment
391 // 3- process command line args
392 #if ENABLE_FEATURE_VI_COLON
394 char *p = getenv("EXINIT");
396 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
399 while ((c = getopt(argc, argv, "hCRH" USE_FEATURE_VI_COLON("c:"))) != -1) {
401 #if ENABLE_FEATURE_VI_CRASHME
406 #if ENABLE_FEATURE_VI_READONLY
407 case 'R': // Read-only flag
408 SET_READONLY_MODE(readonly_mode);
411 #if ENABLE_FEATURE_VI_COLON
412 case 'c': // cmd line vi command
414 initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
427 // The argv array can be used by the ":next" and ":rewind" commands
429 fn_start = optind; // remember first file name for :next and :rew
432 //----- This is the main file handling loop --------------
433 if (optind >= argc) {
436 for (; optind < argc; optind++) {
437 edit_file(argv[optind]);
440 //-----------------------------------------------------------
445 /* read text from file or create an empty buf */
446 /* will also update current_filename */
447 static int init_text_buffer(char *fn)
450 int size = file_size(fn); // file size. -1 means does not exist.
452 /* allocate/reallocate text buffer */
454 text_size = size * 2;
455 if (text_size < 10240)
456 text_size = 10240; // have a minimum size for new files
457 screenbegin = dot = end = text = xzalloc(text_size);
459 if (fn != current_filename) {
460 free(current_filename);
461 current_filename = xstrdup(fn);
464 // file dont exist. Start empty buf with dummy line
465 char_insert(text, '\n');
468 rc = file_insert(fn, text
469 USE_FEATURE_VI_READONLY(, 1));
472 last_file_modified = -1;
473 #if ENABLE_FEATURE_VI_YANKMARK
474 /* init the marks. */
475 memset(mark, 0, sizeof(mark));
480 static void edit_file(char *fn)
485 #if ENABLE_FEATURE_VI_USE_SIGNALS
488 #if ENABLE_FEATURE_VI_YANKMARK
489 static char *cur_line;
492 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
497 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
498 get_terminal_width_height(0, &columns, &rows);
499 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
500 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
502 new_screen(rows, columns); // get memory for virtual screen
503 init_text_buffer(fn);
505 #if ENABLE_FEATURE_VI_YANKMARK
506 YDreg = 26; // default Yank/Delete reg
507 Ureg = 27; // hold orig line for "U" cmd
508 mark[26] = mark[27] = text; // init "previous context"
511 last_forward_char = last_input_char = '\0';
515 #if ENABLE_FEATURE_VI_USE_SIGNALS
517 signal(SIGWINCH, winch_sig);
518 signal(SIGTSTP, suspend_sig);
519 sig = sigsetjmp(restart, 1);
521 screenbegin = dot = text;
525 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
528 offset = 0; // no horizontal offset
530 #if ENABLE_FEATURE_VI_DOT_CMD
532 ioq = ioq_start = NULL;
537 #if ENABLE_FEATURE_VI_COLON
542 while ((p = initial_cmds[n])) {
552 free(initial_cmds[n]);
553 initial_cmds[n] = NULL;
558 redraw(FALSE); // dont force every col re-draw
559 //------This is the main Vi cmd handling loop -----------------------
560 while (editing > 0) {
561 #if ENABLE_FEATURE_VI_CRASHME
563 if ((end - text) > 1) {
564 crash_dummy(); // generate a random command
567 dot = string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
572 last_input_char = c = get_one_char(); // get a cmd from user
573 #if ENABLE_FEATURE_VI_YANKMARK
574 // save a copy of the current line- for the 'U" command
575 if (begin_line(dot) != cur_line) {
576 cur_line = begin_line(dot);
577 text_yank(begin_line(dot), end_line(dot), Ureg);
580 #if ENABLE_FEATURE_VI_DOT_CMD
581 // These are commands that change text[].
582 // Remember the input for the "." command
583 if (!adding2q && ioq_start == NULL
584 && strchr(modifying_cmds, c)
589 do_cmd(c); // execute the user command
591 // poll to see if there is input already waiting. if we are
592 // not able to display output fast enough to keep up, skip
593 // the display update until we catch up with input.
594 if (mysleep(0) == 0) {
595 // no input pending- so update output
599 #if ENABLE_FEATURE_VI_CRASHME
601 crash_test(); // test editor variables
604 //-------------------------------------------------------------------
606 place_cursor(rows, 0, FALSE); // go to bottom of screen
607 clear_to_eol(); // Erase to end of line
611 //----- The Colon commands -------------------------------------
612 #if ENABLE_FEATURE_VI_COLON
613 static char *get_one_address(char *p, int *addr) // get colon addr, if present
617 USE_FEATURE_VI_YANKMARK(char c;)
618 USE_FEATURE_VI_SEARCH(char *pat;)
620 *addr = -1; // assume no addr
621 if (*p == '.') { // the current line
624 *addr = count_lines(text, q);
626 #if ENABLE_FEATURE_VI_YANKMARK
627 else if (*p == '\'') { // is this a mark addr
631 if (c >= 'a' && c <= 'z') {
634 q = mark[(unsigned char) c];
635 if (q != NULL) { // is mark valid
636 *addr = count_lines(text, q); // count lines
641 #if ENABLE_FEATURE_VI_SEARCH
642 else if (*p == '/') { // a search pattern
643 q = strchrnul(++p, '/');
644 pat = xstrndup(p, q - p); // save copy of pattern
648 q = char_search(dot, pat, FORWARD, FULL);
650 *addr = count_lines(text, q);
655 else if (*p == '$') { // the last line in file
657 q = begin_line(end - 1);
658 *addr = count_lines(text, q);
659 } else if (isdigit(*p)) { // specific line number
660 sscanf(p, "%d%n", addr, &st);
663 // unrecognised address - assume -1
669 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
671 //----- get the address' i.e., 1,3 'a,'b -----
672 // get FIRST addr, if present
674 p++; // skip over leading spaces
675 if (*p == '%') { // alias for 1,$
678 *e = count_lines(text, end-1);
681 p = get_one_address(p, b);
684 if (*p == ',') { // is there a address separator
688 // get SECOND addr, if present
689 p = get_one_address(p, e);
693 p++; // skip over trailing spaces
697 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
698 static void setops(const char *args, const char *opname, int flg_no,
699 const char *short_opname, int opt)
701 const char *a = args + flg_no;
702 int l = strlen(opname) - 1; /* opname have + ' ' */
704 if (strncasecmp(a, opname, l) == 0
705 || strncasecmp(a, short_opname, 2) == 0
715 // buf must be no longer than MAX_INPUT_LEN!
716 static void colon(char *buf)
718 char c, *orig_buf, *buf1, *q, *r;
719 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
720 int i, l, li, ch, b, e;
721 int useforce, forced = FALSE;
723 // :3154 // if (-e line 3154) goto it else stay put
724 // :4,33w! foo // write a portion of buffer to file "foo"
725 // :w // write all of buffer to current file
727 // :q! // quit- dont care about modified file
728 // :'a,'z!sort -u // filter block through sort
729 // :'f // goto mark "f"
730 // :'fl // list literal the mark "f" line
731 // :.r bar // read file "bar" into buffer before dot
732 // :/123/,/abc/d // delete lines from "123" line to "abc" line
733 // :/xyz/ // goto the "xyz" line
734 // :s/find/replace/ // substitute pattern "find" with "replace"
735 // :!<cmd> // run <cmd> then return
741 buf++; // move past the ':'
745 q = text; // assume 1,$ for the range
747 li = count_lines(text, end - 1);
748 fn = current_filename;
750 // look for optional address(es) :. :1 :1,9 :'q,'a :%
751 buf = get_address(buf, &b, &e);
753 // remember orig command line
756 // get the COMMAND into cmd[]
758 while (*buf != '\0') {
765 while (isblank(*buf))
769 buf1 = last_char_is(cmd, '!');
772 *buf1 = '\0'; // get rid of !
775 // if there is only one addr, then the addr
776 // is the line number of the single line the
777 // user wants. So, reset the end
778 // pointer to point at end of the "b" line
779 q = find_line(b); // what line is #b
784 // we were given two addrs. change the
785 // end pointer to the addr given by user.
786 r = find_line(e); // what line is #e
790 // ------------ now look for the command ------------
792 if (i == 0) { // :123CR goto line #123
794 dot = find_line(b); // what line is #b
798 #if ENABLE_FEATURE_ALLOW_EXEC
799 else if (strncmp(cmd, "!", 1) == 0) { // run a cmd
801 // :!ls run the <cmd>
802 place_cursor(rows - 1, 0, FALSE); // go to Status line
803 clear_to_eol(); // clear the line
805 retcode = system(orig_buf + 1); // run the cmd
807 printf("\nshell returned %i\n\n", retcode);
809 Hit_Return(); // let user see results
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 int find_range(char ** start, char ** stop, char c)
1667 char *save_dot, *p, *q, *t;
1668 int cnt, multiline = 0;
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("^%$0bBeEfth\b\177", 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 // nothing -- this causes any other values of c to
1710 // represent the one-character range under the
1711 // cursor. this is correct for ' ' and 'l', but
1712 // perhaps no others.
1721 // backward char movements don't include start position
1722 if (q > p && strchr("^0bBh\b\177", c)) q--;
1725 for (t = p; t <= q; t++) {
1738 static int st_test(char * p, int type, int dir, char * tested)
1748 if (type == S_BEFORE_WS) {
1750 test = ((!isspace(c)) || c == '\n');
1752 if (type == S_TO_WS) {
1754 test = ((!isspace(c)) || c == '\n');
1756 if (type == S_OVER_WS) {
1758 test = ((isspace(c)));
1760 if (type == S_END_PUNCT) {
1762 test = ((ispunct(c)));
1764 if (type == S_END_ALNUM) {
1766 test = ((isalnum(c)) || c == '_');
1772 static char *skip_thing(char * p, int linecnt, int dir, int type)
1776 while (st_test(p, type, dir, &c)) {
1777 // make sure we limit search to correct number of lines
1778 if (c == '\n' && --linecnt < 1)
1780 if (dir >= 0 && p >= end - 1)
1782 if (dir < 0 && p <= text)
1784 p += dir; // move to next char
1789 // find matching char of pair () [] {}
1790 static char *find_pair(char * p, const char c)
1797 dir = 1; // assume forward
1799 case '(': match = ')'; break;
1800 case '[': match = ']'; break;
1801 case '{': match = '}'; break;
1802 case ')': match = '('; dir = -1; break;
1803 case ']': match = '['; dir = -1; break;
1804 case '}': match = '{'; dir = -1; break;
1806 for (q = p + dir; text <= q && q < end; q += dir) {
1807 // look for match, count levels of pairs (( ))
1809 level++; // increase pair levels
1811 level--; // reduce pair level
1813 break; // found matching pair
1816 q = NULL; // indicate no match
1820 #if ENABLE_FEATURE_VI_SETOPTS
1821 // show the matching char of a pair, () [] {}
1822 static void showmatching(char *p)
1826 // we found half of a pair
1827 q = find_pair(p, *p); // get loc of matching char
1829 indicate_error('3'); // no matching char
1831 // "q" now points to matching pair
1832 save_dot = dot; // remember where we are
1833 dot = q; // go to new loc
1834 refresh(FALSE); // let the user see it
1835 mysleep(40); // give user some time
1836 dot = save_dot; // go back to old loc
1840 #endif /* FEATURE_VI_SETOPTS */
1842 // open a hole in text[]
1843 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1852 cnt = end - src; // the rest of buffer
1853 if ( ((end + size) >= (text + text_size)) // TODO: realloc here
1854 || memmove(dest, src, cnt) != dest) {
1855 status_line_bold("can't create room for new characters");
1859 memset(p, ' ', size); // clear new hole
1860 end += size; // adjust the new END
1861 file_modified++; // has the file been modified
1866 // close a hole in text[]
1867 static char *text_hole_delete(char * p, char * q) // delete "p" through "q", inclusive
1872 // move forwards, from beginning
1876 if (q < p) { // they are backward- swap them
1880 hole_size = q - p + 1;
1882 if (src < text || src > end)
1884 if (dest < text || dest >= end)
1887 goto thd_atend; // just delete the end of the buffer
1888 if (memmove(dest, src, cnt) != dest) {
1889 status_line_bold("can't delete the character");
1892 end = end - hole_size; // adjust the new END
1894 dest = end - 1; // make sure dest in below end-1
1896 dest = end = text; // keep pointers valid
1897 file_modified++; // has the file been modified
1902 // copy text into register, then delete text.
1903 // if dist <= 0, do not include, or go past, a NewLine
1905 static char *yank_delete(char * start, char * stop, int dist, int yf)
1909 // make sure start <= stop
1911 // they are backwards, reverse them
1917 // we cannot cross NL boundaries
1921 // dont go past a NewLine
1922 for (; p + 1 <= stop; p++) {
1924 stop = p; // "stop" just before NewLine
1930 #if ENABLE_FEATURE_VI_YANKMARK
1931 text_yank(start, stop, YDreg);
1933 if (yf == YANKDEL) {
1934 p = text_hole_delete(start, stop);
1939 static void show_help(void)
1941 puts("These features are available:"
1942 #if ENABLE_FEATURE_VI_SEARCH
1943 "\n\tPattern searches with / and ?"
1945 #if ENABLE_FEATURE_VI_DOT_CMD
1946 "\n\tLast command repeat with \'.\'"
1948 #if ENABLE_FEATURE_VI_YANKMARK
1949 "\n\tLine marking with 'x"
1950 "\n\tNamed buffers with \"x"
1952 #if ENABLE_FEATURE_VI_READONLY
1953 "\n\tReadonly if vi is called as \"view\""
1954 "\n\tReadonly with -R command line arg"
1956 #if ENABLE_FEATURE_VI_SET
1957 "\n\tSome colon mode commands with \':\'"
1959 #if ENABLE_FEATURE_VI_SETOPTS
1960 "\n\tSettable options with \":set\""
1962 #if ENABLE_FEATURE_VI_USE_SIGNALS
1963 "\n\tSignal catching- ^C"
1964 "\n\tJob suspend and resume with ^Z"
1966 #if ENABLE_FEATURE_VI_WIN_RESIZE
1967 "\n\tAdapt to window re-sizes"
1972 #if ENABLE_FEATURE_VI_DOT_CMD
1973 static void start_new_cmd_q(char c)
1975 // get buffer for new cmd
1976 if (!last_modifying_cmd)
1977 last_modifying_cmd = xzalloc(MAX_INPUT_LEN);
1978 // if there is a current cmd count put it in the buffer first
1980 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1981 else { // just save char c onto queue
1982 last_modifying_cmd[0] = c;
1988 static void end_cmd_q(void)
1990 #if ENABLE_FEATURE_VI_YANKMARK
1991 YDreg = 26; // go back to default Yank/Delete reg
1995 #endif /* FEATURE_VI_DOT_CMD */
1997 #if ENABLE_FEATURE_VI_YANKMARK \
1998 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1999 || ENABLE_FEATURE_VI_CRASHME
2000 static char *string_insert(char * p, char * s) // insert the string at 'p'
2005 if (text_hole_make(p, i)) {
2007 for (cnt = 0; *s != '\0'; s++) {
2011 #if ENABLE_FEATURE_VI_YANKMARK
2012 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2019 #if ENABLE_FEATURE_VI_YANKMARK
2020 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2025 if (q < p) { // they are backwards- reverse them
2032 free(t); // if already a yank register, free it
2033 t = xmalloc(cnt + 1); // get a new register
2034 memset(t, '\0', cnt + 1); // clear new text[]
2035 strncpy(t, p, cnt); // copy text[] into bufer
2040 static char what_reg(void)
2044 c = 'D'; // default to D-reg
2045 if (0 <= YDreg && YDreg <= 25)
2046 c = 'a' + (char) YDreg;
2054 static void check_context(char cmd)
2056 // A context is defined to be "modifying text"
2057 // Any modifying command establishes a new context.
2059 if (dot < context_start || dot > context_end) {
2060 if (strchr(modifying_cmds, cmd) != NULL) {
2061 // we are trying to modify text[]- make this the current context
2062 mark[27] = mark[26]; // move cur to prev
2063 mark[26] = dot; // move local to cur
2064 context_start = prev_line(prev_line(dot));
2065 context_end = next_line(next_line(dot));
2066 //loiter= start_loiter= now;
2071 static char *swap_context(char *p) // goto new context for '' command make this the current context
2075 // the current context is in mark[26]
2076 // the previous context is in mark[27]
2077 // only swap context if other context is valid
2078 if (text <= mark[27] && mark[27] <= end - 1) {
2080 mark[27] = mark[26];
2082 p = mark[26]; // where we are going- previous context
2083 context_start = prev_line(prev_line(prev_line(p)));
2084 context_end = next_line(next_line(next_line(p)));
2088 #endif /* FEATURE_VI_YANKMARK */
2090 //----- Set terminal attributes --------------------------------
2091 static void rawmode(void)
2093 tcgetattr(0, &term_orig);
2094 term_vi = term_orig;
2095 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2096 term_vi.c_iflag &= (~IXON & ~ICRNL);
2097 term_vi.c_oflag &= (~ONLCR);
2098 term_vi.c_cc[VMIN] = 1;
2099 term_vi.c_cc[VTIME] = 0;
2100 erase_char = term_vi.c_cc[VERASE];
2101 tcsetattr(0, TCSANOW, &term_vi);
2104 static void cookmode(void)
2107 tcsetattr(0, TCSANOW, &term_orig);
2110 //----- Come here when we get a window resize signal ---------
2111 #if ENABLE_FEATURE_VI_USE_SIGNALS
2112 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2114 // FIXME: do it in main loop!!!
2115 signal(SIGWINCH, winch_sig);
2116 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2117 get_terminal_width_height(0, &columns, &rows);
2118 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2119 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2121 new_screen(rows, columns); // get memory for virtual screen
2122 redraw(TRUE); // re-draw the screen
2125 //----- Come here when we get a continue signal -------------------
2126 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2128 rawmode(); // terminal to "raw"
2129 last_status_cksum = 0; // force status update
2130 redraw(TRUE); // re-draw the screen
2132 signal(SIGTSTP, suspend_sig);
2133 signal(SIGCONT, SIG_DFL);
2134 kill(my_pid, SIGCONT);
2137 //----- Come here when we get a Suspend signal -------------------
2138 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2140 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2141 clear_to_eol(); // Erase to end of line
2142 cookmode(); // terminal to "cooked"
2144 signal(SIGCONT, cont_sig);
2145 signal(SIGTSTP, SIG_DFL);
2146 kill(my_pid, SIGTSTP);
2149 //----- Come here when we get a signal ---------------------------
2150 static void catch_sig(int sig)
2152 signal(SIGINT, catch_sig);
2154 siglongjmp(restart, sig);
2156 #endif /* FEATURE_VI_USE_SIGNALS */
2158 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2160 struct pollfd pfd[1];
2163 pfd[0].events = POLLIN;
2164 return safe_poll(pfd, 1, hund*10) > 0;
2167 static int chars_to_parse;
2169 //----- IO Routines --------------------------------------------
2170 static char readit(void) // read (maybe cursor) key from stdin
2179 static const struct esc_cmds esccmds[] = {
2180 {"OA" , VI_K_UP }, // cursor key Up
2181 {"OB" , VI_K_DOWN }, // cursor key Down
2182 {"OC" , VI_K_RIGHT }, // Cursor Key Right
2183 {"OD" , VI_K_LEFT }, // cursor key Left
2184 {"OH" , VI_K_HOME }, // Cursor Key Home
2185 {"OF" , VI_K_END }, // Cursor Key End
2186 {"[A" , VI_K_UP }, // cursor key Up
2187 {"[B" , VI_K_DOWN }, // cursor key Down
2188 {"[C" , VI_K_RIGHT }, // Cursor Key Right
2189 {"[D" , VI_K_LEFT }, // cursor key Left
2190 {"[H" , VI_K_HOME }, // Cursor Key Home
2191 {"[F" , VI_K_END }, // Cursor Key End
2192 {"[1~" , VI_K_HOME }, // Cursor Key Home
2193 {"[2~" , VI_K_INSERT }, // Cursor Key Insert
2194 {"[3~" , VI_K_DELETE }, // Cursor Key Delete
2195 {"[4~" , VI_K_END }, // Cursor Key End
2196 {"[5~" , VI_K_PAGEUP }, // Cursor Key Page Up
2197 {"[6~" , VI_K_PAGEDOWN}, // Cursor Key Page Down
2198 {"OP" , VI_K_FUN1 }, // Function Key F1
2199 {"OQ" , VI_K_FUN2 }, // Function Key F2
2200 {"OR" , VI_K_FUN3 }, // Function Key F3
2201 {"OS" , VI_K_FUN4 }, // Function Key F4
2202 // careful: these have no terminating NUL!
2203 {"[11~", VI_K_FUN1 }, // Function Key F1
2204 {"[12~", VI_K_FUN2 }, // Function Key F2
2205 {"[13~", VI_K_FUN3 }, // Function Key F3
2206 {"[14~", VI_K_FUN4 }, // Function Key F4
2207 {"[15~", VI_K_FUN5 }, // Function Key F5
2208 {"[17~", VI_K_FUN6 }, // Function Key F6
2209 {"[18~", VI_K_FUN7 }, // Function Key F7
2210 {"[19~", VI_K_FUN8 }, // Function Key F8
2211 {"[20~", VI_K_FUN9 }, // Function Key F9
2212 {"[21~", VI_K_FUN10 }, // Function Key F10
2213 {"[23~", VI_K_FUN11 }, // Function Key F11
2214 {"[24~", VI_K_FUN12 }, // Function Key F12
2216 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
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)
2243 && ((size_t)n <= (sizeof(readbuffer) - 8))
2245 // read the rest of the ESC string
2246 int r = safe_read(0, readbuffer + n, sizeof(readbuffer) - n);
2254 if (c == 27 && n > 1) {
2255 // Maybe cursor or function key?
2256 const struct esc_cmds *eindex;
2258 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2259 int cnt = strnlen(eindex->seq, 4);
2262 if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
2264 c = eindex->val; // magic char value
2265 n = cnt + 1; // squeeze out the ESC sequence
2268 // defined ESC sequence not found
2272 // remove key sequence from Q
2273 chars_to_parse -= n;
2274 memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
2278 //----- IO Routines --------------------------------------------
2279 static char get_one_char(void)
2283 #if ENABLE_FEATURE_VI_DOT_CMD
2285 // we are not adding to the q.
2286 // but, we may be reading from a q
2288 // there is no current q, read from STDIN
2289 c = readit(); // get the users input
2291 // there is a queue to get chars from first
2294 // the end of the q, read from STDIN
2296 ioq_start = ioq = 0;
2297 c = readit(); // get the users input
2301 // adding STDIN chars to q
2302 c = readit(); // get the users input
2303 if (last_modifying_cmd != NULL) {
2304 if (lmc_len >= MAX_INPUT_LEN - 1) {
2305 status_line_bold("last_modifying_cmd overrun");
2307 // add new char to q
2308 last_modifying_cmd[lmc_len++] = c;
2313 c = readit(); // get the users input
2314 #endif /* FEATURE_VI_DOT_CMD */
2318 // Get input line (uses "status line" area)
2319 static char *get_input_line(const char *prompt)
2321 static char *buf; // [MAX_INPUT_LEN]
2326 if (!buf) buf = xmalloc(MAX_INPUT_LEN);
2328 strcpy(buf, prompt);
2329 last_status_cksum = 0; // force status update
2330 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2331 clear_to_eol(); // clear the line
2332 write1(prompt); // write out the :, /, or ? prompt
2335 while (i < MAX_INPUT_LEN) {
2337 if (c == '\n' || c == '\r' || c == 27)
2338 break; // this is end of input
2339 if (c == erase_char || c == 8 || c == 127) {
2340 // user wants to erase prev char
2342 write1("\b \b"); // erase char on screen
2343 if (i <= 0) // user backs up before b-o-l, exit
2355 static int file_size(const char *fn) // what is the byte size of "fn"
2361 if (fn && fn[0] && stat(fn, &st_buf) == 0) // see if file exists
2362 cnt = (int) st_buf.st_size;
2366 static int file_insert(const char * fn, char *p
2367 USE_FEATURE_VI_READONLY(, int update_ro_status))
2371 struct stat statbuf;
2374 if (stat(fn, &statbuf) < 0) {
2375 status_line_bold("\"%s\" %s", fn, strerror(errno));
2378 if ((statbuf.st_mode & S_IFREG) == 0) {
2379 // This is not a regular file
2380 status_line_bold("\"%s\" Not a regular file", fn);
2383 /* // this check is done by open()
2384 if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2385 // dont have any read permissions
2386 status_line_bold("\"%s\" Not readable", fn);
2390 if (p < text || p > end) {
2391 status_line_bold("Trying to insert file outside of memory");
2395 // read file to buffer
2396 fd = open(fn, O_RDONLY);
2398 status_line_bold("\"%s\" %s", fn, strerror(errno));
2401 size = statbuf.st_size;
2402 p = text_hole_make(p, size);
2405 cnt = safe_read(fd, p, size);
2407 status_line_bold("\"%s\" %s", fn, strerror(errno));
2408 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2409 } else if (cnt < size) {
2410 // There was a partial read, shrink unused space text[]
2411 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2412 status_line_bold("cannot read all of file \"%s\"", fn);
2418 #if ENABLE_FEATURE_VI_READONLY
2419 if (update_ro_status
2420 && ((access(fn, W_OK) < 0) ||
2421 /* root will always have access()
2422 * so we check fileperms too */
2423 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2426 SET_READONLY_FILE(readonly_mode);
2433 static int file_write(char * fn, char * first, char * last)
2435 int fd, cnt, charcnt;
2438 status_line_bold("No current filename");
2442 /* By popular request we do not open file with O_TRUNC,
2443 * but instead ftruncate() it _after_ successful write.
2444 * Might reduce amount of data lost on power fail etc.
2446 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2449 cnt = last - first + 1;
2450 charcnt = full_write(fd, first, cnt);
2451 ftruncate(fd, charcnt);
2452 if (charcnt == cnt) {
2454 //file_modified = FALSE; // the file has not been modified
2462 //----- Terminal Drawing ---------------------------------------
2463 // The terminal is made up of 'rows' line of 'columns' columns.
2464 // classically this would be 24 x 80.
2465 // screen coordinates
2471 // 23,0 ... 23,79 <- status line
2473 //----- Move the cursor to row x col (count from 0, not 1) -------
2474 static void place_cursor(int row, int col, int optimize)
2476 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2479 if (row < 0) row = 0;
2480 if (row >= rows) row = rows - 1;
2481 if (col < 0) col = 0;
2482 if (col >= columns) col = columns - 1;
2484 //----- 1. Try the standard terminal ESC sequence
2485 sprintf(cm1, CMrc, row + 1, col + 1);
2488 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2489 if (optimize && col < 16) {
2491 SZ_UP = sizeof(CMup),
2492 SZ_DN = sizeof(CMdown),
2493 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2495 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2497 int Rrow = last_row;
2498 int diff = Rrow - row;
2500 if (diff < -5 || diff > 5)
2503 //----- find the minimum # of chars to move cursor -------------
2504 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2507 // move to the correct row
2508 while (row < Rrow) {
2509 // the cursor has to move up
2513 while (row > Rrow) {
2514 // the cursor has to move down
2515 strcat(cm2, CMdown);
2519 // now move to the correct column
2520 strcat(cm2, "\r"); // start at col 0
2521 // just send out orignal source char to get to correct place
2522 screenp = &screen[row * columns]; // start of screen line
2523 strncat(cm2, screenp, col);
2525 // pick the shortest cursor motion to send out
2526 if (strlen(cm2) < strlen(cm)) {
2532 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2536 //----- Erase from cursor to end of line -----------------------
2537 static void clear_to_eol(void)
2539 write1(Ceol); // Erase from cursor to end of line
2542 //----- Erase from cursor to end of screen -----------------------
2543 static void clear_to_eos(void)
2545 write1(Ceos); // Erase from cursor to end of screen
2548 //----- Start standout mode ------------------------------------
2549 static void standout_start(void) // send "start reverse video" sequence
2551 write1(SOs); // Start reverse video mode
2554 //----- End standout mode --------------------------------------
2555 static void standout_end(void) // send "end reverse video" sequence
2557 write1(SOn); // End reverse video mode
2560 //----- Flash the screen --------------------------------------
2561 static void flash(int h)
2563 standout_start(); // send "start reverse video" sequence
2566 standout_end(); // send "end reverse video" sequence
2570 static void Indicate_Error(void)
2572 #if ENABLE_FEATURE_VI_CRASHME
2574 return; // generate a random command
2577 write1(bell); // send out a bell character
2583 //----- Screen[] Routines --------------------------------------
2584 //----- Erase the Screen[] memory ------------------------------
2585 static void screen_erase(void)
2587 memset(screen, ' ', screensize); // clear new screen
2590 static int bufsum(char *buf, int count)
2593 char *e = buf + count;
2596 sum += (unsigned char) *buf++;
2600 //----- Draw the status line at bottom of the screen -------------
2601 static void show_status_line(void)
2603 int cnt = 0, cksum = 0;
2605 // either we already have an error or status message, or we
2607 if (!have_status_msg) {
2608 cnt = format_edit_status();
2609 cksum = bufsum(status_buffer, cnt);
2611 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2612 last_status_cksum = cksum; // remember if we have seen this line
2613 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2614 write1(status_buffer);
2616 if (have_status_msg) {
2617 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2619 have_status_msg = 0;
2622 have_status_msg = 0;
2624 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2629 //----- format the status buffer, the bottom line of screen ------
2630 // format status buffer, with STANDOUT mode
2631 static void status_line_bold(const char *format, ...)
2635 va_start(args, format);
2636 strcpy(status_buffer, SOs); // Terminal standout mode on
2637 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2638 strcat(status_buffer, SOn); // Terminal standout mode off
2641 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2644 // format status buffer
2645 static void status_line(const char *format, ...)
2649 va_start(args, format);
2650 vsprintf(status_buffer, format, args);
2653 have_status_msg = 1;
2656 // copy s to buf, convert unprintable
2657 static void print_literal(char *buf, const char *s)
2670 c_is_no_print = (c & 0x80) && !Isprint(c);
2671 if (c_is_no_print) {
2675 if (c < ' ' || c == 127) {
2688 if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
2693 static void not_implemented(const char *s)
2695 char buf[MAX_INPUT_LEN];
2697 print_literal(buf, s);
2698 status_line_bold("\'%s\' is not implemented", buf);
2701 // show file status on status line
2702 static int format_edit_status(void)
2705 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2706 int cur, percent, ret, trunc_at;
2708 // file_modified is now a counter rather than a flag. this
2709 // helps reduce the amount of line counting we need to do.
2710 // (this will cause a mis-reporting of modified status
2711 // once every MAXINT editing operations.)
2713 // it would be nice to do a similar optimization here -- if
2714 // we haven't done a motion that could have changed which line
2715 // we're on, then we shouldn't have to do this count_lines()
2716 cur = count_lines(text, dot);
2718 // reduce counting -- the total lines can't have
2719 // changed if we haven't done any edits.
2720 if (file_modified != last_file_modified) {
2721 tot = cur + count_lines(dot, end - 1) - 1;
2722 last_file_modified = file_modified;
2725 // current line percent
2726 // ------------- ~~ ----------
2729 percent = (100 * cur) / tot;
2735 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2736 columns : STATUS_BUFFER_LEN-1;
2738 ret = snprintf(status_buffer, trunc_at+1,
2739 #if ENABLE_FEATURE_VI_READONLY
2740 "%c %s%s%s %d/%d %d%%",
2742 "%c %s%s %d/%d %d%%",
2744 cmd_mode_indicator[cmd_mode & 3],
2745 (current_filename != NULL ? current_filename : "No file"),
2746 #if ENABLE_FEATURE_VI_READONLY
2747 (readonly_mode ? " [Readonly]" : ""),
2749 (file_modified ? " [Modified]" : ""),
2752 if (ret >= 0 && ret < trunc_at)
2753 return ret; /* it all fit */
2755 return trunc_at; /* had to truncate */
2758 //----- Force refresh of all Lines -----------------------------
2759 static void redraw(int full_screen)
2761 place_cursor(0, 0, FALSE); // put cursor in correct place
2762 clear_to_eos(); // tel terminal to erase display
2763 screen_erase(); // erase the internal screen buffer
2764 last_status_cksum = 0; // force status update
2765 refresh(full_screen); // this will redraw the entire display
2769 //----- Format a text[] line into a buffer ---------------------
2770 static char* format_line(char *src /*, int li*/)
2775 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2777 c = '~'; // char in col 0 in non-existent lines is '~'
2779 while (co < columns + tabstop) {
2780 // have we gone past the end?
2785 if ((c & 0x80) && !Isprint(c)) {
2788 if (c < ' ' || c == 0x7f) {
2792 while ((co % tabstop) != (tabstop - 1)) {
2800 c += '@'; // Ctrl-X -> 'X'
2805 // discard scrolled-off-to-the-left portion,
2806 // in tabstop-sized pieces
2807 if (ofs >= tabstop && co >= tabstop) {
2808 memmove(dest, dest + tabstop, co);
2815 // check "short line, gigantic offset" case
2818 // discard last scrolled off part
2821 // fill the rest with spaces
2823 memset(&dest[co], ' ', columns - co);
2827 //----- Refresh the changed screen lines -----------------------
2828 // Copy the source line from text[] into the buffer and note
2829 // if the current screenline is different from the new buffer.
2830 // If they differ then that line needs redrawing on the terminal.
2832 static void refresh(int full_screen)
2834 static int old_offset;
2837 char *tp, *sp; // pointer into text[] and screen[]
2839 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2840 unsigned c = columns, r = rows;
2841 get_terminal_width_height(0, &columns, &rows);
2842 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2843 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2844 full_screen |= (c - columns) | (r - rows);
2846 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2847 tp = screenbegin; // index into text[] of top line
2849 // compare text[] to screen[] and mark screen[] lines that need updating
2850 for (li = 0; li < rows - 1; li++) {
2851 int cs, ce; // column start & end
2853 // format current text line
2854 out_buf = format_line(tp /*, li*/);
2856 // skip to the end of the current text[] line
2858 char *t = memchr(tp, '\n', end - tp);
2859 if (!t) t = end - 1;
2863 // see if there are any changes between vitual screen and out_buf
2864 changed = FALSE; // assume no change
2867 sp = &screen[li * columns]; // start of screen line
2869 // force re-draw of every single column from 0 - columns-1
2872 // compare newly formatted buffer with virtual screen
2873 // look forward for first difference between buf and screen
2874 for (; cs <= ce; cs++) {
2875 if (out_buf[cs] != sp[cs]) {
2876 changed = TRUE; // mark for redraw
2881 // look backward for last difference between out_buf and screen
2882 for (; ce >= cs; ce--) {
2883 if (out_buf[ce] != sp[ce]) {
2884 changed = TRUE; // mark for redraw
2888 // now, cs is index of first diff, and ce is index of last diff
2890 // if horz offset has changed, force a redraw
2891 if (offset != old_offset) {
2896 // make a sanity check of columns indexes
2898 if (ce > columns - 1) ce = columns - 1;
2899 if (cs > ce) { cs = 0; ce = columns - 1; }
2900 // is there a change between vitual screen and out_buf
2902 // copy changed part of buffer to virtual screen
2903 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2905 // move cursor to column of first change
2906 //if (offset != old_offset) {
2907 // // place_cursor is still too stupid
2908 // // to handle offsets correctly
2909 // place_cursor(li, cs, FALSE);
2911 place_cursor(li, cs, TRUE);
2914 // write line out to terminal
2915 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2919 place_cursor(crow, ccol, TRUE);
2921 old_offset = offset;
2924 //---------------------------------------------------------------------
2925 //----- the Ascii Chart -----------------------------------------------
2927 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2928 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2929 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2930 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2931 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2932 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2933 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2934 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2935 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2936 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2937 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2938 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2939 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2940 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2941 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2942 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2943 //---------------------------------------------------------------------
2945 //----- Execute a Vi Command -----------------------------------
2946 static void do_cmd(char c)
2948 const char *msg = msg; // for compiler
2949 char c1, *p, *q, *save_dot;
2951 int dir = dir; // for compiler
2954 // c1 = c; // quiet the compiler
2955 // cnt = yf = 0; // quiet the compiler
2956 // msg = p = q = save_dot = buf; // quiet the compiler
2957 memset(buf, '\0', 12);
2961 /* if this is a cursor key, skip these checks */
2974 if (cmd_mode == 2) {
2975 // flip-flop Insert/Replace mode
2976 if (c == VI_K_INSERT)
2978 // we are 'R'eplacing the current *dot with new char
2980 // don't Replace past E-o-l
2981 cmd_mode = 1; // convert to insert
2983 if (1 <= c || Isprint(c)) {
2985 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2986 dot = char_insert(dot, c); // insert new char
2991 if (cmd_mode == 1) {
2992 // hitting "Insert" twice means "R" replace mode
2993 if (c == VI_K_INSERT) goto dc5;
2994 // insert the char c at "dot"
2995 if (1 <= c || Isprint(c)) {
2996 dot = char_insert(dot, c);
3011 #if ENABLE_FEATURE_VI_CRASHME
3012 case 0x14: // dc4 ctrl-T
3013 crashme = (crashme == 0) ? 1 : 0;
3042 //case 'u': // u- FIXME- there is no undo
3044 default: // unrecognised command
3052 not_implemented(buf);
3053 end_cmd_q(); // stop adding to q
3054 case 0x00: // nul- ignore
3056 case 2: // ctrl-B scroll up full screen
3057 case VI_K_PAGEUP: // Cursor Key Page Up
3058 dot_scroll(rows - 2, -1);
3060 case 4: // ctrl-D scroll down half screen
3061 dot_scroll((rows - 2) / 2, 1);
3063 case 5: // ctrl-E scroll down one line
3066 case 6: // ctrl-F scroll down full screen
3067 case VI_K_PAGEDOWN: // Cursor Key Page Down
3068 dot_scroll(rows - 2, 1);
3070 case 7: // ctrl-G show current status
3071 last_status_cksum = 0; // force status update
3073 case 'h': // h- move left
3074 case VI_K_LEFT: // cursor key Left
3075 case 8: // ctrl-H- move left (This may be ERASE char)
3076 case 0x7f: // DEL- move left (This may be ERASE char)
3082 case 10: // Newline ^J
3083 case 'j': // j- goto next line, same col
3084 case VI_K_DOWN: // cursor key Down
3088 dot_next(); // go to next B-o-l
3089 dot = move_to_col(dot, ccol + offset); // try stay in same col
3091 case 12: // ctrl-L force redraw whole screen
3092 case 18: // ctrl-R force redraw
3093 place_cursor(0, 0, FALSE); // put cursor in correct place
3094 clear_to_eos(); // tel terminal to erase display
3096 screen_erase(); // erase the internal screen buffer
3097 last_status_cksum = 0; // force status update
3098 refresh(TRUE); // this will redraw the entire display
3100 case 13: // Carriage Return ^M
3101 case '+': // +- goto next line
3108 case 21: // ctrl-U scroll up half screen
3109 dot_scroll((rows - 2) / 2, -1);
3111 case 25: // ctrl-Y scroll up one line
3117 cmd_mode = 0; // stop insrting
3119 last_status_cksum = 0; // force status update
3121 case ' ': // move right
3122 case 'l': // move right
3123 case VI_K_RIGHT: // Cursor Key Right
3129 #if ENABLE_FEATURE_VI_YANKMARK
3130 case '"': // "- name a register to use for Delete/Yank
3131 c1 = get_one_char();
3139 case '\'': // '- goto a specific mark
3140 c1 = get_one_char();
3145 q = mark[(unsigned char) c1];
3146 if (text <= q && q < end) {
3148 dot_begin(); // go to B-o-l
3151 } else if (c1 == '\'') { // goto previous context
3152 dot = swap_context(dot); // swap current and previous context
3153 dot_begin(); // go to B-o-l
3159 case 'm': // m- Mark a line
3160 // this is really stupid. If there are any inserts or deletes
3161 // between text[0] and dot then this mark will not point to the
3162 // correct location! It could be off by many lines!
3163 // Well..., at least its quick and dirty.
3164 c1 = get_one_char();
3168 // remember the line
3169 mark[(int) c1] = dot;
3174 case 'P': // P- Put register before
3175 case 'p': // p- put register after
3178 status_line_bold("Nothing in register %c", what_reg());
3181 // are we putting whole lines or strings
3182 if (strchr(p, '\n') != NULL) {
3184 dot_begin(); // putting lines- Put above
3187 // are we putting after very last line?
3188 if (end_line(dot) == (end - 1)) {
3189 dot = end; // force dot to end of text[]
3191 dot_next(); // next line, then put before
3196 dot_right(); // move to right, can move to NL
3198 dot = string_insert(dot, p); // insert the string
3199 end_cmd_q(); // stop adding to q
3201 case 'U': // U- Undo; replace current line with original version
3202 if (reg[Ureg] != 0) {
3203 p = begin_line(dot);
3205 p = text_hole_delete(p, q); // delete cur line
3206 p = string_insert(p, reg[Ureg]); // insert orig line
3211 #endif /* FEATURE_VI_YANKMARK */
3212 case '$': // $- goto end of line
3213 case VI_K_END: // Cursor Key End
3217 dot = end_line(dot);
3219 case '%': // %- find matching char of pair () [] {}
3220 for (q = dot; q < end && *q != '\n'; q++) {
3221 if (strchr("()[]{}", *q) != NULL) {
3222 // we found half of a pair
3223 p = find_pair(q, *q);
3235 case 'f': // f- forward to a user specified char
3236 last_forward_char = get_one_char(); // get the search char
3238 // dont separate these two commands. 'f' depends on ';'
3240 //**** fall through to ... ';'
3241 case ';': // ;- look at rest of line for last forward char
3245 if (last_forward_char == 0)
3248 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3251 if (*q == last_forward_char)
3254 case ',': // repeat latest 'f' in opposite direction
3258 if (last_forward_char == 0)
3261 while (q >= text && *q != '\n' && *q != last_forward_char) {
3264 if (q >= text && *q == last_forward_char)
3268 case '-': // -- goto prev line
3275 #if ENABLE_FEATURE_VI_DOT_CMD
3276 case '.': // .- repeat the last modifying command
3277 // Stuff the last_modifying_cmd back into stdin
3278 // and let it be re-executed.
3279 if (last_modifying_cmd != NULL && lmc_len > 0) {
3280 last_modifying_cmd[lmc_len] = 0;
3281 ioq = ioq_start = xstrdup(last_modifying_cmd);
3285 #if ENABLE_FEATURE_VI_SEARCH
3286 case '?': // /- search for a pattern
3287 case '/': // /- search for a pattern
3290 q = get_input_line(buf); // get input line- use "status line"
3291 if (q[0] && !q[1]) {
3292 if (last_search_pattern[0])
3293 last_search_pattern[0] = c;
3294 goto dc3; // if no pat re-use old pat
3296 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3297 // there is a new pat
3298 free(last_search_pattern);
3299 last_search_pattern = xstrdup(q);
3300 goto dc3; // now find the pattern
3302 // user changed mind and erased the "/"- do nothing
3304 case 'N': // N- backward search for last pattern
3308 dir = BACK; // assume BACKWARD search
3310 if (last_search_pattern[0] == '?') {
3314 goto dc4; // now search for pattern
3316 case 'n': // n- repeat search for last pattern
3317 // search rest of text[] starting at next char
3318 // if search fails return orignal "p" not the "p+1" address
3323 if (last_search_pattern == 0) {
3324 msg = "No previous regular expression";
3327 if (last_search_pattern[0] == '/') {
3328 dir = FORWARD; // assume FORWARD search
3331 if (last_search_pattern[0] == '?') {
3336 q = char_search(p, last_search_pattern + 1, dir, FULL);
3338 dot = q; // good search, update "dot"
3342 // no pattern found between "dot" and "end"- continue at top
3347 q = char_search(p, last_search_pattern + 1, dir, FULL);
3348 if (q != NULL) { // found something
3349 dot = q; // found new pattern- goto it
3350 msg = "search hit BOTTOM, continuing at TOP";
3352 msg = "search hit TOP, continuing at BOTTOM";
3355 msg = "Pattern not found";
3359 status_line_bold("%s", msg);
3361 case '{': // {- move backward paragraph
3362 q = char_search(dot, "\n\n", BACK, FULL);
3363 if (q != NULL) { // found blank line
3364 dot = next_line(q); // move to next blank line
3367 case '}': // }- move forward paragraph
3368 q = char_search(dot, "\n\n", FORWARD, FULL);
3369 if (q != NULL) { // found blank line
3370 dot = next_line(q); // move to next blank line
3373 #endif /* FEATURE_VI_SEARCH */
3374 case '0': // 0- goto begining of line
3384 if (c == '0' && cmdcnt < 1) {
3385 dot_begin(); // this was a standalone zero
3387 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3390 case ':': // :- the colon mode commands
3391 p = get_input_line(":"); // get input line- use "status line"
3392 #if ENABLE_FEATURE_VI_COLON
3393 colon(p); // execute the command
3396 p++; // move past the ':'
3400 if (strncasecmp(p, "quit", cnt) == 0
3401 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3403 if (file_modified && p[1] != '!') {
3404 status_line_bold("No write since last change (:quit! overrides)");
3408 } else if (strncasecmp(p, "write", cnt) == 0
3409 || strncasecmp(p, "wq", cnt) == 0
3410 || strncasecmp(p, "wn", cnt) == 0
3411 || strncasecmp(p, "x", cnt) == 0
3413 cnt = file_write(current_filename, text, end - 1);
3416 status_line_bold("Write error: %s", strerror(errno));
3419 last_file_modified = -1;
3420 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3421 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3422 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3427 } else if (strncasecmp(p, "file", cnt) == 0) {
3428 last_status_cksum = 0; // force status update
3429 } else if (sscanf(p, "%d", &j) > 0) {
3430 dot = find_line(j); // go to line # j
3432 } else { // unrecognised cmd
3435 #endif /* !FEATURE_VI_COLON */
3437 case '<': // <- Left shift something
3438 case '>': // >- Right shift something
3439 cnt = count_lines(text, dot); // remember what line we are on
3440 c1 = get_one_char(); // get the type of thing to delete
3441 find_range(&p, &q, c1);
3442 yank_delete(p, q, 1, YANKONLY); // save copy before change
3445 i = count_lines(p, q); // # of lines we are shifting
3446 for ( ; i > 0; i--, p = next_line(p)) {
3448 // shift left- remove tab or 8 spaces
3450 // shrink buffer 1 char
3451 text_hole_delete(p, p);
3452 } else if (*p == ' ') {
3453 // we should be calculating columns, not just SPACE
3454 for (j = 0; *p == ' ' && j < tabstop; j++) {
3455 text_hole_delete(p, p);
3458 } else if (c == '>') {
3459 // shift right -- add tab or 8 spaces
3460 char_insert(p, '\t');
3463 dot = find_line(cnt); // what line were we on
3465 end_cmd_q(); // stop adding to q
3467 case 'A': // A- append at e-o-l
3468 dot_end(); // go to e-o-l
3469 //**** fall through to ... 'a'
3470 case 'a': // a- append after current char
3475 case 'B': // B- back a blank-delimited Word
3476 case 'E': // E- end of a blank-delimited word
3477 case 'W': // W- forward a blank-delimited word
3484 if (c == 'W' || isspace(dot[dir])) {
3485 dot = skip_thing(dot, 1, dir, S_TO_WS);
3486 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3489 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3491 case 'C': // C- Change to e-o-l
3492 case 'D': // D- delete to e-o-l
3494 dot = dollar_line(dot); // move to before NL
3495 // copy text into a register and delete
3496 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3498 goto dc_i; // start inserting
3499 #if ENABLE_FEATURE_VI_DOT_CMD
3501 end_cmd_q(); // stop adding to q
3504 case 'g': // 'gg' goto a line number (from vim)
3505 // (default to first line in file)
3506 c1 = get_one_char();
3511 not_implemented(buf);
3517 case 'G': // G- goto to a line number (default= E-O-F)
3518 dot = end - 1; // assume E-O-F
3520 dot = find_line(cmdcnt); // what line is #cmdcnt
3524 case 'H': // H- goto top line on screen
3526 if (cmdcnt > (rows - 1)) {
3527 cmdcnt = (rows - 1);
3534 case 'I': // I- insert before first non-blank
3537 //**** fall through to ... 'i'
3538 case 'i': // i- insert before current char
3539 case VI_K_INSERT: // Cursor Key Insert
3541 cmd_mode = 1; // start insrting
3543 case 'J': // J- join current and next lines together
3547 dot_end(); // move to NL
3548 if (dot < end - 1) { // make sure not last char in text[]
3549 *dot++ = ' '; // replace NL with space
3551 while (isblank(*dot)) { // delete leading WS
3555 end_cmd_q(); // stop adding to q
3557 case 'L': // L- goto bottom line on screen
3559 if (cmdcnt > (rows - 1)) {
3560 cmdcnt = (rows - 1);
3568 case 'M': // M- goto middle line on screen
3570 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3571 dot = next_line(dot);
3573 case 'O': // O- open a empty line above
3575 p = begin_line(dot);
3576 if (p[-1] == '\n') {
3578 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3580 dot = char_insert(dot, '\n');
3583 dot = char_insert(dot, '\n'); // i\n ESC
3588 case 'R': // R- continuous Replace char
3595 case 'X': // X- delete char before dot
3596 case 'x': // x- delete the current char
3597 case 's': // s- substitute the current char
3604 if (dot[dir] != '\n') {
3606 dot--; // delete prev char
3607 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3610 goto dc_i; // start insrting
3611 end_cmd_q(); // stop adding to q
3613 case 'Z': // Z- if modified, {write}; exit
3614 // ZZ means to save file (if necessary), then exit
3615 c1 = get_one_char();
3620 if (file_modified) {
3621 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3622 status_line_bold("\"%s\" File is read only", current_filename);
3625 cnt = file_write(current_filename, text, end - 1);
3628 status_line_bold("Write error: %s", strerror(errno));
3629 } else if (cnt == (end - 1 - text + 1)) {
3636 case '^': // ^- move to first non-blank on line
3640 case 'b': // b- back a word
3641 case 'e': // e- end of word
3648 if ((dot + dir) < text || (dot + dir) > end - 1)
3651 if (isspace(*dot)) {
3652 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3654 if (isalnum(*dot) || *dot == '_') {
3655 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3656 } else if (ispunct(*dot)) {
3657 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3660 case 'c': // c- change something
3661 case 'd': // d- delete something
3662 #if ENABLE_FEATURE_VI_YANKMARK
3663 case 'y': // y- yank something
3664 case 'Y': // Y- Yank a line
3667 int yf, ml, whole = 0;
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 // determine range, and whether it spans lines
3677 ml = find_range(&p, &q, c1);
3678 if (c1 == 27) { // ESC- user changed mind and wants out
3679 c = c1 = 27; // Escape- do nothing
3680 } else if (strchr("wW", c1)) {
3682 // don't include trailing WS as part of word
3683 while (isblank(*q)) {
3684 if (q <= text || q[-1] == '\n')
3689 dot = yank_delete(p, q, ml, yf); // delete word
3690 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3691 // partial line copy text into a register and delete
3692 dot = yank_delete(p, q, ml, yf); // delete word
3693 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3694 // whole line copy text into a register and delete
3695 dot = yank_delete(p, q, ml, yf); // delete lines
3698 // could not recognize object
3699 c = c1 = 27; // error-
3705 dot = char_insert(dot, '\n');
3706 // on the last line of file don't move to prev line
3707 if (whole && dot != (end-1)) {
3710 } else if (c == 'd') {
3716 // if CHANGING, not deleting, start inserting after the delete
3718 strcpy(buf, "Change");
3719 goto dc_i; // start inserting
3722 strcpy(buf, "Delete");
3724 #if ENABLE_FEATURE_VI_YANKMARK
3725 if (c == 'y' || c == 'Y') {
3726 strcpy(buf, "Yank");
3730 for (cnt = 0; p <= q; p++) {
3734 status_line("%s %d lines (%d chars) using [%c]",
3735 buf, cnt, strlen(reg[YDreg]), what_reg());
3737 end_cmd_q(); // stop adding to q
3741 case 'k': // k- goto prev line, same col
3742 case VI_K_UP: // cursor key Up
3747 dot = move_to_col(dot, ccol + offset); // try stay in same col
3749 case 'r': // r- replace the current char with user input
3750 c1 = get_one_char(); // get the replacement char
3753 file_modified++; // has the file been modified
3755 end_cmd_q(); // stop adding to q
3757 case 't': // t- move to char prior to next x
3758 last_forward_char = get_one_char();
3760 if (*dot == last_forward_char)
3762 last_forward_char= 0;
3764 case 'w': // w- forward a word
3768 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3769 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3770 } else if (ispunct(*dot)) { // we are on PUNCT
3771 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3774 dot++; // move over word
3775 if (isspace(*dot)) {
3776 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3780 c1 = get_one_char(); // get the replacement char
3783 cnt = (rows - 2) / 2; // put dot at center
3785 cnt = rows - 2; // put dot at bottom
3786 screenbegin = begin_line(dot); // start dot at top
3787 dot_scroll(cnt, -1);
3789 case '|': // |- move to column "cmdcnt"
3790 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3792 case '~': // ~- flip the case of letters a-z -> A-Z
3796 if (islower(*dot)) {
3797 *dot = toupper(*dot);
3798 file_modified++; // has the file been modified
3799 } else if (isupper(*dot)) {
3800 *dot = tolower(*dot);
3801 file_modified++; // has the file been modified
3804 end_cmd_q(); // stop adding to q
3806 //----- The Cursor and Function Keys -----------------------------
3807 case VI_K_HOME: // Cursor Key Home
3810 // The Fn keys could point to do_macro which could translate them
3811 case VI_K_FUN1: // Function Key F1
3812 case VI_K_FUN2: // Function Key F2
3813 case VI_K_FUN3: // Function Key F3
3814 case VI_K_FUN4: // Function Key F4
3815 case VI_K_FUN5: // Function Key F5
3816 case VI_K_FUN6: // Function Key F6
3817 case VI_K_FUN7: // Function Key F7
3818 case VI_K_FUN8: // Function Key F8
3819 case VI_K_FUN9: // Function Key F9
3820 case VI_K_FUN10: // Function Key F10
3821 case VI_K_FUN11: // Function Key F11
3822 case VI_K_FUN12: // Function Key F12
3827 // if text[] just became empty, add back an empty line
3829 char_insert(text, '\n'); // start empty buf with dummy line
3832 // it is OK for dot to exactly equal to end, otherwise check dot validity
3834 dot = bound_dot(dot); // make sure "dot" is valid
3836 #if ENABLE_FEATURE_VI_YANKMARK
3837 check_context(c); // update the current context
3841 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3842 cnt = dot - begin_line(dot);
3843 // Try to stay off of the Newline
3844 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3848 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3849 #if ENABLE_FEATURE_VI_CRASHME
3850 static int totalcmds = 0;
3851 static int Mp = 85; // Movement command Probability
3852 static int Np = 90; // Non-movement command Probability
3853 static int Dp = 96; // Delete command Probability
3854 static int Ip = 97; // Insert command Probability
3855 static int Yp = 98; // Yank command Probability
3856 static int Pp = 99; // Put command Probability
3857 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3858 static const char chars[20] = "\t012345 abcdABCD-=.$";
3859 static const char *const words[20] = {
3860 "this", "is", "a", "test",
3861 "broadcast", "the", "emergency", "of",
3862 "system", "quick", "brown", "fox",
3863 "jumped", "over", "lazy", "dogs",
3864 "back", "January", "Febuary", "March"
3866 static const char *const lines[20] = {
3867 "You should have received a copy of the GNU General Public License\n",
3868 "char c, cm, *cmd, *cmd1;\n",
3869 "generate a command by percentages\n",
3870 "Numbers may be typed as a prefix to some commands.\n",
3871 "Quit, discarding changes!\n",
3872 "Forced write, if permission originally not valid.\n",
3873 "In general, any ex or ed command (such as substitute or delete).\n",
3874 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3875 "Please get w/ me and I will go over it with you.\n",
3876 "The following is a list of scheduled, committed changes.\n",
3877 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3878 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3879 "Any question about transactions please contact Sterling Huxley.\n",
3880 "I will try to get back to you by Friday, December 31.\n",
3881 "This Change will be implemented on Friday.\n",
3882 "Let me know if you have problems accessing this;\n",
3883 "Sterling Huxley recently added you to the access list.\n",
3884 "Would you like to go to lunch?\n",
3885 "The last command will be automatically run.\n",
3886 "This is too much english for a computer geek.\n",
3888 static char *multilines[20] = {
3889 "You should have received a copy of the GNU General Public License\n",
3890 "char c, cm, *cmd, *cmd1;\n",
3891 "generate a command by percentages\n",
3892 "Numbers may be typed as a prefix to some commands.\n",
3893 "Quit, discarding changes!\n",
3894 "Forced write, if permission originally not valid.\n",
3895 "In general, any ex or ed command (such as substitute or delete).\n",
3896 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3897 "Please get w/ me and I will go over it with you.\n",
3898 "The following is a list of scheduled, committed changes.\n",
3899 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3900 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3901 "Any question about transactions please contact Sterling Huxley.\n",
3902 "I will try to get back to you by Friday, December 31.\n",
3903 "This Change will be implemented on Friday.\n",
3904 "Let me know if you have problems accessing this;\n",
3905 "Sterling Huxley recently added you to the access list.\n",
3906 "Would you like to go to lunch?\n",
3907 "The last command will be automatically run.\n",
3908 "This is too much english for a computer geek.\n",
3911 // create a random command to execute
3912 static void crash_dummy()
3914 static int sleeptime; // how long to pause between commands
3915 char c, cm, *cmd, *cmd1;
3916 int i, cnt, thing, rbi, startrbi, percent;
3918 // "dot" movement commands
3919 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3921 // is there already a command running?
3922 if (chars_to_parse > 0)
3926 sleeptime = 0; // how long to pause between commands
3927 memset(readbuffer, '\0', sizeof(readbuffer));
3928 // generate a command by percentages
3929 percent = (int) lrand48() % 100; // get a number from 0-99
3930 if (percent < Mp) { // Movement commands
3931 // available commands
3934 } else if (percent < Np) { // non-movement commands
3935 cmd = "mz<>\'\""; // available commands
3937 } else if (percent < Dp) { // Delete commands
3938 cmd = "dx"; // available commands
3940 } else if (percent < Ip) { // Inset commands
3941 cmd = "iIaAsrJ"; // available commands
3943 } else if (percent < Yp) { // Yank commands
3944 cmd = "yY"; // available commands
3946 } else if (percent < Pp) { // Put commands
3947 cmd = "pP"; // available commands
3950 // We do not know how to handle this command, try again
3954 // randomly pick one of the available cmds from "cmd[]"
3955 i = (int) lrand48() % strlen(cmd);
3957 if (strchr(":\024", cm))
3958 goto cd0; // dont allow colon or ctrl-T commands
3959 readbuffer[rbi++] = cm; // put cmd into input buffer
3961 // now we have the command-
3962 // there are 1, 2, and multi char commands
3963 // find out which and generate the rest of command as necessary
3964 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3965 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3966 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3967 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3969 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3971 readbuffer[rbi++] = c; // add movement to input buffer
3973 if (strchr("iIaAsc", cm)) { // multi-char commands
3975 // change some thing
3976 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3978 readbuffer[rbi++] = c; // add movement to input buffer
3980 thing = (int) lrand48() % 4; // what thing to insert
3981 cnt = (int) lrand48() % 10; // how many to insert
3982 for (i = 0; i < cnt; i++) {
3983 if (thing == 0) { // insert chars
3984 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3985 } else if (thing == 1) { // insert words
3986 strcat(readbuffer, words[(int) lrand48() % 20]);
3987 strcat(readbuffer, " ");
3988 sleeptime = 0; // how fast to type
3989 } else if (thing == 2) { // insert lines
3990 strcat(readbuffer, lines[(int) lrand48() % 20]);
3991 sleeptime = 0; // how fast to type
3992 } else { // insert multi-lines
3993 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3994 sleeptime = 0; // how fast to type
3997 strcat(readbuffer, "\033");
3999 chars_to_parse = strlen(readbuffer);
4003 mysleep(sleeptime); // sleep 1/100 sec
4006 // test to see if there are any errors
4007 static void crash_test()
4009 static time_t oldtim;
4016 strcat(msg, "end<text ");
4018 if (end > textend) {
4019 strcat(msg, "end>textend ");
4022 strcat(msg, "dot<text ");
4025 strcat(msg, "dot>end ");
4027 if (screenbegin < text) {
4028 strcat(msg, "screenbegin<text ");
4030 if (screenbegin > end - 1) {
4031 strcat(msg, "screenbegin>end-1 ");
4035 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4036 totalcmds, last_input_char, msg, SOs, SOn);
4038 while (safe_read(0, d, 1) > 0) {
4039 if (d[0] == '\n' || d[0] == '\r')
4044 if (tim >= (oldtim + 3)) {
4045 sprintf(status_buffer,
4046 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4047 totalcmds, M, N, I, D, Y, P, U, end - text + 1);