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 int 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 jmp_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 = setjmp(restart);
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 alarm(0); // wait for input- no alarms
803 place_cursor(rows - 1, 0, FALSE); // go to Status line
804 clear_to_eol(); // clear the line
806 retcode = system(orig_buf + 1); // run the cmd
808 printf("\nshell returned %i\n\n", retcode);
810 Hit_Return(); // let user see results
811 alarm(3); // done waiting for input
814 else if (strncmp(cmd, "=", i) == 0) { // where is the address
815 if (b < 0) { // no addr given- use defaults
816 b = e = count_lines(text, dot);
818 status_line("%d", b);
819 } else if (strncasecmp(cmd, "delete", i) == 0) { // delete lines
820 if (b < 0) { // no addr given- use defaults
821 q = begin_line(dot); // assume .,. for the range
824 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
826 } else if (strncasecmp(cmd, "edit", i) == 0) { // Edit a file
827 // don't edit, if the current file has been modified
828 if (file_modified && !useforce) {
829 status_line_bold("No write since last change (:edit! overrides)");
833 // the user supplied a file name
835 } else if (current_filename && current_filename[0]) {
836 // no user supplied name- use the current filename
837 // fn = current_filename; was set by default
839 // no user file name, no current name- punt
840 status_line_bold("No current filename");
844 if (init_text_buffer(fn) < 0)
847 #if ENABLE_FEATURE_VI_YANKMARK
848 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
849 free(reg[Ureg]); // free orig line reg- for 'U'
852 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
853 free(reg[YDreg]); // free default yank/delete register
857 // how many lines in text[]?
858 li = count_lines(text, end - 1);
859 status_line("\"%s\"%s"
860 USE_FEATURE_VI_READONLY("%s")
861 " %dL, %dC", current_filename,
862 (file_size(fn) < 0 ? " [New file]" : ""),
863 USE_FEATURE_VI_READONLY(
864 ((readonly_mode) ? " [Readonly]" : ""),
867 } else if (strncasecmp(cmd, "file", i) == 0) { // what File is this
868 if (b != -1 || e != -1) {
869 not_implemented("No address allowed on this command");
873 // user wants a new filename
874 free(current_filename);
875 current_filename = xstrdup(args);
877 // user wants file status info
878 last_status_cksum = 0; // force status update
880 } else if (strncasecmp(cmd, "features", i) == 0) { // what features are available
881 // print out values of all features
882 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
883 clear_to_eol(); // clear the line
888 } else if (strncasecmp(cmd, "list", i) == 0) { // literal print line
889 if (b < 0) { // no addr given- use defaults
890 q = begin_line(dot); // assume .,. for the range
893 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
894 clear_to_eol(); // clear the line
896 for (; q <= r; q++) {
900 c_is_no_print = (c & 0x80) && !Isprint(c);
907 } else if (c < ' ' || c == 127) {
918 #if ENABLE_FEATURE_VI_SET
922 } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
923 || strncasecmp(cmd, "next", i) == 0 // edit next file
926 // force end of argv list
933 // don't exit if the file been modified
935 status_line_bold("No write since last change (:%s! overrides)",
936 (*cmd == 'q' ? "quit" : "next"));
939 // are there other file to edit
940 if (*cmd == 'q' && optind < save_argc - 1) {
941 status_line_bold("%d more file to edit", (save_argc - optind - 1));
944 if (*cmd == 'n' && optind >= save_argc - 1) {
945 status_line_bold("No more files to edit");
949 } else if (strncasecmp(cmd, "read", i) == 0) { // read file into text[]
952 status_line_bold("No filename given");
955 if (b < 0) { // no addr given- use defaults
956 q = begin_line(dot); // assume "dot"
958 // read after current line- unless user said ":0r foo"
961 ch = file_insert(fn, q USE_FEATURE_VI_READONLY(, 0));
963 goto vc1; // nothing was inserted
964 // how many lines in text[]?
965 li = count_lines(q, q + ch - 1);
967 USE_FEATURE_VI_READONLY("%s")
969 USE_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
972 // if the insert is before "dot" then we need to update
977 } else if (strncasecmp(cmd, "rewind", i) == 0) { // rewind cmd line args
978 if (file_modified && !useforce) {
979 status_line_bold("No write since last change (:rewind! overrides)");
981 // reset the filenames to edit
982 optind = fn_start - 1;
985 #if ENABLE_FEATURE_VI_SET
986 } else if (strncasecmp(cmd, "set", i) == 0) { // set or clear features
987 #if ENABLE_FEATURE_VI_SETOPTS
990 i = 0; // offset into args
991 // only blank is regarded as args delmiter. What about tab '\t' ?
992 if (!args[0] || strcasecmp(args, "all") == 0) {
993 // print out values of all options
994 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
995 clear_to_eol(); // clear the line
996 printf("----------------------------------------\r\n");
997 #if ENABLE_FEATURE_VI_SETOPTS
1000 printf("autoindent ");
1006 printf("ignorecase ");
1009 printf("showmatch ");
1010 printf("tabstop=%d ", tabstop);
1015 #if ENABLE_FEATURE_VI_SETOPTS
1018 if (strncasecmp(argp, "no", 2) == 0)
1019 i = 2; // ":set noautoindent"
1020 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1021 setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
1022 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1023 setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
1025 if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
1026 sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
1027 if (ch > 0 && ch <= MAX_TABSTOP)
1030 while (*argp && *argp != ' ')
1031 argp++; // skip to arg delimiter (i.e. blank)
1032 while (*argp && *argp == ' ')
1033 argp++; // skip all delimiting blanks
1035 #endif /* FEATURE_VI_SETOPTS */
1036 #endif /* FEATURE_VI_SET */
1037 #if ENABLE_FEATURE_VI_SEARCH
1038 } else if (strncasecmp(cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1042 // F points to the "find" pattern
1043 // R points to the "replace" pattern
1044 // replace the cmd line delimiters "/" with NULLs
1045 gflag = 0; // global replace flag
1046 c = orig_buf[1]; // what is the delimiter
1047 F = orig_buf + 2; // start of "find"
1048 R = strchr(F, c); // middle delimiter
1049 if (!R) goto colon_s_fail;
1050 *R++ = '\0'; // terminate "find"
1051 buf1 = strchr(R, c);
1052 if (!buf1) goto colon_s_fail;
1053 *buf1++ = '\0'; // terminate "replace"
1054 if (*buf1 == 'g') { // :s/foo/bar/g
1056 gflag++; // turn on gflag
1059 if (b < 0) { // maybe :s/foo/bar/
1060 q = begin_line(dot); // start with cur line
1061 b = count_lines(text, q); // cur line number
1064 e = b; // maybe :.s/foo/bar/
1065 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1066 ls = q; // orig line start
1068 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1070 // we found the "find" pattern - delete it
1071 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1072 // inset the "replace" patern
1073 string_insert(buf1, R); // insert the string
1074 // check for "global" :s/foo/bar/g
1076 if ((buf1 + strlen(R)) < end_line(ls)) {
1077 q = buf1 + strlen(R);
1078 goto vc4; // don't let q move past cur line
1084 #endif /* FEATURE_VI_SEARCH */
1085 } else if (strncasecmp(cmd, "version", i) == 0) { // show software version
1086 status_line("%s", BB_VER " " BB_BT);
1087 } else if (strncasecmp(cmd, "write", i) == 0 // write text to file
1088 || strncasecmp(cmd, "wq", i) == 0
1089 || strncasecmp(cmd, "wn", i) == 0
1090 || strncasecmp(cmd, "x", i) == 0
1092 // is there a file name to write to?
1096 #if ENABLE_FEATURE_VI_READONLY
1097 if (readonly_mode && !useforce) {
1098 status_line_bold("\"%s\" File is read only", fn);
1102 // how many lines in text[]?
1103 li = count_lines(q, r);
1105 // see if file exists- if not, its just a new file request
1107 // if "fn" is not write-able, chmod u+w
1108 // sprintf(syscmd, "chmod u+w %s", fn);
1112 l = file_write(fn, q, r);
1113 if (useforce && forced) {
1115 // sprintf(syscmd, "chmod u-w %s", fn);
1121 status_line_bold("\"%s\" %s", fn, strerror(errno));
1123 status_line("\"%s\" %dL, %dC", fn, li, l);
1124 if (q == text && r == end - 1 && l == ch) {
1126 last_file_modified = -1;
1128 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1129 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1134 #if ENABLE_FEATURE_VI_READONLY
1137 #if ENABLE_FEATURE_VI_YANKMARK
1138 } else if (strncasecmp(cmd, "yank", i) == 0) { // yank lines
1139 if (b < 0) { // no addr given- use defaults
1140 q = begin_line(dot); // assume .,. for the range
1143 text_yank(q, r, YDreg);
1144 li = count_lines(q, r);
1145 status_line("Yank %d lines (%d chars) into [%c]",
1146 li, strlen(reg[YDreg]), what_reg());
1150 not_implemented(cmd);
1153 dot = bound_dot(dot); // make sure "dot" is valid
1155 #if ENABLE_FEATURE_VI_SEARCH
1157 status_line(":s expression missing delimiters");
1161 #endif /* FEATURE_VI_COLON */
1163 static void Hit_Return(void)
1168 write1("[Hit return to continue]");
1170 while ((c = get_one_char()) != '\n' && c != '\r')
1172 redraw(TRUE); // force redraw all
1175 static int next_tabstop(int col)
1177 return col + ((tabstop - 1) - (col % tabstop));
1180 //----- Synchronize the cursor to Dot --------------------------
1181 static void sync_cursor(char *d, int *row, int *col)
1183 char *beg_cur; // begin and end of "d" line
1187 beg_cur = begin_line(d); // first char of cur line
1189 if (beg_cur < screenbegin) {
1190 // "d" is before top line on screen
1191 // how many lines do we have to move
1192 cnt = count_lines(beg_cur, screenbegin);
1194 screenbegin = beg_cur;
1195 if (cnt > (rows - 1) / 2) {
1196 // we moved too many lines. put "dot" in middle of screen
1197 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1198 screenbegin = prev_line(screenbegin);
1202 char *end_scr; // begin and end of screen
1203 end_scr = end_screen(); // last char of screen
1204 if (beg_cur > end_scr) {
1205 // "d" is after bottom line on screen
1206 // how many lines do we have to move
1207 cnt = count_lines(end_scr, beg_cur);
1208 if (cnt > (rows - 1) / 2)
1209 goto sc1; // too many lines
1210 for (ro = 0; ro < cnt - 1; ro++) {
1211 // move screen begin the same amount
1212 screenbegin = next_line(screenbegin);
1213 // now, move the end of screen
1214 end_scr = next_line(end_scr);
1215 end_scr = end_line(end_scr);
1219 // "d" is on screen- find out which row
1221 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1227 // find out what col "d" is on
1229 while (tp < d) { // drive "co" to correct column
1230 if (*tp == '\n') //vda || *tp == '\0')
1233 // handle tabs like real vi
1234 if (d == tp && cmd_mode) {
1237 co = next_tabstop(co);
1239 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1240 co++; // display as ^X, use 2 columns
1246 // "co" is the column where "dot" is.
1247 // The screen has "columns" columns.
1248 // The currently displayed columns are 0+offset -- columns+ofset
1249 // |-------------------------------------------------------------|
1251 // offset | |------- columns ----------------|
1253 // If "co" is already in this range then we do not have to adjust offset
1254 // but, we do have to subtract the "offset" bias from "co".
1255 // If "co" is outside this range then we have to change "offset".
1256 // If the first char of a line is a tab the cursor will try to stay
1257 // in column 7, but we have to set offset to 0.
1259 if (co < 0 + offset) {
1262 if (co >= columns + offset) {
1263 offset = co - columns + 1;
1265 // if the first char of the line is a tab, and "dot" is sitting on it
1266 // force offset to 0.
1267 if (d == beg_cur && *d == '\t') {
1276 //----- Text Movement Routines ---------------------------------
1277 static char *begin_line(char *p) // return pointer to first char cur line
1280 p = memrchr(text, '\n', p - text);
1288 static char *end_line(char *p) // return pointer to NL of cur line line
1291 p = memchr(p, '\n', end - p - 1);
1298 static char *dollar_line(char *p) // return pointer to just before NL line
1301 // Try to stay off of the Newline
1302 if (*p == '\n' && (p - begin_line(p)) > 0)
1307 static char *prev_line(char *p) // return pointer first char prev line
1309 p = begin_line(p); // goto begining of cur line
1310 if (p[-1] == '\n' && p > text)
1311 p--; // step to prev line
1312 p = begin_line(p); // goto begining of prev line
1316 static char *next_line(char *p) // return pointer first char next line
1319 if (*p == '\n' && p < end - 1)
1320 p++; // step to next line
1324 //----- Text Information Routines ------------------------------
1325 static char *end_screen(void)
1330 // find new bottom line
1332 for (cnt = 0; cnt < rows - 2; cnt++)
1338 // count line from start to stop
1339 static int count_lines(char *start, char *stop)
1344 if (stop < start) { // start and stop are backwards- reverse them
1350 stop = end_line(stop);
1351 while (start <= stop && start <= end - 1) {
1352 start = end_line(start);
1360 static char *find_line(int li) // find begining of line #li
1364 for (q = text; li > 1; li--) {
1370 //----- Dot Movement Routines ----------------------------------
1371 static void dot_left(void)
1373 if (dot > text && dot[-1] != '\n')
1377 static void dot_right(void)
1379 if (dot < end - 1 && *dot != '\n')
1383 static void dot_begin(void)
1385 dot = begin_line(dot); // return pointer to first char cur line
1388 static void dot_end(void)
1390 dot = end_line(dot); // return pointer to last char cur line
1393 static char *move_to_col(char *p, int l)
1399 while (co < l && p < end) {
1400 if (*p == '\n') //vda || *p == '\0')
1403 co = next_tabstop(co);
1404 } else if (*p < ' ' || *p == 127) {
1405 co++; // display as ^X, use 2 columns
1413 static void dot_next(void)
1415 dot = next_line(dot);
1418 static void dot_prev(void)
1420 dot = prev_line(dot);
1423 static void dot_scroll(int cnt, int dir)
1427 for (; cnt > 0; cnt--) {
1430 // ctrl-Y scroll up one line
1431 screenbegin = prev_line(screenbegin);
1434 // ctrl-E scroll down one line
1435 screenbegin = next_line(screenbegin);
1438 // make sure "dot" stays on the screen so we dont scroll off
1439 if (dot < screenbegin)
1441 q = end_screen(); // find new bottom line
1443 dot = begin_line(q); // is dot is below bottom line?
1447 static void dot_skip_over_ws(void)
1450 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1454 static void dot_delete(void) // delete the char at 'dot'
1456 text_hole_delete(dot, dot);
1459 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1461 if (p >= end && end > text) {
1463 indicate_error('1');
1467 indicate_error('2');
1472 //----- Helper Utility Routines --------------------------------
1474 //----------------------------------------------------------------
1475 //----- Char Routines --------------------------------------------
1476 /* Chars that are part of a word-
1477 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1478 * Chars that are Not part of a word (stoppers)
1479 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1480 * Chars that are WhiteSpace
1481 * TAB NEWLINE VT FF RETURN SPACE
1482 * DO NOT COUNT NEWLINE AS WHITESPACE
1485 static char *new_screen(int ro, int co)
1490 screensize = ro * co + 8;
1491 screen = xmalloc(screensize);
1492 // initialize the new screen. assume this will be a empty file.
1494 // non-existent text[] lines start with a tilde (~).
1495 for (li = 1; li < ro - 1; li++) {
1496 screen[(li * co) + 0] = '~';
1501 #if ENABLE_FEATURE_VI_SEARCH
1502 static int mycmp(const char * s1, const char * s2, int len)
1506 i = strncmp(s1, s2, len);
1507 if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) {
1508 i = strncasecmp(s1, s2, len);
1513 // search for pattern starting at p
1514 static char *char_search(char * p, const char * pat, int dir, int range)
1516 #ifndef REGEX_SEARCH
1521 if (dir == FORWARD) {
1522 stop = end - 1; // assume range is p - end-1
1523 if (range == LIMITED)
1524 stop = next_line(p); // range is to next line
1525 for (start = p; start < stop; start++) {
1526 if (mycmp(start, pat, len) == 0) {
1530 } else if (dir == BACK) {
1531 stop = text; // assume range is text - p
1532 if (range == LIMITED)
1533 stop = prev_line(p); // range is to prev line
1534 for (start = p - len; start >= stop; start--) {
1535 if (mycmp(start, pat, len) == 0) {
1540 // pattern not found
1542 #else /* REGEX_SEARCH */
1544 struct re_pattern_buffer preg;
1548 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1554 // assume a LIMITED forward search
1562 // count the number of chars to search over, forward or backward
1566 // RANGE could be negative if we are searching backwards
1569 q = re_compile_pattern(pat, strlen(pat), &preg);
1571 // The pattern was not compiled
1572 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1573 i = 0; // return p if pattern not compiled
1583 // search for the compiled pattern, preg, in p[]
1584 // range < 0- search backward
1585 // range > 0- search forward
1587 // re_search() < 0 not found or error
1588 // re_search() > 0 index of found pattern
1589 // struct pattern char int int int struct reg
1590 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1591 i = re_search(&preg, q, size, 0, range, 0);
1594 i = 0; // return NULL if pattern not found
1597 if (dir == FORWARD) {
1603 #endif /* REGEX_SEARCH */
1605 #endif /* FEATURE_VI_SEARCH */
1607 static char *char_insert(char * p, char c) // insert the char c at 'p'
1609 if (c == 22) { // Is this an ctrl-V?
1610 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1611 p--; // backup onto ^
1612 refresh(FALSE); // show the ^
1616 file_modified++; // has the file been modified
1617 } else if (c == 27) { // Is this an ESC?
1620 end_cmd_q(); // stop adding to q
1621 last_status_cksum = 0; // force status update
1622 if ((p[-1] != '\n') && (dot > text)) {
1625 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1627 if ((p[-1] != '\n') && (dot>text)) {
1629 p = text_hole_delete(p, p); // shrink buffer 1 char
1632 // insert a char into text[]
1633 char *sp; // "save p"
1636 c = '\n'; // translate \r to \n
1637 sp = p; // remember addr of insert
1638 p = stupid_insert(p, c); // insert the char
1639 #if ENABLE_FEATURE_VI_SETOPTS
1640 if (showmatch && strchr(")]}", *sp) != NULL) {
1643 if (autoindent && c == '\n') { // auto indent the new line
1646 q = prev_line(p); // use prev line as templet
1647 for (; isblank(*q); q++) {
1648 p = stupid_insert(p, *q); // insert the char
1656 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1658 p = text_hole_make(p, 1);
1661 file_modified++; // has the file been modified
1667 static int find_range(char ** start, char ** stop, char c)
1669 char *save_dot, *p, *q, *t;
1670 int cnt, multiline = 0;
1675 if (strchr("cdy><", c)) {
1676 // these cmds operate on whole lines
1677 p = q = begin_line(p);
1678 for (cnt = 1; cnt < cmdcnt; cnt++) {
1682 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1683 // These cmds operate on char positions
1684 do_cmd(c); // execute movement cmd
1686 } else if (strchr("wW", c)) {
1687 do_cmd(c); // execute movement cmd
1688 // if we are at the next word's first char
1689 // step back one char
1690 // but check the possibilities when it is true
1691 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1692 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1693 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1694 dot--; // move back off of next word
1695 if (dot > text && *dot == '\n')
1696 dot--; // stay off NL
1698 } else if (strchr("H-k{", c)) {
1699 // these operate on multi-lines backwards
1700 q = end_line(dot); // find NL
1701 do_cmd(c); // execute movement cmd
1704 } else if (strchr("L+j}\r\n", c)) {
1705 // these operate on multi-lines forwards
1706 p = begin_line(dot);
1707 do_cmd(c); // execute movement cmd
1708 dot_end(); // find NL
1711 // nothing -- this causes any other values of c to
1712 // represent the one-character range under the
1713 // cursor. this is correct for ' ' and 'l', but
1714 // perhaps no others.
1723 // backward char movements don't include start position
1724 if (q > p && strchr("^0bBh\b\177", c)) q--;
1727 for (t = p; t <= q; t++) {
1740 static int st_test(char * p, int type, int dir, char * tested)
1750 if (type == S_BEFORE_WS) {
1752 test = ((!isspace(c)) || c == '\n');
1754 if (type == S_TO_WS) {
1756 test = ((!isspace(c)) || c == '\n');
1758 if (type == S_OVER_WS) {
1760 test = ((isspace(c)));
1762 if (type == S_END_PUNCT) {
1764 test = ((ispunct(c)));
1766 if (type == S_END_ALNUM) {
1768 test = ((isalnum(c)) || c == '_');
1774 static char *skip_thing(char * p, int linecnt, int dir, int type)
1778 while (st_test(p, type, dir, &c)) {
1779 // make sure we limit search to correct number of lines
1780 if (c == '\n' && --linecnt < 1)
1782 if (dir >= 0 && p >= end - 1)
1784 if (dir < 0 && p <= text)
1786 p += dir; // move to next char
1791 // find matching char of pair () [] {}
1792 static char *find_pair(char * p, const char c)
1799 dir = 1; // assume forward
1801 case '(': match = ')'; break;
1802 case '[': match = ']'; break;
1803 case '{': match = '}'; break;
1804 case ')': match = '('; dir = -1; break;
1805 case ']': match = '['; dir = -1; break;
1806 case '}': match = '{'; dir = -1; break;
1808 for (q = p + dir; text <= q && q < end; q += dir) {
1809 // look for match, count levels of pairs (( ))
1811 level++; // increase pair levels
1813 level--; // reduce pair level
1815 break; // found matching pair
1818 q = NULL; // indicate no match
1822 #if ENABLE_FEATURE_VI_SETOPTS
1823 // show the matching char of a pair, () [] {}
1824 static void showmatching(char *p)
1828 // we found half of a pair
1829 q = find_pair(p, *p); // get loc of matching char
1831 indicate_error('3'); // no matching char
1833 // "q" now points to matching pair
1834 save_dot = dot; // remember where we are
1835 dot = q; // go to new loc
1836 refresh(FALSE); // let the user see it
1837 mysleep(40); // give user some time
1838 dot = save_dot; // go back to old loc
1842 #endif /* FEATURE_VI_SETOPTS */
1844 // open a hole in text[]
1845 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1854 cnt = end - src; // the rest of buffer
1855 if ( ((end + size) >= (text + text_size)) // TODO: realloc here
1856 || memmove(dest, src, cnt) != dest) {
1857 status_line_bold("can't create room for new characters");
1861 memset(p, ' ', size); // clear new hole
1862 end += size; // adjust the new END
1863 file_modified++; // has the file been modified
1868 // close a hole in text[]
1869 static char *text_hole_delete(char * p, char * q) // delete "p" through "q", inclusive
1874 // move forwards, from beginning
1878 if (q < p) { // they are backward- swap them
1882 hole_size = q - p + 1;
1884 if (src < text || src > end)
1886 if (dest < text || dest >= end)
1889 goto thd_atend; // just delete the end of the buffer
1890 if (memmove(dest, src, cnt) != dest) {
1891 status_line_bold("can't delete the character");
1894 end = end - hole_size; // adjust the new END
1896 dest = end - 1; // make sure dest in below end-1
1898 dest = end = text; // keep pointers valid
1899 file_modified++; // has the file been modified
1904 // copy text into register, then delete text.
1905 // if dist <= 0, do not include, or go past, a NewLine
1907 static char *yank_delete(char * start, char * stop, int dist, int yf)
1911 // make sure start <= stop
1913 // they are backwards, reverse them
1919 // we cannot cross NL boundaries
1923 // dont go past a NewLine
1924 for (; p + 1 <= stop; p++) {
1926 stop = p; // "stop" just before NewLine
1932 #if ENABLE_FEATURE_VI_YANKMARK
1933 text_yank(start, stop, YDreg);
1935 if (yf == YANKDEL) {
1936 p = text_hole_delete(start, stop);
1941 static void show_help(void)
1943 puts("These features are available:"
1944 #if ENABLE_FEATURE_VI_SEARCH
1945 "\n\tPattern searches with / and ?"
1947 #if ENABLE_FEATURE_VI_DOT_CMD
1948 "\n\tLast command repeat with \'.\'"
1950 #if ENABLE_FEATURE_VI_YANKMARK
1951 "\n\tLine marking with 'x"
1952 "\n\tNamed buffers with \"x"
1954 #if ENABLE_FEATURE_VI_READONLY
1955 "\n\tReadonly if vi is called as \"view\""
1956 "\n\tReadonly with -R command line arg"
1958 #if ENABLE_FEATURE_VI_SET
1959 "\n\tSome colon mode commands with \':\'"
1961 #if ENABLE_FEATURE_VI_SETOPTS
1962 "\n\tSettable options with \":set\""
1964 #if ENABLE_FEATURE_VI_USE_SIGNALS
1965 "\n\tSignal catching- ^C"
1966 "\n\tJob suspend and resume with ^Z"
1968 #if ENABLE_FEATURE_VI_WIN_RESIZE
1969 "\n\tAdapt to window re-sizes"
1974 #if ENABLE_FEATURE_VI_DOT_CMD
1975 static void start_new_cmd_q(char c)
1977 // get buffer for new cmd
1978 if (!last_modifying_cmd)
1979 last_modifying_cmd = xzalloc(MAX_INPUT_LEN);
1980 // if there is a current cmd count put it in the buffer first
1982 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1983 else { // just save char c onto queue
1984 last_modifying_cmd[0] = c;
1990 static void end_cmd_q(void)
1992 #if ENABLE_FEATURE_VI_YANKMARK
1993 YDreg = 26; // go back to default Yank/Delete reg
1997 #endif /* FEATURE_VI_DOT_CMD */
1999 #if ENABLE_FEATURE_VI_YANKMARK \
2000 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2001 || ENABLE_FEATURE_VI_CRASHME
2002 static char *string_insert(char * p, char * s) // insert the string at 'p'
2007 if (text_hole_make(p, i)) {
2009 for (cnt = 0; *s != '\0'; s++) {
2013 #if ENABLE_FEATURE_VI_YANKMARK
2014 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2021 #if ENABLE_FEATURE_VI_YANKMARK
2022 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2027 if (q < p) { // they are backwards- reverse them
2034 free(t); // if already a yank register, free it
2035 t = xmalloc(cnt + 1); // get a new register
2036 memset(t, '\0', cnt + 1); // clear new text[]
2037 strncpy(t, p, cnt); // copy text[] into bufer
2042 static char what_reg(void)
2046 c = 'D'; // default to D-reg
2047 if (0 <= YDreg && YDreg <= 25)
2048 c = 'a' + (char) YDreg;
2056 static void check_context(char cmd)
2058 // A context is defined to be "modifying text"
2059 // Any modifying command establishes a new context.
2061 if (dot < context_start || dot > context_end) {
2062 if (strchr(modifying_cmds, cmd) != NULL) {
2063 // we are trying to modify text[]- make this the current context
2064 mark[27] = mark[26]; // move cur to prev
2065 mark[26] = dot; // move local to cur
2066 context_start = prev_line(prev_line(dot));
2067 context_end = next_line(next_line(dot));
2068 //loiter= start_loiter= now;
2073 static char *swap_context(char *p) // goto new context for '' command make this the current context
2077 // the current context is in mark[26]
2078 // the previous context is in mark[27]
2079 // only swap context if other context is valid
2080 if (text <= mark[27] && mark[27] <= end - 1) {
2082 mark[27] = mark[26];
2084 p = mark[26]; // where we are going- previous context
2085 context_start = prev_line(prev_line(prev_line(p)));
2086 context_end = next_line(next_line(next_line(p)));
2090 #endif /* FEATURE_VI_YANKMARK */
2092 //----- Set terminal attributes --------------------------------
2093 static void rawmode(void)
2095 tcgetattr(0, &term_orig);
2096 term_vi = term_orig;
2097 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2098 term_vi.c_iflag &= (~IXON & ~ICRNL);
2099 term_vi.c_oflag &= (~ONLCR);
2100 term_vi.c_cc[VMIN] = 1;
2101 term_vi.c_cc[VTIME] = 0;
2102 erase_char = term_vi.c_cc[VERASE];
2103 tcsetattr(0, TCSANOW, &term_vi);
2106 static void cookmode(void)
2109 tcsetattr(0, TCSANOW, &term_orig);
2112 //----- Come here when we get a window resize signal ---------
2113 #if ENABLE_FEATURE_VI_USE_SIGNALS
2114 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2116 // FIXME: do it in main loop!!!
2117 signal(SIGWINCH, winch_sig);
2118 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2119 get_terminal_width_height(0, &columns, &rows);
2120 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2121 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2123 new_screen(rows, columns); // get memory for virtual screen
2124 redraw(TRUE); // re-draw the screen
2127 //----- Come here when we get a continue signal -------------------
2128 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2130 rawmode(); // terminal to "raw"
2131 last_status_cksum = 0; // force status update
2132 redraw(TRUE); // re-draw the screen
2134 signal(SIGTSTP, suspend_sig);
2135 signal(SIGCONT, SIG_DFL);
2136 kill(my_pid, SIGCONT);
2139 //----- Come here when we get a Suspend signal -------------------
2140 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2142 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2143 clear_to_eol(); // Erase to end of line
2144 cookmode(); // terminal to "cooked"
2146 signal(SIGCONT, cont_sig);
2147 signal(SIGTSTP, SIG_DFL);
2148 kill(my_pid, SIGTSTP);
2151 //----- Come here when we get a signal ---------------------------
2152 static void catch_sig(int sig)
2154 signal(SIGINT, catch_sig);
2156 longjmp(restart, sig);
2158 #endif /* FEATURE_VI_USE_SIGNALS */
2160 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2162 struct pollfd pfd[1];
2165 pfd[0].events = POLLIN;
2166 return safe_poll(pfd, 1, hund*10) > 0;
2169 static int chars_to_parse;
2171 //----- IO Routines --------------------------------------------
2172 static char readit(void) // read (maybe cursor) key from stdin
2181 static const struct esc_cmds esccmds[] = {
2182 {"OA" , VI_K_UP }, // cursor key Up
2183 {"OB" , VI_K_DOWN }, // cursor key Down
2184 {"OC" , VI_K_RIGHT }, // Cursor Key Right
2185 {"OD" , VI_K_LEFT }, // cursor key Left
2186 {"OH" , VI_K_HOME }, // Cursor Key Home
2187 {"OF" , VI_K_END }, // Cursor Key End
2188 {"[A" , VI_K_UP }, // cursor key Up
2189 {"[B" , VI_K_DOWN }, // cursor key Down
2190 {"[C" , VI_K_RIGHT }, // Cursor Key Right
2191 {"[D" , VI_K_LEFT }, // cursor key Left
2192 {"[H" , VI_K_HOME }, // Cursor Key Home
2193 {"[F" , VI_K_END }, // Cursor Key End
2194 {"[1~" , VI_K_HOME }, // Cursor Key Home
2195 {"[2~" , VI_K_INSERT }, // Cursor Key Insert
2196 {"[3~" , VI_K_DELETE }, // Cursor Key Delete
2197 {"[4~" , VI_K_END }, // Cursor Key End
2198 {"[5~" , VI_K_PAGEUP }, // Cursor Key Page Up
2199 {"[6~" , VI_K_PAGEDOWN}, // Cursor Key Page Down
2200 {"OP" , VI_K_FUN1 }, // Function Key F1
2201 {"OQ" , VI_K_FUN2 }, // Function Key F2
2202 {"OR" , VI_K_FUN3 }, // Function Key F3
2203 {"OS" , VI_K_FUN4 }, // Function Key F4
2204 // careful: these have no terminating NUL!
2205 {"[11~", VI_K_FUN1 }, // Function Key F1
2206 {"[12~", VI_K_FUN2 }, // Function Key F2
2207 {"[13~", VI_K_FUN3 }, // Function Key F3
2208 {"[14~", VI_K_FUN4 }, // Function Key F4
2209 {"[15~", VI_K_FUN5 }, // Function Key F5
2210 {"[17~", VI_K_FUN6 }, // Function Key F6
2211 {"[18~", VI_K_FUN7 }, // Function Key F7
2212 {"[19~", VI_K_FUN8 }, // Function Key F8
2213 {"[20~", VI_K_FUN9 }, // Function Key F9
2214 {"[21~", VI_K_FUN10 }, // Function Key F10
2215 {"[23~", VI_K_FUN11 }, // Function Key F11
2216 {"[24~", VI_K_FUN12 }, // Function Key F12
2218 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2220 alarm(0); // turn alarm OFF while we wait for input
2223 // get input from User- are there already input chars in Q?
2225 // the Q is empty, wait for a typed char
2226 n = safe_read(0, readbuffer, sizeof(readbuffer));
2228 if (errno == EBADF || errno == EFAULT || errno == EINVAL
2230 editing = 0; // want to exit
2235 if (readbuffer[0] == 27) {
2236 // This is an ESC char. Is this Esc sequence?
2237 // Could be bare Esc key. See if there are any
2238 // more chars to read after the ESC. This would
2239 // be a Function or Cursor Key sequence.
2240 struct pollfd pfd[1];
2242 pfd[0].events = POLLIN;
2243 // keep reading while there are input chars, and room in buffer
2244 // for a complete ESC sequence (assuming 8 chars is enough)
2245 while (safe_poll(pfd, 1, 0) > 0 && n <= (sizeof(readbuffer) - 8)) {
2246 // read the rest of the ESC string
2247 int r = safe_read(0, readbuffer + n, sizeof(readbuffer) - n);
2255 if (c == 27 && n > 1) {
2256 // Maybe cursor or function key?
2257 const struct esc_cmds *eindex;
2259 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2260 int cnt = strnlen(eindex->seq, 4);
2263 if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
2265 c = eindex->val; // magic char value
2266 n = cnt + 1; // squeeze out the ESC sequence
2269 // defined ESC sequence not found
2273 // remove key sequence from Q
2274 chars_to_parse -= n;
2275 memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
2276 alarm(3); // we are done waiting for input, turn alarm ON
2280 //----- IO Routines --------------------------------------------
2281 static char get_one_char(void)
2285 #if ENABLE_FEATURE_VI_DOT_CMD
2287 // we are not adding to the q.
2288 // but, we may be reading from a q
2290 // there is no current q, read from STDIN
2291 c = readit(); // get the users input
2293 // there is a queue to get chars from first
2296 // the end of the q, read from STDIN
2298 ioq_start = ioq = 0;
2299 c = readit(); // get the users input
2303 // adding STDIN chars to q
2304 c = readit(); // get the users input
2305 if (last_modifying_cmd != NULL) {
2306 if (lmc_len >= MAX_INPUT_LEN - 1) {
2307 status_line_bold("last_modifying_cmd overrun");
2309 // add new char to q
2310 last_modifying_cmd[lmc_len++] = c;
2315 c = readit(); // get the users input
2316 #endif /* FEATURE_VI_DOT_CMD */
2320 // Get input line (uses "status line" area)
2321 static char *get_input_line(const char *prompt)
2323 static char *buf; // [MAX_INPUT_LEN]
2328 if (!buf) buf = xmalloc(MAX_INPUT_LEN);
2330 strcpy(buf, prompt);
2331 last_status_cksum = 0; // force status update
2332 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2333 clear_to_eol(); // clear the line
2334 write1(prompt); // write out the :, /, or ? prompt
2337 while (i < MAX_INPUT_LEN) {
2339 if (c == '\n' || c == '\r' || c == 27)
2340 break; // this is end of input
2341 if (c == erase_char || c == 8 || c == 127) {
2342 // user wants to erase prev char
2344 write1("\b \b"); // erase char on screen
2345 if (i <= 0) // user backs up before b-o-l, exit
2357 static int file_size(const char *fn) // what is the byte size of "fn"
2363 if (fn && fn[0] && stat(fn, &st_buf) == 0) // see if file exists
2364 cnt = (int) st_buf.st_size;
2368 static int file_insert(const char * fn, char *p
2369 USE_FEATURE_VI_READONLY(, int update_ro_status))
2373 struct stat statbuf;
2376 if (stat(fn, &statbuf) < 0) {
2377 status_line_bold("\"%s\" %s", fn, strerror(errno));
2380 if ((statbuf.st_mode & S_IFREG) == 0) {
2381 // This is not a regular file
2382 status_line_bold("\"%s\" Not a regular file", fn);
2385 /* // this check is done by open()
2386 if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2387 // dont have any read permissions
2388 status_line_bold("\"%s\" Not readable", fn);
2392 if (p < text || p > end) {
2393 status_line_bold("Trying to insert file outside of memory");
2397 // read file to buffer
2398 fd = open(fn, O_RDONLY);
2400 status_line_bold("\"%s\" %s", fn, strerror(errno));
2403 size = statbuf.st_size;
2404 p = text_hole_make(p, size);
2407 cnt = safe_read(fd, p, size);
2409 status_line_bold("\"%s\" %s", fn, strerror(errno));
2410 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2411 } else if (cnt < size) {
2412 // There was a partial read, shrink unused space text[]
2413 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2414 status_line_bold("cannot read all of file \"%s\"", fn);
2420 #if ENABLE_FEATURE_VI_READONLY
2421 if (update_ro_status
2422 && ((access(fn, W_OK) < 0) ||
2423 /* root will always have access()
2424 * so we check fileperms too */
2425 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2428 SET_READONLY_FILE(readonly_mode);
2435 static int file_write(char * fn, char * first, char * last)
2437 int fd, cnt, charcnt;
2440 status_line_bold("No current filename");
2444 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
2447 cnt = last - first + 1;
2448 charcnt = full_write(fd, first, cnt);
2449 if (charcnt == cnt) {
2451 //file_modified = FALSE; // the file has not been modified
2459 //----- Terminal Drawing ---------------------------------------
2460 // The terminal is made up of 'rows' line of 'columns' columns.
2461 // classically this would be 24 x 80.
2462 // screen coordinates
2468 // 23,0 ... 23,79 <- status line
2470 //----- Move the cursor to row x col (count from 0, not 1) -------
2471 static void place_cursor(int row, int col, int optimize)
2473 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2476 if (row < 0) row = 0;
2477 if (row >= rows) row = rows - 1;
2478 if (col < 0) col = 0;
2479 if (col >= columns) col = columns - 1;
2481 //----- 1. Try the standard terminal ESC sequence
2482 sprintf(cm1, CMrc, row + 1, col + 1);
2485 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2486 if (optimize && col < 16) {
2488 SZ_UP = sizeof(CMup),
2489 SZ_DN = sizeof(CMdown),
2490 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2492 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2494 int Rrow = last_row;
2495 int diff = Rrow - row;
2497 if (diff < -5 || diff > 5)
2500 //----- find the minimum # of chars to move cursor -------------
2501 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2504 // move to the correct row
2505 while (row < Rrow) {
2506 // the cursor has to move up
2510 while (row > Rrow) {
2511 // the cursor has to move down
2512 strcat(cm2, CMdown);
2516 // now move to the correct column
2517 strcat(cm2, "\r"); // start at col 0
2518 // just send out orignal source char to get to correct place
2519 screenp = &screen[row * columns]; // start of screen line
2520 strncat(cm2, screenp, col);
2522 // pick the shortest cursor motion to send out
2523 if (strlen(cm2) < strlen(cm)) {
2529 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2533 //----- Erase from cursor to end of line -----------------------
2534 static void clear_to_eol(void)
2536 write1(Ceol); // Erase from cursor to end of line
2539 //----- Erase from cursor to end of screen -----------------------
2540 static void clear_to_eos(void)
2542 write1(Ceos); // Erase from cursor to end of screen
2545 //----- Start standout mode ------------------------------------
2546 static void standout_start(void) // send "start reverse video" sequence
2548 write1(SOs); // Start reverse video mode
2551 //----- End standout mode --------------------------------------
2552 static void standout_end(void) // send "end reverse video" sequence
2554 write1(SOn); // End reverse video mode
2557 //----- Flash the screen --------------------------------------
2558 static void flash(int h)
2560 standout_start(); // send "start reverse video" sequence
2563 standout_end(); // send "end reverse video" sequence
2567 static void Indicate_Error(void)
2569 #if ENABLE_FEATURE_VI_CRASHME
2571 return; // generate a random command
2574 write1(bell); // send out a bell character
2580 //----- Screen[] Routines --------------------------------------
2581 //----- Erase the Screen[] memory ------------------------------
2582 static void screen_erase(void)
2584 memset(screen, ' ', screensize); // clear new screen
2587 static int bufsum(char *buf, int count)
2590 char *e = buf + count;
2593 sum += (unsigned char) *buf++;
2597 //----- Draw the status line at bottom of the screen -------------
2598 static void show_status_line(void)
2600 int cnt = 0, cksum = 0;
2602 // either we already have an error or status message, or we
2604 if (!have_status_msg) {
2605 cnt = format_edit_status();
2606 cksum = bufsum(status_buffer, cnt);
2608 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2609 last_status_cksum = cksum; // remember if we have seen this line
2610 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2611 write1(status_buffer);
2613 if (have_status_msg) {
2614 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2616 have_status_msg = 0;
2619 have_status_msg = 0;
2621 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2626 //----- format the status buffer, the bottom line of screen ------
2627 // format status buffer, with STANDOUT mode
2628 static void status_line_bold(const char *format, ...)
2632 va_start(args, format);
2633 strcpy(status_buffer, SOs); // Terminal standout mode on
2634 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2635 strcat(status_buffer, SOn); // Terminal standout mode off
2638 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2641 // format status buffer
2642 static void status_line(const char *format, ...)
2646 va_start(args, format);
2647 vsprintf(status_buffer, format, args);
2650 have_status_msg = 1;
2653 // copy s to buf, convert unprintable
2654 static void print_literal(char *buf, const char *s)
2667 c_is_no_print = (c & 0x80) && !Isprint(c);
2668 if (c_is_no_print) {
2672 if (c < ' ' || c == 127) {
2685 if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
2690 static void not_implemented(const char *s)
2692 char buf[MAX_INPUT_LEN];
2694 print_literal(buf, s);
2695 status_line_bold("\'%s\' is not implemented", buf);
2698 // show file status on status line
2699 static int format_edit_status(void)
2702 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2703 int cur, percent, ret, trunc_at;
2705 // file_modified is now a counter rather than a flag. this
2706 // helps reduce the amount of line counting we need to do.
2707 // (this will cause a mis-reporting of modified status
2708 // once every MAXINT editing operations.)
2710 // it would be nice to do a similar optimization here -- if
2711 // we haven't done a motion that could have changed which line
2712 // we're on, then we shouldn't have to do this count_lines()
2713 cur = count_lines(text, dot);
2715 // reduce counting -- the total lines can't have
2716 // changed if we haven't done any edits.
2717 if (file_modified != last_file_modified) {
2718 tot = cur + count_lines(dot, end - 1) - 1;
2719 last_file_modified = file_modified;
2722 // current line percent
2723 // ------------- ~~ ----------
2726 percent = (100 * cur) / tot;
2732 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2733 columns : STATUS_BUFFER_LEN-1;
2735 ret = snprintf(status_buffer, trunc_at+1,
2736 #if ENABLE_FEATURE_VI_READONLY
2737 "%c %s%s%s %d/%d %d%%",
2739 "%c %s%s %d/%d %d%%",
2741 cmd_mode_indicator[cmd_mode & 3],
2742 (current_filename != NULL ? current_filename : "No file"),
2743 #if ENABLE_FEATURE_VI_READONLY
2744 (readonly_mode ? " [Readonly]" : ""),
2746 (file_modified ? " [Modified]" : ""),
2749 if (ret >= 0 && ret < trunc_at)
2750 return ret; /* it all fit */
2752 return trunc_at; /* had to truncate */
2755 //----- Force refresh of all Lines -----------------------------
2756 static void redraw(int full_screen)
2758 place_cursor(0, 0, FALSE); // put cursor in correct place
2759 clear_to_eos(); // tel terminal to erase display
2760 screen_erase(); // erase the internal screen buffer
2761 last_status_cksum = 0; // force status update
2762 refresh(full_screen); // this will redraw the entire display
2766 //----- Format a text[] line into a buffer ---------------------
2767 static char* format_line(char *src /*, int li*/)
2772 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2774 c = '~'; // char in col 0 in non-existent lines is '~'
2776 while (co < columns + tabstop) {
2777 // have we gone past the end?
2782 if ((c & 0x80) && !Isprint(c)) {
2785 if (c < ' ' || c == 0x7f) {
2789 while ((co % tabstop) != (tabstop - 1)) {
2797 c += '@'; // Ctrl-X -> 'X'
2802 // discard scrolled-off-to-the-left portion,
2803 // in tabstop-sized pieces
2804 if (ofs >= tabstop && co >= tabstop) {
2805 memmove(dest, dest + tabstop, co);
2812 // check "short line, gigantic offset" case
2815 // discard last scrolled off part
2818 // fill the rest with spaces
2820 memset(&dest[co], ' ', columns - co);
2824 //----- Refresh the changed screen lines -----------------------
2825 // Copy the source line from text[] into the buffer and note
2826 // if the current screenline is different from the new buffer.
2827 // If they differ then that line needs redrawing on the terminal.
2829 static void refresh(int full_screen)
2831 static int old_offset;
2834 char *tp, *sp; // pointer into text[] and screen[]
2836 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2837 int c = columns, r = rows;
2838 get_terminal_width_height(0, &columns, &rows);
2839 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2840 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2841 full_screen |= (c - columns) | (r - rows);
2843 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2844 tp = screenbegin; // index into text[] of top line
2846 // compare text[] to screen[] and mark screen[] lines that need updating
2847 for (li = 0; li < rows - 1; li++) {
2848 int cs, ce; // column start & end
2850 // format current text line
2851 out_buf = format_line(tp /*, li*/);
2853 // skip to the end of the current text[] line
2855 char *t = memchr(tp, '\n', end - tp);
2856 if (!t) t = end - 1;
2860 // see if there are any changes between vitual screen and out_buf
2861 changed = FALSE; // assume no change
2864 sp = &screen[li * columns]; // start of screen line
2866 // force re-draw of every single column from 0 - columns-1
2869 // compare newly formatted buffer with virtual screen
2870 // look forward for first difference between buf and screen
2871 for (; cs <= ce; cs++) {
2872 if (out_buf[cs] != sp[cs]) {
2873 changed = TRUE; // mark for redraw
2878 // look backward for last difference between out_buf and screen
2879 for (; ce >= cs; ce--) {
2880 if (out_buf[ce] != sp[ce]) {
2881 changed = TRUE; // mark for redraw
2885 // now, cs is index of first diff, and ce is index of last diff
2887 // if horz offset has changed, force a redraw
2888 if (offset != old_offset) {
2893 // make a sanity check of columns indexes
2895 if (ce > columns - 1) ce = columns - 1;
2896 if (cs > ce) { cs = 0; ce = columns - 1; }
2897 // is there a change between vitual screen and out_buf
2899 // copy changed part of buffer to virtual screen
2900 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2902 // move cursor to column of first change
2903 //if (offset != old_offset) {
2904 // // place_cursor is still too stupid
2905 // // to handle offsets correctly
2906 // place_cursor(li, cs, FALSE);
2908 place_cursor(li, cs, TRUE);
2911 // write line out to terminal
2912 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2916 place_cursor(crow, ccol, TRUE);
2918 old_offset = offset;
2921 //---------------------------------------------------------------------
2922 //----- the Ascii Chart -----------------------------------------------
2924 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2925 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2926 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2927 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2928 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2929 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2930 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2931 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2932 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2933 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2934 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2935 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2936 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2937 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2938 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2939 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2940 //---------------------------------------------------------------------
2942 //----- Execute a Vi Command -----------------------------------
2943 static void do_cmd(char c)
2945 const char *msg = msg; // for compiler
2946 char c1, *p, *q, *save_dot;
2948 int dir = dir; // for compiler
2951 // c1 = c; // quiet the compiler
2952 // cnt = yf = 0; // quiet the compiler
2953 // msg = p = q = save_dot = buf; // quiet the compiler
2954 memset(buf, '\0', 12);
2958 /* if this is a cursor key, skip these checks */
2971 if (cmd_mode == 2) {
2972 // flip-flop Insert/Replace mode
2973 if (c == VI_K_INSERT)
2975 // we are 'R'eplacing the current *dot with new char
2977 // don't Replace past E-o-l
2978 cmd_mode = 1; // convert to insert
2980 if (1 <= c || Isprint(c)) {
2982 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2983 dot = char_insert(dot, c); // insert new char
2988 if (cmd_mode == 1) {
2989 // hitting "Insert" twice means "R" replace mode
2990 if (c == VI_K_INSERT) goto dc5;
2991 // insert the char c at "dot"
2992 if (1 <= c || Isprint(c)) {
2993 dot = char_insert(dot, c);
3008 #if ENABLE_FEATURE_VI_CRASHME
3009 case 0x14: // dc4 ctrl-T
3010 crashme = (crashme == 0) ? 1 : 0;
3039 //case 'u': // u- FIXME- there is no undo
3041 default: // unrecognised command
3049 not_implemented(buf);
3050 end_cmd_q(); // stop adding to q
3051 case 0x00: // nul- ignore
3053 case 2: // ctrl-B scroll up full screen
3054 case VI_K_PAGEUP: // Cursor Key Page Up
3055 dot_scroll(rows - 2, -1);
3057 #if ENABLE_FEATURE_VI_USE_SIGNALS
3058 case 0x03: // ctrl-C interrupt
3059 longjmp(restart, 1);
3061 case 26: // ctrl-Z suspend
3062 suspend_sig(SIGTSTP);
3065 case 4: // ctrl-D scroll down half screen
3066 dot_scroll((rows - 2) / 2, 1);
3068 case 5: // ctrl-E scroll down one line
3071 case 6: // ctrl-F scroll down full screen
3072 case VI_K_PAGEDOWN: // Cursor Key Page Down
3073 dot_scroll(rows - 2, 1);
3075 case 7: // ctrl-G show current status
3076 last_status_cksum = 0; // force status update
3078 case 'h': // h- move left
3079 case VI_K_LEFT: // cursor key Left
3080 case 8: // ctrl-H- move left (This may be ERASE char)
3081 case 0x7f: // DEL- move left (This may be ERASE char)
3087 case 10: // Newline ^J
3088 case 'j': // j- goto next line, same col
3089 case VI_K_DOWN: // cursor key Down
3093 dot_next(); // go to next B-o-l
3094 dot = move_to_col(dot, ccol + offset); // try stay in same col
3096 case 12: // ctrl-L force redraw whole screen
3097 case 18: // ctrl-R force redraw
3098 place_cursor(0, 0, FALSE); // put cursor in correct place
3099 clear_to_eos(); // tel terminal to erase display
3101 screen_erase(); // erase the internal screen buffer
3102 last_status_cksum = 0; // force status update
3103 refresh(TRUE); // this will redraw the entire display
3105 case 13: // Carriage Return ^M
3106 case '+': // +- goto next line
3113 case 21: // ctrl-U scroll up half screen
3114 dot_scroll((rows - 2) / 2, -1);
3116 case 25: // ctrl-Y scroll up one line
3122 cmd_mode = 0; // stop insrting
3124 last_status_cksum = 0; // force status update
3126 case ' ': // move right
3127 case 'l': // move right
3128 case VI_K_RIGHT: // Cursor Key Right
3134 #if ENABLE_FEATURE_VI_YANKMARK
3135 case '"': // "- name a register to use for Delete/Yank
3136 c1 = get_one_char();
3144 case '\'': // '- goto a specific mark
3145 c1 = get_one_char();
3150 q = mark[(unsigned char) c1];
3151 if (text <= q && q < end) {
3153 dot_begin(); // go to B-o-l
3156 } else if (c1 == '\'') { // goto previous context
3157 dot = swap_context(dot); // swap current and previous context
3158 dot_begin(); // go to B-o-l
3164 case 'm': // m- Mark a line
3165 // this is really stupid. If there are any inserts or deletes
3166 // between text[0] and dot then this mark will not point to the
3167 // correct location! It could be off by many lines!
3168 // Well..., at least its quick and dirty.
3169 c1 = get_one_char();
3173 // remember the line
3174 mark[(int) c1] = dot;
3179 case 'P': // P- Put register before
3180 case 'p': // p- put register after
3183 status_line_bold("Nothing in register %c", what_reg());
3186 // are we putting whole lines or strings
3187 if (strchr(p, '\n') != NULL) {
3189 dot_begin(); // putting lines- Put above
3192 // are we putting after very last line?
3193 if (end_line(dot) == (end - 1)) {
3194 dot = end; // force dot to end of text[]
3196 dot_next(); // next line, then put before
3201 dot_right(); // move to right, can move to NL
3203 dot = string_insert(dot, p); // insert the string
3204 end_cmd_q(); // stop adding to q
3206 case 'U': // U- Undo; replace current line with original version
3207 if (reg[Ureg] != 0) {
3208 p = begin_line(dot);
3210 p = text_hole_delete(p, q); // delete cur line
3211 p = string_insert(p, reg[Ureg]); // insert orig line
3216 #endif /* FEATURE_VI_YANKMARK */
3217 case '$': // $- goto end of line
3218 case VI_K_END: // Cursor Key End
3222 dot = end_line(dot);
3224 case '%': // %- find matching char of pair () [] {}
3225 for (q = dot; q < end && *q != '\n'; q++) {
3226 if (strchr("()[]{}", *q) != NULL) {
3227 // we found half of a pair
3228 p = find_pair(q, *q);
3240 case 'f': // f- forward to a user specified char
3241 last_forward_char = get_one_char(); // get the search char
3243 // dont separate these two commands. 'f' depends on ';'
3245 //**** fall through to ... ';'
3246 case ';': // ;- look at rest of line for last forward char
3250 if (last_forward_char == 0)
3253 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3256 if (*q == last_forward_char)
3259 case ',': // repeat latest 'f' in opposite direction
3263 if (last_forward_char == 0)
3266 while (q >= text && *q != '\n' && *q != last_forward_char) {
3269 if (q >= text && *q == last_forward_char)
3273 case '-': // -- goto prev line
3280 #if ENABLE_FEATURE_VI_DOT_CMD
3281 case '.': // .- repeat the last modifying command
3282 // Stuff the last_modifying_cmd back into stdin
3283 // and let it be re-executed.
3284 if (last_modifying_cmd != NULL && lmc_len > 0) {
3285 last_modifying_cmd[lmc_len] = 0;
3286 ioq = ioq_start = xstrdup(last_modifying_cmd);
3290 #if ENABLE_FEATURE_VI_SEARCH
3291 case '?': // /- search for a pattern
3292 case '/': // /- search for a pattern
3295 q = get_input_line(buf); // get input line- use "status line"
3296 if (q[0] && !q[1]) {
3297 if (last_search_pattern[0])
3298 last_search_pattern[0] = c;
3299 goto dc3; // if no pat re-use old pat
3301 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3302 // there is a new pat
3303 free(last_search_pattern);
3304 last_search_pattern = xstrdup(q);
3305 goto dc3; // now find the pattern
3307 // user changed mind and erased the "/"- do nothing
3309 case 'N': // N- backward search for last pattern
3313 dir = BACK; // assume BACKWARD search
3315 if (last_search_pattern[0] == '?') {
3319 goto dc4; // now search for pattern
3321 case 'n': // n- repeat search for last pattern
3322 // search rest of text[] starting at next char
3323 // if search fails return orignal "p" not the "p+1" address
3328 if (last_search_pattern == 0) {
3329 msg = "No previous regular expression";
3332 if (last_search_pattern[0] == '/') {
3333 dir = FORWARD; // assume FORWARD search
3336 if (last_search_pattern[0] == '?') {
3341 q = char_search(p, last_search_pattern + 1, dir, FULL);
3343 dot = q; // good search, update "dot"
3347 // no pattern found between "dot" and "end"- continue at top
3352 q = char_search(p, last_search_pattern + 1, dir, FULL);
3353 if (q != NULL) { // found something
3354 dot = q; // found new pattern- goto it
3355 msg = "search hit BOTTOM, continuing at TOP";
3357 msg = "search hit TOP, continuing at BOTTOM";
3360 msg = "Pattern not found";
3364 status_line_bold("%s", msg);
3366 case '{': // {- move backward paragraph
3367 q = char_search(dot, "\n\n", BACK, FULL);
3368 if (q != NULL) { // found blank line
3369 dot = next_line(q); // move to next blank line
3372 case '}': // }- move forward paragraph
3373 q = char_search(dot, "\n\n", FORWARD, FULL);
3374 if (q != NULL) { // found blank line
3375 dot = next_line(q); // move to next blank line
3378 #endif /* FEATURE_VI_SEARCH */
3379 case '0': // 0- goto begining of line
3389 if (c == '0' && cmdcnt < 1) {
3390 dot_begin(); // this was a standalone zero
3392 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3395 case ':': // :- the colon mode commands
3396 p = get_input_line(":"); // get input line- use "status line"
3397 #if ENABLE_FEATURE_VI_COLON
3398 colon(p); // execute the command
3401 p++; // move past the ':'
3405 if (strncasecmp(p, "quit", cnt) == 0
3406 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3408 if (file_modified && p[1] != '!') {
3409 status_line_bold("No write since last change (:quit! overrides)");
3413 } else if (strncasecmp(p, "write", cnt) == 0
3414 || strncasecmp(p, "wq", cnt) == 0
3415 || strncasecmp(p, "wn", cnt) == 0
3416 || strncasecmp(p, "x", cnt) == 0
3418 cnt = file_write(current_filename, text, end - 1);
3421 status_line_bold("Write error: %s", strerror(errno));
3424 last_file_modified = -1;
3425 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3426 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3427 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3432 } else if (strncasecmp(p, "file", cnt) == 0) {
3433 last_status_cksum = 0; // force status update
3434 } else if (sscanf(p, "%d", &j) > 0) {
3435 dot = find_line(j); // go to line # j
3437 } else { // unrecognised cmd
3440 #endif /* !FEATURE_VI_COLON */
3442 case '<': // <- Left shift something
3443 case '>': // >- Right shift something
3444 cnt = count_lines(text, dot); // remember what line we are on
3445 c1 = get_one_char(); // get the type of thing to delete
3446 find_range(&p, &q, c1);
3447 yank_delete(p, q, 1, YANKONLY); // save copy before change
3450 i = count_lines(p, q); // # of lines we are shifting
3451 for ( ; i > 0; i--, p = next_line(p)) {
3453 // shift left- remove tab or 8 spaces
3455 // shrink buffer 1 char
3456 text_hole_delete(p, p);
3457 } else if (*p == ' ') {
3458 // we should be calculating columns, not just SPACE
3459 for (j = 0; *p == ' ' && j < tabstop; j++) {
3460 text_hole_delete(p, p);
3463 } else if (c == '>') {
3464 // shift right -- add tab or 8 spaces
3465 char_insert(p, '\t');
3468 dot = find_line(cnt); // what line were we on
3470 end_cmd_q(); // stop adding to q
3472 case 'A': // A- append at e-o-l
3473 dot_end(); // go to e-o-l
3474 //**** fall through to ... 'a'
3475 case 'a': // a- append after current char
3480 case 'B': // B- back a blank-delimited Word
3481 case 'E': // E- end of a blank-delimited word
3482 case 'W': // W- forward a blank-delimited word
3489 if (c == 'W' || isspace(dot[dir])) {
3490 dot = skip_thing(dot, 1, dir, S_TO_WS);
3491 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3494 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3496 case 'C': // C- Change to e-o-l
3497 case 'D': // D- delete to e-o-l
3499 dot = dollar_line(dot); // move to before NL
3500 // copy text into a register and delete
3501 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3503 goto dc_i; // start inserting
3504 #if ENABLE_FEATURE_VI_DOT_CMD
3506 end_cmd_q(); // stop adding to q
3509 case 'g': // 'gg' goto a line number (from vim)
3510 // (default to first line in file)
3511 c1 = get_one_char();
3516 not_implemented(buf);
3522 case 'G': // G- goto to a line number (default= E-O-F)
3523 dot = end - 1; // assume E-O-F
3525 dot = find_line(cmdcnt); // what line is #cmdcnt
3529 case 'H': // H- goto top line on screen
3531 if (cmdcnt > (rows - 1)) {
3532 cmdcnt = (rows - 1);
3539 case 'I': // I- insert before first non-blank
3542 //**** fall through to ... 'i'
3543 case 'i': // i- insert before current char
3544 case VI_K_INSERT: // Cursor Key Insert
3546 cmd_mode = 1; // start insrting
3548 case 'J': // J- join current and next lines together
3552 dot_end(); // move to NL
3553 if (dot < end - 1) { // make sure not last char in text[]
3554 *dot++ = ' '; // replace NL with space
3556 while (isblank(*dot)) { // delete leading WS
3560 end_cmd_q(); // stop adding to q
3562 case 'L': // L- goto bottom line on screen
3564 if (cmdcnt > (rows - 1)) {
3565 cmdcnt = (rows - 1);
3573 case 'M': // M- goto middle line on screen
3575 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3576 dot = next_line(dot);
3578 case 'O': // O- open a empty line above
3580 p = begin_line(dot);
3581 if (p[-1] == '\n') {
3583 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3585 dot = char_insert(dot, '\n');
3588 dot = char_insert(dot, '\n'); // i\n ESC
3593 case 'R': // R- continuous Replace char
3600 case 'X': // X- delete char before dot
3601 case 'x': // x- delete the current char
3602 case 's': // s- substitute the current char
3609 if (dot[dir] != '\n') {
3611 dot--; // delete prev char
3612 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3615 goto dc_i; // start insrting
3616 end_cmd_q(); // stop adding to q
3618 case 'Z': // Z- if modified, {write}; exit
3619 // ZZ means to save file (if necessary), then exit
3620 c1 = get_one_char();
3625 if (file_modified) {
3626 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3627 status_line_bold("\"%s\" File is read only", current_filename);
3630 cnt = file_write(current_filename, text, end - 1);
3633 status_line_bold("Write error: %s", strerror(errno));
3634 } else if (cnt == (end - 1 - text + 1)) {
3641 case '^': // ^- move to first non-blank on line
3645 case 'b': // b- back a word
3646 case 'e': // e- end of word
3653 if ((dot + dir) < text || (dot + dir) > end - 1)
3656 if (isspace(*dot)) {
3657 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3659 if (isalnum(*dot) || *dot == '_') {
3660 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3661 } else if (ispunct(*dot)) {
3662 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3665 case 'c': // c- change something
3666 case 'd': // d- delete something
3667 #if ENABLE_FEATURE_VI_YANKMARK
3668 case 'y': // y- yank something
3669 case 'Y': // Y- Yank a line
3672 int yf, ml, whole = 0;
3673 yf = YANKDEL; // assume either "c" or "d"
3674 #if ENABLE_FEATURE_VI_YANKMARK
3675 if (c == 'y' || c == 'Y')
3680 c1 = get_one_char(); // get the type of thing to delete
3681 // determine range, and whether it spans lines
3682 ml = find_range(&p, &q, c1);
3683 if (c1 == 27) { // ESC- user changed mind and wants out
3684 c = c1 = 27; // Escape- do nothing
3685 } else if (strchr("wW", c1)) {
3687 // don't include trailing WS as part of word
3688 while (isblank(*q)) {
3689 if (q <= text || q[-1] == '\n')
3694 dot = yank_delete(p, q, ml, yf); // delete word
3695 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3696 // partial line copy text into a register and delete
3697 dot = yank_delete(p, q, ml, yf); // delete word
3698 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3699 // whole line copy text into a register and delete
3700 dot = yank_delete(p, q, ml, yf); // delete lines
3703 // could not recognize object
3704 c = c1 = 27; // error-
3710 dot = char_insert(dot, '\n');
3711 // on the last line of file don't move to prev line
3712 if (whole && dot != (end-1)) {
3715 } else if (c == 'd') {
3721 // if CHANGING, not deleting, start inserting after the delete
3723 strcpy(buf, "Change");
3724 goto dc_i; // start inserting
3727 strcpy(buf, "Delete");
3729 #if ENABLE_FEATURE_VI_YANKMARK
3730 if (c == 'y' || c == 'Y') {
3731 strcpy(buf, "Yank");
3735 for (cnt = 0; p <= q; p++) {
3739 status_line("%s %d lines (%d chars) using [%c]",
3740 buf, cnt, strlen(reg[YDreg]), what_reg());
3742 end_cmd_q(); // stop adding to q
3746 case 'k': // k- goto prev line, same col
3747 case VI_K_UP: // cursor key Up
3752 dot = move_to_col(dot, ccol + offset); // try stay in same col
3754 case 'r': // r- replace the current char with user input
3755 c1 = get_one_char(); // get the replacement char
3758 file_modified++; // has the file been modified
3760 end_cmd_q(); // stop adding to q
3762 case 't': // t- move to char prior to next x
3763 last_forward_char = get_one_char();
3765 if (*dot == last_forward_char)
3767 last_forward_char= 0;
3769 case 'w': // w- forward a word
3773 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3774 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3775 } else if (ispunct(*dot)) { // we are on PUNCT
3776 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3779 dot++; // move over word
3780 if (isspace(*dot)) {
3781 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3785 c1 = get_one_char(); // get the replacement char
3788 cnt = (rows - 2) / 2; // put dot at center
3790 cnt = rows - 2; // put dot at bottom
3791 screenbegin = begin_line(dot); // start dot at top
3792 dot_scroll(cnt, -1);
3794 case '|': // |- move to column "cmdcnt"
3795 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3797 case '~': // ~- flip the case of letters a-z -> A-Z
3801 if (islower(*dot)) {
3802 *dot = toupper(*dot);
3803 file_modified++; // has the file been modified
3804 } else if (isupper(*dot)) {
3805 *dot = tolower(*dot);
3806 file_modified++; // has the file been modified
3809 end_cmd_q(); // stop adding to q
3811 //----- The Cursor and Function Keys -----------------------------
3812 case VI_K_HOME: // Cursor Key Home
3815 // The Fn keys could point to do_macro which could translate them
3816 case VI_K_FUN1: // Function Key F1
3817 case VI_K_FUN2: // Function Key F2
3818 case VI_K_FUN3: // Function Key F3
3819 case VI_K_FUN4: // Function Key F4
3820 case VI_K_FUN5: // Function Key F5
3821 case VI_K_FUN6: // Function Key F6
3822 case VI_K_FUN7: // Function Key F7
3823 case VI_K_FUN8: // Function Key F8
3824 case VI_K_FUN9: // Function Key F9
3825 case VI_K_FUN10: // Function Key F10
3826 case VI_K_FUN11: // Function Key F11
3827 case VI_K_FUN12: // Function Key F12
3832 // if text[] just became empty, add back an empty line
3834 char_insert(text, '\n'); // start empty buf with dummy line
3837 // it is OK for dot to exactly equal to end, otherwise check dot validity
3839 dot = bound_dot(dot); // make sure "dot" is valid
3841 #if ENABLE_FEATURE_VI_YANKMARK
3842 check_context(c); // update the current context
3846 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3847 cnt = dot - begin_line(dot);
3848 // Try to stay off of the Newline
3849 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3853 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3854 #if ENABLE_FEATURE_VI_CRASHME
3855 static int totalcmds = 0;
3856 static int Mp = 85; // Movement command Probability
3857 static int Np = 90; // Non-movement command Probability
3858 static int Dp = 96; // Delete command Probability
3859 static int Ip = 97; // Insert command Probability
3860 static int Yp = 98; // Yank command Probability
3861 static int Pp = 99; // Put command Probability
3862 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3863 static const char chars[20] = "\t012345 abcdABCD-=.$";
3864 static const char *const words[20] = {
3865 "this", "is", "a", "test",
3866 "broadcast", "the", "emergency", "of",
3867 "system", "quick", "brown", "fox",
3868 "jumped", "over", "lazy", "dogs",
3869 "back", "January", "Febuary", "March"
3871 static const char *const lines[20] = {
3872 "You should have received a copy of the GNU General Public License\n",
3873 "char c, cm, *cmd, *cmd1;\n",
3874 "generate a command by percentages\n",
3875 "Numbers may be typed as a prefix to some commands.\n",
3876 "Quit, discarding changes!\n",
3877 "Forced write, if permission originally not valid.\n",
3878 "In general, any ex or ed command (such as substitute or delete).\n",
3879 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3880 "Please get w/ me and I will go over it with you.\n",
3881 "The following is a list of scheduled, committed changes.\n",
3882 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3883 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3884 "Any question about transactions please contact Sterling Huxley.\n",
3885 "I will try to get back to you by Friday, December 31.\n",
3886 "This Change will be implemented on Friday.\n",
3887 "Let me know if you have problems accessing this;\n",
3888 "Sterling Huxley recently added you to the access list.\n",
3889 "Would you like to go to lunch?\n",
3890 "The last command will be automatically run.\n",
3891 "This is too much english for a computer geek.\n",
3893 static char *multilines[20] = {
3894 "You should have received a copy of the GNU General Public License\n",
3895 "char c, cm, *cmd, *cmd1;\n",
3896 "generate a command by percentages\n",
3897 "Numbers may be typed as a prefix to some commands.\n",
3898 "Quit, discarding changes!\n",
3899 "Forced write, if permission originally not valid.\n",
3900 "In general, any ex or ed command (such as substitute or delete).\n",
3901 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3902 "Please get w/ me and I will go over it with you.\n",
3903 "The following is a list of scheduled, committed changes.\n",
3904 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3905 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3906 "Any question about transactions please contact Sterling Huxley.\n",
3907 "I will try to get back to you by Friday, December 31.\n",
3908 "This Change will be implemented on Friday.\n",
3909 "Let me know if you have problems accessing this;\n",
3910 "Sterling Huxley recently added you to the access list.\n",
3911 "Would you like to go to lunch?\n",
3912 "The last command will be automatically run.\n",
3913 "This is too much english for a computer geek.\n",
3916 // create a random command to execute
3917 static void crash_dummy()
3919 static int sleeptime; // how long to pause between commands
3920 char c, cm, *cmd, *cmd1;
3921 int i, cnt, thing, rbi, startrbi, percent;
3923 // "dot" movement commands
3924 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3926 // is there already a command running?
3927 if (chars_to_parse > 0)
3931 sleeptime = 0; // how long to pause between commands
3932 memset(readbuffer, '\0', sizeof(readbuffer));
3933 // generate a command by percentages
3934 percent = (int) lrand48() % 100; // get a number from 0-99
3935 if (percent < Mp) { // Movement commands
3936 // available commands
3939 } else if (percent < Np) { // non-movement commands
3940 cmd = "mz<>\'\""; // available commands
3942 } else if (percent < Dp) { // Delete commands
3943 cmd = "dx"; // available commands
3945 } else if (percent < Ip) { // Inset commands
3946 cmd = "iIaAsrJ"; // available commands
3948 } else if (percent < Yp) { // Yank commands
3949 cmd = "yY"; // available commands
3951 } else if (percent < Pp) { // Put commands
3952 cmd = "pP"; // available commands
3955 // We do not know how to handle this command, try again
3959 // randomly pick one of the available cmds from "cmd[]"
3960 i = (int) lrand48() % strlen(cmd);
3962 if (strchr(":\024", cm))
3963 goto cd0; // dont allow colon or ctrl-T commands
3964 readbuffer[rbi++] = cm; // put cmd into input buffer
3966 // now we have the command-
3967 // there are 1, 2, and multi char commands
3968 // find out which and generate the rest of command as necessary
3969 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3970 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3971 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3972 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3974 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3976 readbuffer[rbi++] = c; // add movement to input buffer
3978 if (strchr("iIaAsc", cm)) { // multi-char commands
3980 // change some thing
3981 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3983 readbuffer[rbi++] = c; // add movement to input buffer
3985 thing = (int) lrand48() % 4; // what thing to insert
3986 cnt = (int) lrand48() % 10; // how many to insert
3987 for (i = 0; i < cnt; i++) {
3988 if (thing == 0) { // insert chars
3989 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3990 } else if (thing == 1) { // insert words
3991 strcat(readbuffer, words[(int) lrand48() % 20]);
3992 strcat(readbuffer, " ");
3993 sleeptime = 0; // how fast to type
3994 } else if (thing == 2) { // insert lines
3995 strcat(readbuffer, lines[(int) lrand48() % 20]);
3996 sleeptime = 0; // how fast to type
3997 } else { // insert multi-lines
3998 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3999 sleeptime = 0; // how fast to type
4002 strcat(readbuffer, "\033");
4004 chars_to_parse = strlen(readbuffer);
4008 mysleep(sleeptime); // sleep 1/100 sec
4011 // test to see if there are any errors
4012 static void crash_test()
4014 static time_t oldtim;
4021 strcat(msg, "end<text ");
4023 if (end > textend) {
4024 strcat(msg, "end>textend ");
4027 strcat(msg, "dot<text ");
4030 strcat(msg, "dot>end ");
4032 if (screenbegin < text) {
4033 strcat(msg, "screenbegin<text ");
4035 if (screenbegin > end - 1) {
4036 strcat(msg, "screenbegin>end-1 ");
4041 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4042 totalcmds, last_input_char, msg, SOs, SOn);
4044 while (safe_read(0, d, 1) > 0) {
4045 if (d[0] == '\n' || d[0] == '\r')
4051 if (tim >= (oldtim + 3)) {
4052 sprintf(status_buffer,
4053 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4054 totalcmds, M, N, I, D, Y, P, U, end - text + 1);