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 sigjmp_buf restart; // catch_sig()
206 struct termios term_orig, term_vi; // remember what the cooked mode was
207 #if ENABLE_FEATURE_VI_COLON
208 char *initial_cmds[3]; // currently 2 entries, NULL terminated
210 // Should be just enough to hold a key sequence,
211 // but CRASME mode uses it as generated command buffer too
212 #if ENABLE_FEATURE_VI_CRASHME
213 char readbuffer[128];
218 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
220 #define G (*ptr_to_globals)
221 #define text (G.text )
222 #define text_size (G.text_size )
226 #define YDreg (G.YDreg )
227 #define Ureg (G.Ureg )
228 #define mark (G.mark )
229 #define context_start (G.context_start )
230 #define context_end (G.context_end )
231 #define restart (G.restart )
232 #define term_orig (G.term_orig )
233 #define term_vi (G.term_vi )
234 #define initial_cmds (G.initial_cmds )
235 #define readbuffer (G.readbuffer )
236 #define scr_out_buf (G.scr_out_buf )
237 #define INIT_G() do { \
238 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
241 static int init_text_buffer(char *); // init from file or create new
242 static void edit_file(char *); // edit one file
243 static void do_cmd(char); // execute a command
244 static int next_tabstop(int);
245 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
246 static char *begin_line(char *); // return pointer to cur line B-o-l
247 static char *end_line(char *); // return pointer to cur line E-o-l
248 static char *prev_line(char *); // return pointer to prev line B-o-l
249 static char *next_line(char *); // return pointer to next line B-o-l
250 static char *end_screen(void); // get pointer to last char on screen
251 static int count_lines(char *, char *); // count line from start to stop
252 static char *find_line(int); // find begining of line #li
253 static char *move_to_col(char *, int); // move "p" to column l
254 static void dot_left(void); // move dot left- dont leave line
255 static void dot_right(void); // move dot right- dont leave line
256 static void dot_begin(void); // move dot to B-o-l
257 static void dot_end(void); // move dot to E-o-l
258 static void dot_next(void); // move dot to next line B-o-l
259 static void dot_prev(void); // move dot to prev line B-o-l
260 static void dot_scroll(int, int); // move the screen up or down
261 static void dot_skip_over_ws(void); // move dot pat WS
262 static void dot_delete(void); // delete the char at 'dot'
263 static char *bound_dot(char *); // make sure text[0] <= P < "end"
264 static char *new_screen(int, int); // malloc virtual screen memory
265 static char *char_insert(char *, char); // insert the char c at 'p'
266 static char *stupid_insert(char *, char); // stupidly insert the char c at 'p'
267 static int find_range(char **, char **, char); // return pointers for an object
268 static int st_test(char *, int, int, char *); // helper for skip_thing()
269 static char *skip_thing(char *, int, int, int); // skip some object
270 static char *find_pair(char *, char); // find matching pair () [] {}
271 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
272 static char *text_hole_make(char *, int); // at "p", make a 'size' byte hole
273 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
274 static void show_help(void); // display some help info
275 static void rawmode(void); // set "raw" mode on tty
276 static void cookmode(void); // return to "cooked" mode on tty
277 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
278 static int mysleep(int);
279 static char readit(void); // read (maybe cursor) key from stdin
280 static char get_one_char(void); // read 1 char from stdin
281 static int file_size(const char *); // what is the byte size of "fn"
282 #if ENABLE_FEATURE_VI_READONLY
283 static int file_insert(const char *, char *, int);
285 static int file_insert(const char *, char *);
287 static int file_write(char *, char *, char *);
288 #if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
289 #define place_cursor(a, b, optimize) place_cursor(a, b)
291 static void place_cursor(int, int, int);
292 static void screen_erase(void);
293 static void clear_to_eol(void);
294 static void clear_to_eos(void);
295 static void standout_start(void); // send "start reverse video" sequence
296 static void standout_end(void); // send "end reverse video" sequence
297 static void flash(int); // flash the terminal screen
298 static void show_status_line(void); // put a message on the bottom line
299 static void status_line(const char *, ...); // print to status buf
300 static void status_line_bold(const char *, ...);
301 static void not_implemented(const char *); // display "Not implemented" message
302 static int format_edit_status(void); // format file status on status line
303 static void redraw(int); // force a full screen refresh
304 static char* format_line(char* /*, int*/);
305 static void refresh(int); // update the terminal from screen[]
307 static void Indicate_Error(void); // use flash or beep to indicate error
308 #define indicate_error(c) Indicate_Error()
309 static void Hit_Return(void);
311 #if ENABLE_FEATURE_VI_SEARCH
312 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
313 static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase"
315 #if ENABLE_FEATURE_VI_COLON
316 static char *get_one_address(char *, int *); // get colon addr, if present
317 static char *get_address(char *, int *, int *); // get two colon addrs, if present
318 static void colon(char *); // execute the "colon" mode cmds
320 #if ENABLE_FEATURE_VI_USE_SIGNALS
321 static void winch_sig(int); // catch window size changes
322 static void suspend_sig(int); // catch ctrl-Z
323 static void catch_sig(int); // catch ctrl-C and alarm time-outs
325 #if ENABLE_FEATURE_VI_DOT_CMD
326 static void start_new_cmd_q(char); // new queue for command
327 static void end_cmd_q(void); // stop saving input chars
329 #define end_cmd_q() ((void)0)
331 #if ENABLE_FEATURE_VI_SETOPTS
332 static void showmatching(char *); // show the matching pair () [] {}
334 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
335 static char *string_insert(char *, char *); // insert the string at 'p'
337 #if ENABLE_FEATURE_VI_YANKMARK
338 static char *text_yank(char *, char *, int); // save copy of "p" into a register
339 static char what_reg(void); // what is letter of current YDreg
340 static void check_context(char); // remember context for '' command
342 #if ENABLE_FEATURE_VI_CRASHME
343 static void crash_dummy();
344 static void crash_test();
345 static int crashme = 0;
349 static void write1(const char *out)
354 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
355 int vi_main(int argc, char **argv)
358 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
360 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
366 #if ENABLE_FEATURE_VI_CRASHME
367 srand((long) my_pid);
370 status_buffer = STATUS_BUFFER;
371 last_status_cksum = 0;
374 #ifdef NO_SUCH_APPLET_YET
375 /* If we aren't "vi", we are "view" */
376 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
377 SET_READONLY_MODE(readonly_mode);
381 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
382 #if ENABLE_FEATURE_VI_YANKMARK
383 memset(reg, 0, sizeof(reg)); // init the yank regs
385 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
386 modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
389 // 1- process $HOME/.exrc file (not inplemented yet)
390 // 2- process EXINIT variable from environment
391 // 3- process command line args
392 #if ENABLE_FEATURE_VI_COLON
394 char *p = getenv("EXINIT");
396 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
399 while ((c = getopt(argc, argv, "hCRH" USE_FEATURE_VI_COLON("c:"))) != -1) {
401 #if ENABLE_FEATURE_VI_CRASHME
406 #if ENABLE_FEATURE_VI_READONLY
407 case 'R': // Read-only flag
408 SET_READONLY_MODE(readonly_mode);
411 #if ENABLE_FEATURE_VI_COLON
412 case 'c': // cmd line vi command
414 initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
427 // The argv array can be used by the ":next" and ":rewind" commands
429 fn_start = optind; // remember first file name for :next and :rew
432 //----- This is the main file handling loop --------------
433 if (optind >= argc) {
436 for (; optind < argc; optind++) {
437 edit_file(argv[optind]);
440 //-----------------------------------------------------------
445 /* read text from file or create an empty buf */
446 /* will also update current_filename */
447 static int init_text_buffer(char *fn)
450 int size = file_size(fn); // file size. -1 means does not exist.
452 /* allocate/reallocate text buffer */
454 text_size = size * 2;
455 if (text_size < 10240)
456 text_size = 10240; // have a minimum size for new files
457 screenbegin = dot = end = text = xzalloc(text_size);
459 if (fn != current_filename) {
460 free(current_filename);
461 current_filename = xstrdup(fn);
464 // file dont exist. Start empty buf with dummy line
465 char_insert(text, '\n');
468 rc = file_insert(fn, text
469 USE_FEATURE_VI_READONLY(, 1));
472 last_file_modified = -1;
473 #if ENABLE_FEATURE_VI_YANKMARK
474 /* init the marks. */
475 memset(mark, 0, sizeof(mark));
480 static void edit_file(char *fn)
485 #if ENABLE_FEATURE_VI_USE_SIGNALS
488 #if ENABLE_FEATURE_VI_YANKMARK
489 static char *cur_line;
492 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
497 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
498 get_terminal_width_height(0, &columns, &rows);
499 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
500 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
502 new_screen(rows, columns); // get memory for virtual screen
503 init_text_buffer(fn);
505 #if ENABLE_FEATURE_VI_YANKMARK
506 YDreg = 26; // default Yank/Delete reg
507 Ureg = 27; // hold orig line for "U" cmd
508 mark[26] = mark[27] = text; // init "previous context"
511 last_forward_char = last_input_char = '\0';
515 #if ENABLE_FEATURE_VI_USE_SIGNALS
517 signal(SIGWINCH, winch_sig);
518 signal(SIGTSTP, suspend_sig);
519 sig = sigsetjmp(restart, 1);
521 screenbegin = dot = text;
525 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
528 offset = 0; // no horizontal offset
530 #if ENABLE_FEATURE_VI_DOT_CMD
532 ioq = ioq_start = NULL;
537 #if ENABLE_FEATURE_VI_COLON
542 while ((p = initial_cmds[n])) {
552 free(initial_cmds[n]);
553 initial_cmds[n] = NULL;
558 redraw(FALSE); // dont force every col re-draw
559 //------This is the main Vi cmd handling loop -----------------------
560 while (editing > 0) {
561 #if ENABLE_FEATURE_VI_CRASHME
563 if ((end - text) > 1) {
564 crash_dummy(); // generate a random command
567 dot = string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
572 last_input_char = c = get_one_char(); // get a cmd from user
573 #if ENABLE_FEATURE_VI_YANKMARK
574 // save a copy of the current line- for the 'U" command
575 if (begin_line(dot) != cur_line) {
576 cur_line = begin_line(dot);
577 text_yank(begin_line(dot), end_line(dot), Ureg);
580 #if ENABLE_FEATURE_VI_DOT_CMD
581 // These are commands that change text[].
582 // Remember the input for the "." command
583 if (!adding2q && ioq_start == NULL
584 && strchr(modifying_cmds, c)
589 do_cmd(c); // execute the user command
591 // poll to see if there is input already waiting. if we are
592 // not able to display output fast enough to keep up, skip
593 // the display update until we catch up with input.
594 if (mysleep(0) == 0) {
595 // no input pending- so update output
599 #if ENABLE_FEATURE_VI_CRASHME
601 crash_test(); // test editor variables
604 //-------------------------------------------------------------------
606 place_cursor(rows, 0, FALSE); // go to bottom of screen
607 clear_to_eol(); // Erase to end of line
611 //----- The Colon commands -------------------------------------
612 #if ENABLE_FEATURE_VI_COLON
613 static char *get_one_address(char *p, int *addr) // get colon addr, if present
617 USE_FEATURE_VI_YANKMARK(char c;)
618 USE_FEATURE_VI_SEARCH(char *pat;)
620 *addr = -1; // assume no addr
621 if (*p == '.') { // the current line
624 *addr = count_lines(text, q);
626 #if ENABLE_FEATURE_VI_YANKMARK
627 else if (*p == '\'') { // is this a mark addr
631 if (c >= 'a' && c <= 'z') {
634 q = mark[(unsigned char) c];
635 if (q != NULL) { // is mark valid
636 *addr = count_lines(text, q); // count lines
641 #if ENABLE_FEATURE_VI_SEARCH
642 else if (*p == '/') { // a search pattern
643 q = strchrnul(++p, '/');
644 pat = xstrndup(p, q - p); // save copy of pattern
648 q = char_search(dot, pat, FORWARD, FULL);
650 *addr = count_lines(text, q);
655 else if (*p == '$') { // the last line in file
657 q = begin_line(end - 1);
658 *addr = count_lines(text, q);
659 } else if (isdigit(*p)) { // specific line number
660 sscanf(p, "%d%n", addr, &st);
663 // unrecognised address - assume -1
669 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
671 //----- get the address' i.e., 1,3 'a,'b -----
672 // get FIRST addr, if present
674 p++; // skip over leading spaces
675 if (*p == '%') { // alias for 1,$
678 *e = count_lines(text, end-1);
681 p = get_one_address(p, b);
684 if (*p == ',') { // is there a address separator
688 // get SECOND addr, if present
689 p = get_one_address(p, e);
693 p++; // skip over trailing spaces
697 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
698 static void setops(const char *args, const char *opname, int flg_no,
699 const char *short_opname, int opt)
701 const char *a = args + flg_no;
702 int l = strlen(opname) - 1; /* opname have + ' ' */
704 if (strncasecmp(a, opname, l) == 0
705 || strncasecmp(a, short_opname, 2) == 0
715 // buf must be no longer than MAX_INPUT_LEN!
716 static void colon(char *buf)
718 char c, *orig_buf, *buf1, *q, *r;
719 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
720 int i, l, li, ch, b, e;
721 int useforce, forced = FALSE;
723 // :3154 // if (-e line 3154) goto it else stay put
724 // :4,33w! foo // write a portion of buffer to file "foo"
725 // :w // write all of buffer to current file
727 // :q! // quit- dont care about modified file
728 // :'a,'z!sort -u // filter block through sort
729 // :'f // goto mark "f"
730 // :'fl // list literal the mark "f" line
731 // :.r bar // read file "bar" into buffer before dot
732 // :/123/,/abc/d // delete lines from "123" line to "abc" line
733 // :/xyz/ // goto the "xyz" line
734 // :s/find/replace/ // substitute pattern "find" with "replace"
735 // :!<cmd> // run <cmd> then return
741 buf++; // move past the ':'
745 q = text; // assume 1,$ for the range
747 li = count_lines(text, end - 1);
748 fn = current_filename;
750 // look for optional address(es) :. :1 :1,9 :'q,'a :%
751 buf = get_address(buf, &b, &e);
753 // remember orig command line
756 // get the COMMAND into cmd[]
758 while (*buf != '\0') {
765 while (isblank(*buf))
769 buf1 = last_char_is(cmd, '!');
772 *buf1 = '\0'; // get rid of !
775 // if there is only one addr, then the addr
776 // is the line number of the single line the
777 // user wants. So, reset the end
778 // pointer to point at end of the "b" line
779 q = find_line(b); // what line is #b
784 // we were given two addrs. change the
785 // end pointer to the addr given by user.
786 r = find_line(e); // what line is #e
790 // ------------ now look for the command ------------
792 if (i == 0) { // :123CR goto line #123
794 dot = find_line(b); // what line is #b
798 #if ENABLE_FEATURE_ALLOW_EXEC
799 else if (strncmp(cmd, "!", 1) == 0) { // run a cmd
801 // :!ls run the <cmd>
802 place_cursor(rows - 1, 0, FALSE); // go to Status line
803 clear_to_eol(); // clear the line
805 retcode = system(orig_buf + 1); // run the cmd
807 printf("\nshell returned %i\n\n", retcode);
809 Hit_Return(); // let user see results
812 else if (strncmp(cmd, "=", i) == 0) { // where is the address
813 if (b < 0) { // no addr given- use defaults
814 b = e = count_lines(text, dot);
816 status_line("%d", b);
817 } else if (strncasecmp(cmd, "delete", i) == 0) { // delete lines
818 if (b < 0) { // no addr given- use defaults
819 q = begin_line(dot); // assume .,. for the range
822 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
824 } else if (strncasecmp(cmd, "edit", i) == 0) { // Edit a file
825 // don't edit, if the current file has been modified
826 if (file_modified && !useforce) {
827 status_line_bold("No write since last change (:edit! overrides)");
831 // the user supplied a file name
833 } else if (current_filename && current_filename[0]) {
834 // no user supplied name- use the current filename
835 // fn = current_filename; was set by default
837 // no user file name, no current name- punt
838 status_line_bold("No current filename");
842 if (init_text_buffer(fn) < 0)
845 #if ENABLE_FEATURE_VI_YANKMARK
846 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
847 free(reg[Ureg]); // free orig line reg- for 'U'
850 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
851 free(reg[YDreg]); // free default yank/delete register
855 // how many lines in text[]?
856 li = count_lines(text, end - 1);
857 status_line("\"%s\"%s"
858 USE_FEATURE_VI_READONLY("%s")
859 " %dL, %dC", current_filename,
860 (file_size(fn) < 0 ? " [New file]" : ""),
861 USE_FEATURE_VI_READONLY(
862 ((readonly_mode) ? " [Readonly]" : ""),
865 } else if (strncasecmp(cmd, "file", i) == 0) { // what File is this
866 if (b != -1 || e != -1) {
867 not_implemented("No address allowed on this command");
871 // user wants a new filename
872 free(current_filename);
873 current_filename = xstrdup(args);
875 // user wants file status info
876 last_status_cksum = 0; // force status update
878 } else if (strncasecmp(cmd, "features", i) == 0) { // what features are available
879 // print out values of all features
880 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
881 clear_to_eol(); // clear the line
886 } else if (strncasecmp(cmd, "list", i) == 0) { // literal print line
887 if (b < 0) { // no addr given- use defaults
888 q = begin_line(dot); // assume .,. for the range
891 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
892 clear_to_eol(); // clear the line
894 for (; q <= r; q++) {
898 c_is_no_print = (c & 0x80) && !Isprint(c);
905 } else if (c < ' ' || c == 127) {
916 #if ENABLE_FEATURE_VI_SET
920 } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
921 || strncasecmp(cmd, "next", i) == 0 // edit next file
924 // force end of argv list
931 // don't exit if the file been modified
933 status_line_bold("No write since last change (:%s! overrides)",
934 (*cmd == 'q' ? "quit" : "next"));
937 // are there other file to edit
938 if (*cmd == 'q' && optind < save_argc - 1) {
939 status_line_bold("%d more file to edit", (save_argc - optind - 1));
942 if (*cmd == 'n' && optind >= save_argc - 1) {
943 status_line_bold("No more files to edit");
947 } else if (strncasecmp(cmd, "read", i) == 0) { // read file into text[]
950 status_line_bold("No filename given");
953 if (b < 0) { // no addr given- use defaults
954 q = begin_line(dot); // assume "dot"
956 // read after current line- unless user said ":0r foo"
959 ch = file_insert(fn, q USE_FEATURE_VI_READONLY(, 0));
961 goto vc1; // nothing was inserted
962 // how many lines in text[]?
963 li = count_lines(q, q + ch - 1);
965 USE_FEATURE_VI_READONLY("%s")
967 USE_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
970 // if the insert is before "dot" then we need to update
975 } else if (strncasecmp(cmd, "rewind", i) == 0) { // rewind cmd line args
976 if (file_modified && !useforce) {
977 status_line_bold("No write since last change (:rewind! overrides)");
979 // reset the filenames to edit
980 optind = fn_start - 1;
983 #if ENABLE_FEATURE_VI_SET
984 } else if (strncasecmp(cmd, "set", i) == 0) { // set or clear features
985 #if ENABLE_FEATURE_VI_SETOPTS
988 i = 0; // offset into args
989 // only blank is regarded as args delmiter. What about tab '\t' ?
990 if (!args[0] || strcasecmp(args, "all") == 0) {
991 // print out values of all options
992 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
993 clear_to_eol(); // clear the line
994 printf("----------------------------------------\r\n");
995 #if ENABLE_FEATURE_VI_SETOPTS
998 printf("autoindent ");
1004 printf("ignorecase ");
1007 printf("showmatch ");
1008 printf("tabstop=%d ", tabstop);
1013 #if ENABLE_FEATURE_VI_SETOPTS
1016 if (strncasecmp(argp, "no", 2) == 0)
1017 i = 2; // ":set noautoindent"
1018 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1019 setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
1020 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1021 setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
1023 if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
1024 sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
1025 if (ch > 0 && ch <= MAX_TABSTOP)
1028 while (*argp && *argp != ' ')
1029 argp++; // skip to arg delimiter (i.e. blank)
1030 while (*argp && *argp == ' ')
1031 argp++; // skip all delimiting blanks
1033 #endif /* FEATURE_VI_SETOPTS */
1034 #endif /* FEATURE_VI_SET */
1035 #if ENABLE_FEATURE_VI_SEARCH
1036 } else if (strncasecmp(cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1040 // F points to the "find" pattern
1041 // R points to the "replace" pattern
1042 // replace the cmd line delimiters "/" with NULLs
1043 gflag = 0; // global replace flag
1044 c = orig_buf[1]; // what is the delimiter
1045 F = orig_buf + 2; // start of "find"
1046 R = strchr(F, c); // middle delimiter
1047 if (!R) goto colon_s_fail;
1048 *R++ = '\0'; // terminate "find"
1049 buf1 = strchr(R, c);
1050 if (!buf1) goto colon_s_fail;
1051 *buf1++ = '\0'; // terminate "replace"
1052 if (*buf1 == 'g') { // :s/foo/bar/g
1054 gflag++; // turn on gflag
1057 if (b < 0) { // maybe :s/foo/bar/
1058 q = begin_line(dot); // start with cur line
1059 b = count_lines(text, q); // cur line number
1062 e = b; // maybe :.s/foo/bar/
1063 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1064 ls = q; // orig line start
1066 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1068 // we found the "find" pattern - delete it
1069 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1070 // inset the "replace" patern
1071 string_insert(buf1, R); // insert the string
1072 // check for "global" :s/foo/bar/g
1074 if ((buf1 + strlen(R)) < end_line(ls)) {
1075 q = buf1 + strlen(R);
1076 goto vc4; // don't let q move past cur line
1082 #endif /* FEATURE_VI_SEARCH */
1083 } else if (strncasecmp(cmd, "version", i) == 0) { // show software version
1084 status_line("%s", BB_VER " " BB_BT);
1085 } else if (strncasecmp(cmd, "write", i) == 0 // write text to file
1086 || strncasecmp(cmd, "wq", i) == 0
1087 || strncasecmp(cmd, "wn", i) == 0
1088 || strncasecmp(cmd, "x", i) == 0
1090 // is there a file name to write to?
1094 #if ENABLE_FEATURE_VI_READONLY
1095 if (readonly_mode && !useforce) {
1096 status_line_bold("\"%s\" File is read only", fn);
1100 // how many lines in text[]?
1101 li = count_lines(q, r);
1103 // see if file exists- if not, its just a new file request
1105 // if "fn" is not write-able, chmod u+w
1106 // sprintf(syscmd, "chmod u+w %s", fn);
1110 l = file_write(fn, q, r);
1111 if (useforce && forced) {
1113 // sprintf(syscmd, "chmod u-w %s", fn);
1119 status_line_bold("\"%s\" %s", fn, strerror(errno));
1121 status_line("\"%s\" %dL, %dC", fn, li, l);
1122 if (q == text && r == end - 1 && l == ch) {
1124 last_file_modified = -1;
1126 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1127 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1132 #if ENABLE_FEATURE_VI_READONLY
1135 #if ENABLE_FEATURE_VI_YANKMARK
1136 } else if (strncasecmp(cmd, "yank", i) == 0) { // yank lines
1137 if (b < 0) { // no addr given- use defaults
1138 q = begin_line(dot); // assume .,. for the range
1141 text_yank(q, r, YDreg);
1142 li = count_lines(q, r);
1143 status_line("Yank %d lines (%d chars) into [%c]",
1144 li, strlen(reg[YDreg]), what_reg());
1148 not_implemented(cmd);
1151 dot = bound_dot(dot); // make sure "dot" is valid
1153 #if ENABLE_FEATURE_VI_SEARCH
1155 status_line(":s expression missing delimiters");
1159 #endif /* FEATURE_VI_COLON */
1161 static void Hit_Return(void)
1166 write1("[Hit return to continue]");
1168 while ((c = get_one_char()) != '\n' && c != '\r')
1170 redraw(TRUE); // force redraw all
1173 static int next_tabstop(int col)
1175 return col + ((tabstop - 1) - (col % tabstop));
1178 //----- Synchronize the cursor to Dot --------------------------
1179 static void sync_cursor(char *d, int *row, int *col)
1181 char *beg_cur; // begin and end of "d" line
1185 beg_cur = begin_line(d); // first char of cur line
1187 if (beg_cur < screenbegin) {
1188 // "d" is before top line on screen
1189 // how many lines do we have to move
1190 cnt = count_lines(beg_cur, screenbegin);
1192 screenbegin = beg_cur;
1193 if (cnt > (rows - 1) / 2) {
1194 // we moved too many lines. put "dot" in middle of screen
1195 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1196 screenbegin = prev_line(screenbegin);
1200 char *end_scr; // begin and end of screen
1201 end_scr = end_screen(); // last char of screen
1202 if (beg_cur > end_scr) {
1203 // "d" is after bottom line on screen
1204 // how many lines do we have to move
1205 cnt = count_lines(end_scr, beg_cur);
1206 if (cnt > (rows - 1) / 2)
1207 goto sc1; // too many lines
1208 for (ro = 0; ro < cnt - 1; ro++) {
1209 // move screen begin the same amount
1210 screenbegin = next_line(screenbegin);
1211 // now, move the end of screen
1212 end_scr = next_line(end_scr);
1213 end_scr = end_line(end_scr);
1217 // "d" is on screen- find out which row
1219 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1225 // find out what col "d" is on
1227 while (tp < d) { // drive "co" to correct column
1228 if (*tp == '\n') //vda || *tp == '\0')
1231 // handle tabs like real vi
1232 if (d == tp && cmd_mode) {
1235 co = next_tabstop(co);
1237 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1238 co++; // display as ^X, use 2 columns
1244 // "co" is the column where "dot" is.
1245 // The screen has "columns" columns.
1246 // The currently displayed columns are 0+offset -- columns+ofset
1247 // |-------------------------------------------------------------|
1249 // offset | |------- columns ----------------|
1251 // If "co" is already in this range then we do not have to adjust offset
1252 // but, we do have to subtract the "offset" bias from "co".
1253 // If "co" is outside this range then we have to change "offset".
1254 // If the first char of a line is a tab the cursor will try to stay
1255 // in column 7, but we have to set offset to 0.
1257 if (co < 0 + offset) {
1260 if (co >= columns + offset) {
1261 offset = co - columns + 1;
1263 // if the first char of the line is a tab, and "dot" is sitting on it
1264 // force offset to 0.
1265 if (d == beg_cur && *d == '\t') {
1274 //----- Text Movement Routines ---------------------------------
1275 static char *begin_line(char *p) // return pointer to first char cur line
1278 p = memrchr(text, '\n', p - text);
1286 static char *end_line(char *p) // return pointer to NL of cur line line
1289 p = memchr(p, '\n', end - p - 1);
1296 static char *dollar_line(char *p) // return pointer to just before NL line
1299 // Try to stay off of the Newline
1300 if (*p == '\n' && (p - begin_line(p)) > 0)
1305 static char *prev_line(char *p) // return pointer first char prev line
1307 p = begin_line(p); // goto begining of cur line
1308 if (p[-1] == '\n' && p > text)
1309 p--; // step to prev line
1310 p = begin_line(p); // goto begining of prev line
1314 static char *next_line(char *p) // return pointer first char next line
1317 if (*p == '\n' && p < end - 1)
1318 p++; // step to next line
1322 //----- Text Information Routines ------------------------------
1323 static char *end_screen(void)
1328 // find new bottom line
1330 for (cnt = 0; cnt < rows - 2; cnt++)
1336 // count line from start to stop
1337 static int count_lines(char *start, char *stop)
1342 if (stop < start) { // start and stop are backwards- reverse them
1348 stop = end_line(stop);
1349 while (start <= stop && start <= end - 1) {
1350 start = end_line(start);
1358 static char *find_line(int li) // find begining of line #li
1362 for (q = text; li > 1; li--) {
1368 //----- Dot Movement Routines ----------------------------------
1369 static void dot_left(void)
1371 if (dot > text && dot[-1] != '\n')
1375 static void dot_right(void)
1377 if (dot < end - 1 && *dot != '\n')
1381 static void dot_begin(void)
1383 dot = begin_line(dot); // return pointer to first char cur line
1386 static void dot_end(void)
1388 dot = end_line(dot); // return pointer to last char cur line
1391 static char *move_to_col(char *p, int l)
1397 while (co < l && p < end) {
1398 if (*p == '\n') //vda || *p == '\0')
1401 co = next_tabstop(co);
1402 } else if (*p < ' ' || *p == 127) {
1403 co++; // display as ^X, use 2 columns
1411 static void dot_next(void)
1413 dot = next_line(dot);
1416 static void dot_prev(void)
1418 dot = prev_line(dot);
1421 static void dot_scroll(int cnt, int dir)
1425 for (; cnt > 0; cnt--) {
1428 // ctrl-Y scroll up one line
1429 screenbegin = prev_line(screenbegin);
1432 // ctrl-E scroll down one line
1433 screenbegin = next_line(screenbegin);
1436 // make sure "dot" stays on the screen so we dont scroll off
1437 if (dot < screenbegin)
1439 q = end_screen(); // find new bottom line
1441 dot = begin_line(q); // is dot is below bottom line?
1445 static void dot_skip_over_ws(void)
1448 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1452 static void dot_delete(void) // delete the char at 'dot'
1454 text_hole_delete(dot, dot);
1457 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1459 if (p >= end && end > text) {
1461 indicate_error('1');
1465 indicate_error('2');
1470 //----- Helper Utility Routines --------------------------------
1472 //----------------------------------------------------------------
1473 //----- Char Routines --------------------------------------------
1474 /* Chars that are part of a word-
1475 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1476 * Chars that are Not part of a word (stoppers)
1477 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1478 * Chars that are WhiteSpace
1479 * TAB NEWLINE VT FF RETURN SPACE
1480 * DO NOT COUNT NEWLINE AS WHITESPACE
1483 static char *new_screen(int ro, int co)
1488 screensize = ro * co + 8;
1489 screen = xmalloc(screensize);
1490 // initialize the new screen. assume this will be a empty file.
1492 // non-existent text[] lines start with a tilde (~).
1493 for (li = 1; li < ro - 1; li++) {
1494 screen[(li * co) + 0] = '~';
1499 #if ENABLE_FEATURE_VI_SEARCH
1500 static int mycmp(const char * s1, const char * s2, int len)
1504 i = strncmp(s1, s2, len);
1505 if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) {
1506 i = strncasecmp(s1, s2, len);
1511 // search for pattern starting at p
1512 static char *char_search(char * p, const char * pat, int dir, int range)
1514 #ifndef REGEX_SEARCH
1519 if (dir == FORWARD) {
1520 stop = end - 1; // assume range is p - end-1
1521 if (range == LIMITED)
1522 stop = next_line(p); // range is to next line
1523 for (start = p; start < stop; start++) {
1524 if (mycmp(start, pat, len) == 0) {
1528 } else if (dir == BACK) {
1529 stop = text; // assume range is text - p
1530 if (range == LIMITED)
1531 stop = prev_line(p); // range is to prev line
1532 for (start = p - len; start >= stop; start--) {
1533 if (mycmp(start, pat, len) == 0) {
1538 // pattern not found
1540 #else /* REGEX_SEARCH */
1542 struct re_pattern_buffer preg;
1546 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1552 // assume a LIMITED forward search
1560 // count the number of chars to search over, forward or backward
1564 // RANGE could be negative if we are searching backwards
1567 q = re_compile_pattern(pat, strlen(pat), &preg);
1569 // The pattern was not compiled
1570 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1571 i = 0; // return p if pattern not compiled
1581 // search for the compiled pattern, preg, in p[]
1582 // range < 0- search backward
1583 // range > 0- search forward
1585 // re_search() < 0 not found or error
1586 // re_search() > 0 index of found pattern
1587 // struct pattern char int int int struct reg
1588 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1589 i = re_search(&preg, q, size, 0, range, 0);
1592 i = 0; // return NULL if pattern not found
1595 if (dir == FORWARD) {
1601 #endif /* REGEX_SEARCH */
1603 #endif /* FEATURE_VI_SEARCH */
1605 static char *char_insert(char * p, char c) // insert the char c at 'p'
1607 if (c == 22) { // Is this an ctrl-V?
1608 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1609 p--; // backup onto ^
1610 refresh(FALSE); // show the ^
1614 file_modified++; // has the file been modified
1615 } else if (c == 27) { // Is this an ESC?
1618 end_cmd_q(); // stop adding to q
1619 last_status_cksum = 0; // force status update
1620 if ((p[-1] != '\n') && (dot > text)) {
1623 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1625 if ((p[-1] != '\n') && (dot>text)) {
1627 p = text_hole_delete(p, p); // shrink buffer 1 char
1630 // insert a char into text[]
1631 char *sp; // "save p"
1634 c = '\n'; // translate \r to \n
1635 sp = p; // remember addr of insert
1636 p = stupid_insert(p, c); // insert the char
1637 #if ENABLE_FEATURE_VI_SETOPTS
1638 if (showmatch && strchr(")]}", *sp) != NULL) {
1641 if (autoindent && c == '\n') { // auto indent the new line
1644 q = prev_line(p); // use prev line as templet
1645 for (; isblank(*q); q++) {
1646 p = stupid_insert(p, *q); // insert the char
1654 static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
1656 p = text_hole_make(p, 1);
1659 file_modified++; // has the file been modified
1665 static int find_range(char ** start, char ** stop, char c)
1667 char *save_dot, *p, *q, *t;
1668 int cnt, multiline = 0;
1673 if (strchr("cdy><", c)) {
1674 // these cmds operate on whole lines
1675 p = q = begin_line(p);
1676 for (cnt = 1; cnt < cmdcnt; cnt++) {
1680 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1681 // These cmds operate on char positions
1682 do_cmd(c); // execute movement cmd
1684 } else if (strchr("wW", c)) {
1685 do_cmd(c); // execute movement cmd
1686 // if we are at the next word's first char
1687 // step back one char
1688 // but check the possibilities when it is true
1689 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1690 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1691 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1692 dot--; // move back off of next word
1693 if (dot > text && *dot == '\n')
1694 dot--; // stay off NL
1696 } else if (strchr("H-k{", c)) {
1697 // these operate on multi-lines backwards
1698 q = end_line(dot); // find NL
1699 do_cmd(c); // execute movement cmd
1702 } else if (strchr("L+j}\r\n", c)) {
1703 // these operate on multi-lines forwards
1704 p = begin_line(dot);
1705 do_cmd(c); // execute movement cmd
1706 dot_end(); // find NL
1709 // nothing -- this causes any other values of c to
1710 // represent the one-character range under the
1711 // cursor. this is correct for ' ' and 'l', but
1712 // perhaps no others.
1721 // backward char movements don't include start position
1722 if (q > p && strchr("^0bBh\b\177", c)) q--;
1725 for (t = p; t <= q; t++) {
1738 static int st_test(char * p, int type, int dir, char * tested)
1748 if (type == S_BEFORE_WS) {
1750 test = ((!isspace(c)) || c == '\n');
1752 if (type == S_TO_WS) {
1754 test = ((!isspace(c)) || c == '\n');
1756 if (type == S_OVER_WS) {
1758 test = ((isspace(c)));
1760 if (type == S_END_PUNCT) {
1762 test = ((ispunct(c)));
1764 if (type == S_END_ALNUM) {
1766 test = ((isalnum(c)) || c == '_');
1772 static char *skip_thing(char * p, int linecnt, int dir, int type)
1776 while (st_test(p, type, dir, &c)) {
1777 // make sure we limit search to correct number of lines
1778 if (c == '\n' && --linecnt < 1)
1780 if (dir >= 0 && p >= end - 1)
1782 if (dir < 0 && p <= text)
1784 p += dir; // move to next char
1789 // find matching char of pair () [] {}
1790 static char *find_pair(char * p, const char c)
1797 dir = 1; // assume forward
1799 case '(': match = ')'; break;
1800 case '[': match = ']'; break;
1801 case '{': match = '}'; break;
1802 case ')': match = '('; dir = -1; break;
1803 case ']': match = '['; dir = -1; break;
1804 case '}': match = '{'; dir = -1; break;
1806 for (q = p + dir; text <= q && q < end; q += dir) {
1807 // look for match, count levels of pairs (( ))
1809 level++; // increase pair levels
1811 level--; // reduce pair level
1813 break; // found matching pair
1816 q = NULL; // indicate no match
1820 #if ENABLE_FEATURE_VI_SETOPTS
1821 // show the matching char of a pair, () [] {}
1822 static void showmatching(char *p)
1826 // we found half of a pair
1827 q = find_pair(p, *p); // get loc of matching char
1829 indicate_error('3'); // no matching char
1831 // "q" now points to matching pair
1832 save_dot = dot; // remember where we are
1833 dot = q; // go to new loc
1834 refresh(FALSE); // let the user see it
1835 mysleep(40); // give user some time
1836 dot = save_dot; // go back to old loc
1840 #endif /* FEATURE_VI_SETOPTS */
1842 // open a hole in text[]
1843 static char *text_hole_make(char * p, int size) // at "p", make a 'size' byte hole
1852 cnt = end - src; // the rest of buffer
1853 if ( ((end + size) >= (text + text_size)) // TODO: realloc here
1854 || memmove(dest, src, cnt) != dest) {
1855 status_line_bold("can't create room for new characters");
1859 memset(p, ' ', size); // clear new hole
1860 end += size; // adjust the new END
1861 file_modified++; // has the file been modified
1866 // close a hole in text[]
1867 static char *text_hole_delete(char * p, char * q) // delete "p" through "q", inclusive
1872 // move forwards, from beginning
1876 if (q < p) { // they are backward- swap them
1880 hole_size = q - p + 1;
1882 if (src < text || src > end)
1884 if (dest < text || dest >= end)
1887 goto thd_atend; // just delete the end of the buffer
1888 if (memmove(dest, src, cnt) != dest) {
1889 status_line_bold("can't delete the character");
1892 end = end - hole_size; // adjust the new END
1894 dest = end - 1; // make sure dest in below end-1
1896 dest = end = text; // keep pointers valid
1897 file_modified++; // has the file been modified
1902 // copy text into register, then delete text.
1903 // if dist <= 0, do not include, or go past, a NewLine
1905 static char *yank_delete(char * start, char * stop, int dist, int yf)
1909 // make sure start <= stop
1911 // they are backwards, reverse them
1917 // we cannot cross NL boundaries
1921 // dont go past a NewLine
1922 for (; p + 1 <= stop; p++) {
1924 stop = p; // "stop" just before NewLine
1930 #if ENABLE_FEATURE_VI_YANKMARK
1931 text_yank(start, stop, YDreg);
1933 if (yf == YANKDEL) {
1934 p = text_hole_delete(start, stop);
1939 static void show_help(void)
1941 puts("These features are available:"
1942 #if ENABLE_FEATURE_VI_SEARCH
1943 "\n\tPattern searches with / and ?"
1945 #if ENABLE_FEATURE_VI_DOT_CMD
1946 "\n\tLast command repeat with \'.\'"
1948 #if ENABLE_FEATURE_VI_YANKMARK
1949 "\n\tLine marking with 'x"
1950 "\n\tNamed buffers with \"x"
1952 #if ENABLE_FEATURE_VI_READONLY
1953 "\n\tReadonly if vi is called as \"view\""
1954 "\n\tReadonly with -R command line arg"
1956 #if ENABLE_FEATURE_VI_SET
1957 "\n\tSome colon mode commands with \':\'"
1959 #if ENABLE_FEATURE_VI_SETOPTS
1960 "\n\tSettable options with \":set\""
1962 #if ENABLE_FEATURE_VI_USE_SIGNALS
1963 "\n\tSignal catching- ^C"
1964 "\n\tJob suspend and resume with ^Z"
1966 #if ENABLE_FEATURE_VI_WIN_RESIZE
1967 "\n\tAdapt to window re-sizes"
1972 #if ENABLE_FEATURE_VI_DOT_CMD
1973 static void start_new_cmd_q(char c)
1975 // get buffer for new cmd
1976 if (!last_modifying_cmd)
1977 last_modifying_cmd = xzalloc(MAX_INPUT_LEN);
1978 // if there is a current cmd count put it in the buffer first
1980 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1981 else { // just save char c onto queue
1982 last_modifying_cmd[0] = c;
1988 static void end_cmd_q(void)
1990 #if ENABLE_FEATURE_VI_YANKMARK
1991 YDreg = 26; // go back to default Yank/Delete reg
1995 #endif /* FEATURE_VI_DOT_CMD */
1997 #if ENABLE_FEATURE_VI_YANKMARK \
1998 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1999 || ENABLE_FEATURE_VI_CRASHME
2000 static char *string_insert(char * p, char * s) // insert the string at 'p'
2005 if (text_hole_make(p, i)) {
2007 for (cnt = 0; *s != '\0'; s++) {
2011 #if ENABLE_FEATURE_VI_YANKMARK
2012 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2019 #if ENABLE_FEATURE_VI_YANKMARK
2020 static char *text_yank(char * p, char * q, int dest) // copy text into a register
2025 if (q < p) { // they are backwards- reverse them
2032 free(t); // if already a yank register, free it
2033 t = xmalloc(cnt + 1); // get a new register
2034 memset(t, '\0', cnt + 1); // clear new text[]
2035 strncpy(t, p, cnt); // copy text[] into bufer
2040 static char what_reg(void)
2044 c = 'D'; // default to D-reg
2045 if (0 <= YDreg && YDreg <= 25)
2046 c = 'a' + (char) YDreg;
2054 static void check_context(char cmd)
2056 // A context is defined to be "modifying text"
2057 // Any modifying command establishes a new context.
2059 if (dot < context_start || dot > context_end) {
2060 if (strchr(modifying_cmds, cmd) != NULL) {
2061 // we are trying to modify text[]- make this the current context
2062 mark[27] = mark[26]; // move cur to prev
2063 mark[26] = dot; // move local to cur
2064 context_start = prev_line(prev_line(dot));
2065 context_end = next_line(next_line(dot));
2066 //loiter= start_loiter= now;
2071 static char *swap_context(char *p) // goto new context for '' command make this the current context
2075 // the current context is in mark[26]
2076 // the previous context is in mark[27]
2077 // only swap context if other context is valid
2078 if (text <= mark[27] && mark[27] <= end - 1) {
2080 mark[27] = mark[26];
2082 p = mark[26]; // where we are going- previous context
2083 context_start = prev_line(prev_line(prev_line(p)));
2084 context_end = next_line(next_line(next_line(p)));
2088 #endif /* FEATURE_VI_YANKMARK */
2090 //----- Set terminal attributes --------------------------------
2091 static void rawmode(void)
2093 tcgetattr(0, &term_orig);
2094 term_vi = term_orig;
2095 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2096 term_vi.c_iflag &= (~IXON & ~ICRNL);
2097 term_vi.c_oflag &= (~ONLCR);
2098 term_vi.c_cc[VMIN] = 1;
2099 term_vi.c_cc[VTIME] = 0;
2100 erase_char = term_vi.c_cc[VERASE];
2101 tcsetattr(0, TCSANOW, &term_vi);
2104 static void cookmode(void)
2107 tcsetattr(0, TCSANOW, &term_orig);
2110 //----- Come here when we get a window resize signal ---------
2111 #if ENABLE_FEATURE_VI_USE_SIGNALS
2112 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2114 // FIXME: do it in main loop!!!
2115 signal(SIGWINCH, winch_sig);
2116 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2117 get_terminal_width_height(0, &columns, &rows);
2118 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2119 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2121 new_screen(rows, columns); // get memory for virtual screen
2122 redraw(TRUE); // re-draw the screen
2125 //----- Come here when we get a continue signal -------------------
2126 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2128 rawmode(); // terminal to "raw"
2129 last_status_cksum = 0; // force status update
2130 redraw(TRUE); // re-draw the screen
2132 signal(SIGTSTP, suspend_sig);
2133 signal(SIGCONT, SIG_DFL);
2134 kill(my_pid, SIGCONT);
2137 //----- Come here when we get a Suspend signal -------------------
2138 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2140 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2141 clear_to_eol(); // Erase to end of line
2142 cookmode(); // terminal to "cooked"
2144 signal(SIGCONT, cont_sig);
2145 signal(SIGTSTP, SIG_DFL);
2146 kill(my_pid, SIGTSTP);
2149 //----- Come here when we get a signal ---------------------------
2150 static void catch_sig(int sig)
2152 signal(SIGINT, catch_sig);
2154 siglongjmp(restart, sig);
2156 #endif /* FEATURE_VI_USE_SIGNALS */
2158 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2160 struct pollfd pfd[1];
2163 pfd[0].events = POLLIN;
2164 return safe_poll(pfd, 1, hund*10) > 0;
2167 static int chars_to_parse;
2169 //----- IO Routines --------------------------------------------
2170 static char readit(void) // read (maybe cursor) key from stdin
2179 static const struct esc_cmds esccmds[] = {
2180 {"OA" , VI_K_UP }, // cursor key Up
2181 {"OB" , VI_K_DOWN }, // cursor key Down
2182 {"OC" , VI_K_RIGHT }, // Cursor Key Right
2183 {"OD" , VI_K_LEFT }, // cursor key Left
2184 {"OH" , VI_K_HOME }, // Cursor Key Home
2185 {"OF" , VI_K_END }, // Cursor Key End
2186 {"[A" , VI_K_UP }, // cursor key Up
2187 {"[B" , VI_K_DOWN }, // cursor key Down
2188 {"[C" , VI_K_RIGHT }, // Cursor Key Right
2189 {"[D" , VI_K_LEFT }, // cursor key Left
2190 {"[H" , VI_K_HOME }, // Cursor Key Home
2191 {"[F" , VI_K_END }, // Cursor Key End
2192 {"[1~" , VI_K_HOME }, // Cursor Key Home
2193 {"[2~" , VI_K_INSERT }, // Cursor Key Insert
2194 {"[3~" , VI_K_DELETE }, // Cursor Key Delete
2195 {"[4~" , VI_K_END }, // Cursor Key End
2196 {"[5~" , VI_K_PAGEUP }, // Cursor Key Page Up
2197 {"[6~" , VI_K_PAGEDOWN}, // Cursor Key Page Down
2198 {"OP" , VI_K_FUN1 }, // Function Key F1
2199 {"OQ" , VI_K_FUN2 }, // Function Key F2
2200 {"OR" , VI_K_FUN3 }, // Function Key F3
2201 {"OS" , VI_K_FUN4 }, // Function Key F4
2202 // careful: these have no terminating NUL!
2203 {"[11~", VI_K_FUN1 }, // Function Key F1
2204 {"[12~", VI_K_FUN2 }, // Function Key F2
2205 {"[13~", VI_K_FUN3 }, // Function Key F3
2206 {"[14~", VI_K_FUN4 }, // Function Key F4
2207 {"[15~", VI_K_FUN5 }, // Function Key F5
2208 {"[17~", VI_K_FUN6 }, // Function Key F6
2209 {"[18~", VI_K_FUN7 }, // Function Key F7
2210 {"[19~", VI_K_FUN8 }, // Function Key F8
2211 {"[20~", VI_K_FUN9 }, // Function Key F9
2212 {"[21~", VI_K_FUN10 }, // Function Key F10
2213 {"[23~", VI_K_FUN11 }, // Function Key F11
2214 {"[24~", VI_K_FUN12 }, // Function Key F12
2216 enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2220 // get input from User- are there already input chars in Q?
2222 // the Q is empty, wait for a typed char
2223 n = safe_read(0, readbuffer, sizeof(readbuffer));
2225 if (errno == EBADF || errno == EFAULT || errno == EINVAL
2227 editing = 0; // want to exit
2232 if (readbuffer[0] == 27) {
2233 // This is an ESC char. Is this Esc sequence?
2234 // Could be bare Esc key. See if there are any
2235 // more chars to read after the ESC. This would
2236 // be a Function or Cursor Key sequence.
2237 struct pollfd pfd[1];
2239 pfd[0].events = POLLIN;
2240 // keep reading while there are input chars, and room in buffer
2241 // for a complete ESC sequence (assuming 8 chars is enough)
2242 while (safe_poll(pfd, 1, 0) > 0 && n <= (sizeof(readbuffer) - 8)) {
2243 // read the rest of the ESC string
2244 int r = safe_read(0, readbuffer + n, sizeof(readbuffer) - n);
2252 if (c == 27 && n > 1) {
2253 // Maybe cursor or function key?
2254 const struct esc_cmds *eindex;
2256 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2257 int cnt = strnlen(eindex->seq, 4);
2260 if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
2262 c = eindex->val; // magic char value
2263 n = cnt + 1; // squeeze out the ESC sequence
2266 // defined ESC sequence not found
2270 // remove key sequence from Q
2271 chars_to_parse -= n;
2272 memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
2276 //----- IO Routines --------------------------------------------
2277 static char get_one_char(void)
2281 #if ENABLE_FEATURE_VI_DOT_CMD
2283 // we are not adding to the q.
2284 // but, we may be reading from a q
2286 // there is no current q, read from STDIN
2287 c = readit(); // get the users input
2289 // there is a queue to get chars from first
2292 // the end of the q, read from STDIN
2294 ioq_start = ioq = 0;
2295 c = readit(); // get the users input
2299 // adding STDIN chars to q
2300 c = readit(); // get the users input
2301 if (last_modifying_cmd != NULL) {
2302 if (lmc_len >= MAX_INPUT_LEN - 1) {
2303 status_line_bold("last_modifying_cmd overrun");
2305 // add new char to q
2306 last_modifying_cmd[lmc_len++] = c;
2311 c = readit(); // get the users input
2312 #endif /* FEATURE_VI_DOT_CMD */
2316 // Get input line (uses "status line" area)
2317 static char *get_input_line(const char *prompt)
2319 static char *buf; // [MAX_INPUT_LEN]
2324 if (!buf) buf = xmalloc(MAX_INPUT_LEN);
2326 strcpy(buf, prompt);
2327 last_status_cksum = 0; // force status update
2328 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2329 clear_to_eol(); // clear the line
2330 write1(prompt); // write out the :, /, or ? prompt
2333 while (i < MAX_INPUT_LEN) {
2335 if (c == '\n' || c == '\r' || c == 27)
2336 break; // this is end of input
2337 if (c == erase_char || c == 8 || c == 127) {
2338 // user wants to erase prev char
2340 write1("\b \b"); // erase char on screen
2341 if (i <= 0) // user backs up before b-o-l, exit
2353 static int file_size(const char *fn) // what is the byte size of "fn"
2359 if (fn && fn[0] && stat(fn, &st_buf) == 0) // see if file exists
2360 cnt = (int) st_buf.st_size;
2364 static int file_insert(const char * fn, char *p
2365 USE_FEATURE_VI_READONLY(, int update_ro_status))
2369 struct stat statbuf;
2372 if (stat(fn, &statbuf) < 0) {
2373 status_line_bold("\"%s\" %s", fn, strerror(errno));
2376 if ((statbuf.st_mode & S_IFREG) == 0) {
2377 // This is not a regular file
2378 status_line_bold("\"%s\" Not a regular file", fn);
2381 /* // this check is done by open()
2382 if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2383 // dont have any read permissions
2384 status_line_bold("\"%s\" Not readable", fn);
2388 if (p < text || p > end) {
2389 status_line_bold("Trying to insert file outside of memory");
2393 // read file to buffer
2394 fd = open(fn, O_RDONLY);
2396 status_line_bold("\"%s\" %s", fn, strerror(errno));
2399 size = statbuf.st_size;
2400 p = text_hole_make(p, size);
2403 cnt = safe_read(fd, p, size);
2405 status_line_bold("\"%s\" %s", fn, strerror(errno));
2406 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2407 } else if (cnt < size) {
2408 // There was a partial read, shrink unused space text[]
2409 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2410 status_line_bold("cannot read all of file \"%s\"", fn);
2416 #if ENABLE_FEATURE_VI_READONLY
2417 if (update_ro_status
2418 && ((access(fn, W_OK) < 0) ||
2419 /* root will always have access()
2420 * so we check fileperms too */
2421 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2424 SET_READONLY_FILE(readonly_mode);
2431 static int file_write(char * fn, char * first, char * last)
2433 int fd, cnt, charcnt;
2436 status_line_bold("No current filename");
2440 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
2443 cnt = last - first + 1;
2444 charcnt = full_write(fd, first, cnt);
2445 if (charcnt == cnt) {
2447 //file_modified = FALSE; // the file has not been modified
2455 //----- Terminal Drawing ---------------------------------------
2456 // The terminal is made up of 'rows' line of 'columns' columns.
2457 // classically this would be 24 x 80.
2458 // screen coordinates
2464 // 23,0 ... 23,79 <- status line
2466 //----- Move the cursor to row x col (count from 0, not 1) -------
2467 static void place_cursor(int row, int col, int optimize)
2469 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2472 if (row < 0) row = 0;
2473 if (row >= rows) row = rows - 1;
2474 if (col < 0) col = 0;
2475 if (col >= columns) col = columns - 1;
2477 //----- 1. Try the standard terminal ESC sequence
2478 sprintf(cm1, CMrc, row + 1, col + 1);
2481 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2482 if (optimize && col < 16) {
2484 SZ_UP = sizeof(CMup),
2485 SZ_DN = sizeof(CMdown),
2486 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2488 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2490 int Rrow = last_row;
2491 int diff = Rrow - row;
2493 if (diff < -5 || diff > 5)
2496 //----- find the minimum # of chars to move cursor -------------
2497 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2500 // move to the correct row
2501 while (row < Rrow) {
2502 // the cursor has to move up
2506 while (row > Rrow) {
2507 // the cursor has to move down
2508 strcat(cm2, CMdown);
2512 // now move to the correct column
2513 strcat(cm2, "\r"); // start at col 0
2514 // just send out orignal source char to get to correct place
2515 screenp = &screen[row * columns]; // start of screen line
2516 strncat(cm2, screenp, col);
2518 // pick the shortest cursor motion to send out
2519 if (strlen(cm2) < strlen(cm)) {
2525 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2529 //----- Erase from cursor to end of line -----------------------
2530 static void clear_to_eol(void)
2532 write1(Ceol); // Erase from cursor to end of line
2535 //----- Erase from cursor to end of screen -----------------------
2536 static void clear_to_eos(void)
2538 write1(Ceos); // Erase from cursor to end of screen
2541 //----- Start standout mode ------------------------------------
2542 static void standout_start(void) // send "start reverse video" sequence
2544 write1(SOs); // Start reverse video mode
2547 //----- End standout mode --------------------------------------
2548 static void standout_end(void) // send "end reverse video" sequence
2550 write1(SOn); // End reverse video mode
2553 //----- Flash the screen --------------------------------------
2554 static void flash(int h)
2556 standout_start(); // send "start reverse video" sequence
2559 standout_end(); // send "end reverse video" sequence
2563 static void Indicate_Error(void)
2565 #if ENABLE_FEATURE_VI_CRASHME
2567 return; // generate a random command
2570 write1(bell); // send out a bell character
2576 //----- Screen[] Routines --------------------------------------
2577 //----- Erase the Screen[] memory ------------------------------
2578 static void screen_erase(void)
2580 memset(screen, ' ', screensize); // clear new screen
2583 static int bufsum(char *buf, int count)
2586 char *e = buf + count;
2589 sum += (unsigned char) *buf++;
2593 //----- Draw the status line at bottom of the screen -------------
2594 static void show_status_line(void)
2596 int cnt = 0, cksum = 0;
2598 // either we already have an error or status message, or we
2600 if (!have_status_msg) {
2601 cnt = format_edit_status();
2602 cksum = bufsum(status_buffer, cnt);
2604 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2605 last_status_cksum = cksum; // remember if we have seen this line
2606 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2607 write1(status_buffer);
2609 if (have_status_msg) {
2610 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2612 have_status_msg = 0;
2615 have_status_msg = 0;
2617 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2622 //----- format the status buffer, the bottom line of screen ------
2623 // format status buffer, with STANDOUT mode
2624 static void status_line_bold(const char *format, ...)
2628 va_start(args, format);
2629 strcpy(status_buffer, SOs); // Terminal standout mode on
2630 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2631 strcat(status_buffer, SOn); // Terminal standout mode off
2634 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2637 // format status buffer
2638 static void status_line(const char *format, ...)
2642 va_start(args, format);
2643 vsprintf(status_buffer, format, args);
2646 have_status_msg = 1;
2649 // copy s to buf, convert unprintable
2650 static void print_literal(char *buf, const char *s)
2663 c_is_no_print = (c & 0x80) && !Isprint(c);
2664 if (c_is_no_print) {
2668 if (c < ' ' || c == 127) {
2681 if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
2686 static void not_implemented(const char *s)
2688 char buf[MAX_INPUT_LEN];
2690 print_literal(buf, s);
2691 status_line_bold("\'%s\' is not implemented", buf);
2694 // show file status on status line
2695 static int format_edit_status(void)
2698 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2699 int cur, percent, ret, trunc_at;
2701 // file_modified is now a counter rather than a flag. this
2702 // helps reduce the amount of line counting we need to do.
2703 // (this will cause a mis-reporting of modified status
2704 // once every MAXINT editing operations.)
2706 // it would be nice to do a similar optimization here -- if
2707 // we haven't done a motion that could have changed which line
2708 // we're on, then we shouldn't have to do this count_lines()
2709 cur = count_lines(text, dot);
2711 // reduce counting -- the total lines can't have
2712 // changed if we haven't done any edits.
2713 if (file_modified != last_file_modified) {
2714 tot = cur + count_lines(dot, end - 1) - 1;
2715 last_file_modified = file_modified;
2718 // current line percent
2719 // ------------- ~~ ----------
2722 percent = (100 * cur) / tot;
2728 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2729 columns : STATUS_BUFFER_LEN-1;
2731 ret = snprintf(status_buffer, trunc_at+1,
2732 #if ENABLE_FEATURE_VI_READONLY
2733 "%c %s%s%s %d/%d %d%%",
2735 "%c %s%s %d/%d %d%%",
2737 cmd_mode_indicator[cmd_mode & 3],
2738 (current_filename != NULL ? current_filename : "No file"),
2739 #if ENABLE_FEATURE_VI_READONLY
2740 (readonly_mode ? " [Readonly]" : ""),
2742 (file_modified ? " [Modified]" : ""),
2745 if (ret >= 0 && ret < trunc_at)
2746 return ret; /* it all fit */
2748 return trunc_at; /* had to truncate */
2751 //----- Force refresh of all Lines -----------------------------
2752 static void redraw(int full_screen)
2754 place_cursor(0, 0, FALSE); // put cursor in correct place
2755 clear_to_eos(); // tel terminal to erase display
2756 screen_erase(); // erase the internal screen buffer
2757 last_status_cksum = 0; // force status update
2758 refresh(full_screen); // this will redraw the entire display
2762 //----- Format a text[] line into a buffer ---------------------
2763 static char* format_line(char *src /*, int li*/)
2768 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2770 c = '~'; // char in col 0 in non-existent lines is '~'
2772 while (co < columns + tabstop) {
2773 // have we gone past the end?
2778 if ((c & 0x80) && !Isprint(c)) {
2781 if (c < ' ' || c == 0x7f) {
2785 while ((co % tabstop) != (tabstop - 1)) {
2793 c += '@'; // Ctrl-X -> 'X'
2798 // discard scrolled-off-to-the-left portion,
2799 // in tabstop-sized pieces
2800 if (ofs >= tabstop && co >= tabstop) {
2801 memmove(dest, dest + tabstop, co);
2808 // check "short line, gigantic offset" case
2811 // discard last scrolled off part
2814 // fill the rest with spaces
2816 memset(&dest[co], ' ', columns - co);
2820 //----- Refresh the changed screen lines -----------------------
2821 // Copy the source line from text[] into the buffer and note
2822 // if the current screenline is different from the new buffer.
2823 // If they differ then that line needs redrawing on the terminal.
2825 static void refresh(int full_screen)
2827 static int old_offset;
2830 char *tp, *sp; // pointer into text[] and screen[]
2832 if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2833 int c = columns, r = rows;
2834 get_terminal_width_height(0, &columns, &rows);
2835 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2836 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2837 full_screen |= (c - columns) | (r - rows);
2839 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2840 tp = screenbegin; // index into text[] of top line
2842 // compare text[] to screen[] and mark screen[] lines that need updating
2843 for (li = 0; li < rows - 1; li++) {
2844 int cs, ce; // column start & end
2846 // format current text line
2847 out_buf = format_line(tp /*, li*/);
2849 // skip to the end of the current text[] line
2851 char *t = memchr(tp, '\n', end - tp);
2852 if (!t) t = end - 1;
2856 // see if there are any changes between vitual screen and out_buf
2857 changed = FALSE; // assume no change
2860 sp = &screen[li * columns]; // start of screen line
2862 // force re-draw of every single column from 0 - columns-1
2865 // compare newly formatted buffer with virtual screen
2866 // look forward for first difference between buf and screen
2867 for (; cs <= ce; cs++) {
2868 if (out_buf[cs] != sp[cs]) {
2869 changed = TRUE; // mark for redraw
2874 // look backward for last difference between out_buf and screen
2875 for (; ce >= cs; ce--) {
2876 if (out_buf[ce] != sp[ce]) {
2877 changed = TRUE; // mark for redraw
2881 // now, cs is index of first diff, and ce is index of last diff
2883 // if horz offset has changed, force a redraw
2884 if (offset != old_offset) {
2889 // make a sanity check of columns indexes
2891 if (ce > columns - 1) ce = columns - 1;
2892 if (cs > ce) { cs = 0; ce = columns - 1; }
2893 // is there a change between vitual screen and out_buf
2895 // copy changed part of buffer to virtual screen
2896 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2898 // move cursor to column of first change
2899 //if (offset != old_offset) {
2900 // // place_cursor is still too stupid
2901 // // to handle offsets correctly
2902 // place_cursor(li, cs, FALSE);
2904 place_cursor(li, cs, TRUE);
2907 // write line out to terminal
2908 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2912 place_cursor(crow, ccol, TRUE);
2914 old_offset = offset;
2917 //---------------------------------------------------------------------
2918 //----- the Ascii Chart -----------------------------------------------
2920 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2921 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2922 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2923 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2924 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2925 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2926 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2927 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2928 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2929 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2930 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2931 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2932 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2933 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2934 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2935 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2936 //---------------------------------------------------------------------
2938 //----- Execute a Vi Command -----------------------------------
2939 static void do_cmd(char c)
2941 const char *msg = msg; // for compiler
2942 char c1, *p, *q, *save_dot;
2944 int dir = dir; // for compiler
2947 // c1 = c; // quiet the compiler
2948 // cnt = yf = 0; // quiet the compiler
2949 // msg = p = q = save_dot = buf; // quiet the compiler
2950 memset(buf, '\0', 12);
2954 /* if this is a cursor key, skip these checks */
2967 if (cmd_mode == 2) {
2968 // flip-flop Insert/Replace mode
2969 if (c == VI_K_INSERT)
2971 // we are 'R'eplacing the current *dot with new char
2973 // don't Replace past E-o-l
2974 cmd_mode = 1; // convert to insert
2976 if (1 <= c || Isprint(c)) {
2978 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2979 dot = char_insert(dot, c); // insert new char
2984 if (cmd_mode == 1) {
2985 // hitting "Insert" twice means "R" replace mode
2986 if (c == VI_K_INSERT) goto dc5;
2987 // insert the char c at "dot"
2988 if (1 <= c || Isprint(c)) {
2989 dot = char_insert(dot, c);
3004 #if ENABLE_FEATURE_VI_CRASHME
3005 case 0x14: // dc4 ctrl-T
3006 crashme = (crashme == 0) ? 1 : 0;
3035 //case 'u': // u- FIXME- there is no undo
3037 default: // unrecognised command
3045 not_implemented(buf);
3046 end_cmd_q(); // stop adding to q
3047 case 0x00: // nul- ignore
3049 case 2: // ctrl-B scroll up full screen
3050 case VI_K_PAGEUP: // Cursor Key Page Up
3051 dot_scroll(rows - 2, -1);
3053 case 4: // ctrl-D scroll down half screen
3054 dot_scroll((rows - 2) / 2, 1);
3056 case 5: // ctrl-E scroll down one line
3059 case 6: // ctrl-F scroll down full screen
3060 case VI_K_PAGEDOWN: // Cursor Key Page Down
3061 dot_scroll(rows - 2, 1);
3063 case 7: // ctrl-G show current status
3064 last_status_cksum = 0; // force status update
3066 case 'h': // h- move left
3067 case VI_K_LEFT: // cursor key Left
3068 case 8: // ctrl-H- move left (This may be ERASE char)
3069 case 0x7f: // DEL- move left (This may be ERASE char)
3075 case 10: // Newline ^J
3076 case 'j': // j- goto next line, same col
3077 case VI_K_DOWN: // cursor key Down
3081 dot_next(); // go to next B-o-l
3082 dot = move_to_col(dot, ccol + offset); // try stay in same col
3084 case 12: // ctrl-L force redraw whole screen
3085 case 18: // ctrl-R force redraw
3086 place_cursor(0, 0, FALSE); // put cursor in correct place
3087 clear_to_eos(); // tel terminal to erase display
3089 screen_erase(); // erase the internal screen buffer
3090 last_status_cksum = 0; // force status update
3091 refresh(TRUE); // this will redraw the entire display
3093 case 13: // Carriage Return ^M
3094 case '+': // +- goto next line
3101 case 21: // ctrl-U scroll up half screen
3102 dot_scroll((rows - 2) / 2, -1);
3104 case 25: // ctrl-Y scroll up one line
3110 cmd_mode = 0; // stop insrting
3112 last_status_cksum = 0; // force status update
3114 case ' ': // move right
3115 case 'l': // move right
3116 case VI_K_RIGHT: // Cursor Key Right
3122 #if ENABLE_FEATURE_VI_YANKMARK
3123 case '"': // "- name a register to use for Delete/Yank
3124 c1 = get_one_char();
3132 case '\'': // '- goto a specific mark
3133 c1 = get_one_char();
3138 q = mark[(unsigned char) c1];
3139 if (text <= q && q < end) {
3141 dot_begin(); // go to B-o-l
3144 } else if (c1 == '\'') { // goto previous context
3145 dot = swap_context(dot); // swap current and previous context
3146 dot_begin(); // go to B-o-l
3152 case 'm': // m- Mark a line
3153 // this is really stupid. If there are any inserts or deletes
3154 // between text[0] and dot then this mark will not point to the
3155 // correct location! It could be off by many lines!
3156 // Well..., at least its quick and dirty.
3157 c1 = get_one_char();
3161 // remember the line
3162 mark[(int) c1] = dot;
3167 case 'P': // P- Put register before
3168 case 'p': // p- put register after
3171 status_line_bold("Nothing in register %c", what_reg());
3174 // are we putting whole lines or strings
3175 if (strchr(p, '\n') != NULL) {
3177 dot_begin(); // putting lines- Put above
3180 // are we putting after very last line?
3181 if (end_line(dot) == (end - 1)) {
3182 dot = end; // force dot to end of text[]
3184 dot_next(); // next line, then put before
3189 dot_right(); // move to right, can move to NL
3191 dot = string_insert(dot, p); // insert the string
3192 end_cmd_q(); // stop adding to q
3194 case 'U': // U- Undo; replace current line with original version
3195 if (reg[Ureg] != 0) {
3196 p = begin_line(dot);
3198 p = text_hole_delete(p, q); // delete cur line
3199 p = string_insert(p, reg[Ureg]); // insert orig line
3204 #endif /* FEATURE_VI_YANKMARK */
3205 case '$': // $- goto end of line
3206 case VI_K_END: // Cursor Key End
3210 dot = end_line(dot);
3212 case '%': // %- find matching char of pair () [] {}
3213 for (q = dot; q < end && *q != '\n'; q++) {
3214 if (strchr("()[]{}", *q) != NULL) {
3215 // we found half of a pair
3216 p = find_pair(q, *q);
3228 case 'f': // f- forward to a user specified char
3229 last_forward_char = get_one_char(); // get the search char
3231 // dont separate these two commands. 'f' depends on ';'
3233 //**** fall through to ... ';'
3234 case ';': // ;- look at rest of line for last forward char
3238 if (last_forward_char == 0)
3241 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3244 if (*q == last_forward_char)
3247 case ',': // repeat latest 'f' in opposite direction
3251 if (last_forward_char == 0)
3254 while (q >= text && *q != '\n' && *q != last_forward_char) {
3257 if (q >= text && *q == last_forward_char)
3261 case '-': // -- goto prev line
3268 #if ENABLE_FEATURE_VI_DOT_CMD
3269 case '.': // .- repeat the last modifying command
3270 // Stuff the last_modifying_cmd back into stdin
3271 // and let it be re-executed.
3272 if (last_modifying_cmd != NULL && lmc_len > 0) {
3273 last_modifying_cmd[lmc_len] = 0;
3274 ioq = ioq_start = xstrdup(last_modifying_cmd);
3278 #if ENABLE_FEATURE_VI_SEARCH
3279 case '?': // /- search for a pattern
3280 case '/': // /- search for a pattern
3283 q = get_input_line(buf); // get input line- use "status line"
3284 if (q[0] && !q[1]) {
3285 if (last_search_pattern[0])
3286 last_search_pattern[0] = c;
3287 goto dc3; // if no pat re-use old pat
3289 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3290 // there is a new pat
3291 free(last_search_pattern);
3292 last_search_pattern = xstrdup(q);
3293 goto dc3; // now find the pattern
3295 // user changed mind and erased the "/"- do nothing
3297 case 'N': // N- backward search for last pattern
3301 dir = BACK; // assume BACKWARD search
3303 if (last_search_pattern[0] == '?') {
3307 goto dc4; // now search for pattern
3309 case 'n': // n- repeat search for last pattern
3310 // search rest of text[] starting at next char
3311 // if search fails return orignal "p" not the "p+1" address
3316 if (last_search_pattern == 0) {
3317 msg = "No previous regular expression";
3320 if (last_search_pattern[0] == '/') {
3321 dir = FORWARD; // assume FORWARD search
3324 if (last_search_pattern[0] == '?') {
3329 q = char_search(p, last_search_pattern + 1, dir, FULL);
3331 dot = q; // good search, update "dot"
3335 // no pattern found between "dot" and "end"- continue at top
3340 q = char_search(p, last_search_pattern + 1, dir, FULL);
3341 if (q != NULL) { // found something
3342 dot = q; // found new pattern- goto it
3343 msg = "search hit BOTTOM, continuing at TOP";
3345 msg = "search hit TOP, continuing at BOTTOM";
3348 msg = "Pattern not found";
3352 status_line_bold("%s", msg);
3354 case '{': // {- move backward paragraph
3355 q = char_search(dot, "\n\n", BACK, FULL);
3356 if (q != NULL) { // found blank line
3357 dot = next_line(q); // move to next blank line
3360 case '}': // }- move forward paragraph
3361 q = char_search(dot, "\n\n", FORWARD, FULL);
3362 if (q != NULL) { // found blank line
3363 dot = next_line(q); // move to next blank line
3366 #endif /* FEATURE_VI_SEARCH */
3367 case '0': // 0- goto begining of line
3377 if (c == '0' && cmdcnt < 1) {
3378 dot_begin(); // this was a standalone zero
3380 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3383 case ':': // :- the colon mode commands
3384 p = get_input_line(":"); // get input line- use "status line"
3385 #if ENABLE_FEATURE_VI_COLON
3386 colon(p); // execute the command
3389 p++; // move past the ':'
3393 if (strncasecmp(p, "quit", cnt) == 0
3394 || strncasecmp(p, "q!", cnt) == 0 // delete lines
3396 if (file_modified && p[1] != '!') {
3397 status_line_bold("No write since last change (:quit! overrides)");
3401 } else if (strncasecmp(p, "write", cnt) == 0
3402 || strncasecmp(p, "wq", cnt) == 0
3403 || strncasecmp(p, "wn", cnt) == 0
3404 || strncasecmp(p, "x", cnt) == 0
3406 cnt = file_write(current_filename, text, end - 1);
3409 status_line_bold("Write error: %s", strerror(errno));
3412 last_file_modified = -1;
3413 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3414 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3415 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3420 } else if (strncasecmp(p, "file", cnt) == 0) {
3421 last_status_cksum = 0; // force status update
3422 } else if (sscanf(p, "%d", &j) > 0) {
3423 dot = find_line(j); // go to line # j
3425 } else { // unrecognised cmd
3428 #endif /* !FEATURE_VI_COLON */
3430 case '<': // <- Left shift something
3431 case '>': // >- Right shift something
3432 cnt = count_lines(text, dot); // remember what line we are on
3433 c1 = get_one_char(); // get the type of thing to delete
3434 find_range(&p, &q, c1);
3435 yank_delete(p, q, 1, YANKONLY); // save copy before change
3438 i = count_lines(p, q); // # of lines we are shifting
3439 for ( ; i > 0; i--, p = next_line(p)) {
3441 // shift left- remove tab or 8 spaces
3443 // shrink buffer 1 char
3444 text_hole_delete(p, p);
3445 } else if (*p == ' ') {
3446 // we should be calculating columns, not just SPACE
3447 for (j = 0; *p == ' ' && j < tabstop; j++) {
3448 text_hole_delete(p, p);
3451 } else if (c == '>') {
3452 // shift right -- add tab or 8 spaces
3453 char_insert(p, '\t');
3456 dot = find_line(cnt); // what line were we on
3458 end_cmd_q(); // stop adding to q
3460 case 'A': // A- append at e-o-l
3461 dot_end(); // go to e-o-l
3462 //**** fall through to ... 'a'
3463 case 'a': // a- append after current char
3468 case 'B': // B- back a blank-delimited Word
3469 case 'E': // E- end of a blank-delimited word
3470 case 'W': // W- forward a blank-delimited word
3477 if (c == 'W' || isspace(dot[dir])) {
3478 dot = skip_thing(dot, 1, dir, S_TO_WS);
3479 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3482 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3484 case 'C': // C- Change to e-o-l
3485 case 'D': // D- delete to e-o-l
3487 dot = dollar_line(dot); // move to before NL
3488 // copy text into a register and delete
3489 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3491 goto dc_i; // start inserting
3492 #if ENABLE_FEATURE_VI_DOT_CMD
3494 end_cmd_q(); // stop adding to q
3497 case 'g': // 'gg' goto a line number (from vim)
3498 // (default to first line in file)
3499 c1 = get_one_char();
3504 not_implemented(buf);
3510 case 'G': // G- goto to a line number (default= E-O-F)
3511 dot = end - 1; // assume E-O-F
3513 dot = find_line(cmdcnt); // what line is #cmdcnt
3517 case 'H': // H- goto top line on screen
3519 if (cmdcnt > (rows - 1)) {
3520 cmdcnt = (rows - 1);
3527 case 'I': // I- insert before first non-blank
3530 //**** fall through to ... 'i'
3531 case 'i': // i- insert before current char
3532 case VI_K_INSERT: // Cursor Key Insert
3534 cmd_mode = 1; // start insrting
3536 case 'J': // J- join current and next lines together
3540 dot_end(); // move to NL
3541 if (dot < end - 1) { // make sure not last char in text[]
3542 *dot++ = ' '; // replace NL with space
3544 while (isblank(*dot)) { // delete leading WS
3548 end_cmd_q(); // stop adding to q
3550 case 'L': // L- goto bottom line on screen
3552 if (cmdcnt > (rows - 1)) {
3553 cmdcnt = (rows - 1);
3561 case 'M': // M- goto middle line on screen
3563 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3564 dot = next_line(dot);
3566 case 'O': // O- open a empty line above
3568 p = begin_line(dot);
3569 if (p[-1] == '\n') {
3571 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3573 dot = char_insert(dot, '\n');
3576 dot = char_insert(dot, '\n'); // i\n ESC
3581 case 'R': // R- continuous Replace char
3588 case 'X': // X- delete char before dot
3589 case 'x': // x- delete the current char
3590 case 's': // s- substitute the current char
3597 if (dot[dir] != '\n') {
3599 dot--; // delete prev char
3600 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3603 goto dc_i; // start insrting
3604 end_cmd_q(); // stop adding to q
3606 case 'Z': // Z- if modified, {write}; exit
3607 // ZZ means to save file (if necessary), then exit
3608 c1 = get_one_char();
3613 if (file_modified) {
3614 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3615 status_line_bold("\"%s\" File is read only", current_filename);
3618 cnt = file_write(current_filename, text, end - 1);
3621 status_line_bold("Write error: %s", strerror(errno));
3622 } else if (cnt == (end - 1 - text + 1)) {
3629 case '^': // ^- move to first non-blank on line
3633 case 'b': // b- back a word
3634 case 'e': // e- end of word
3641 if ((dot + dir) < text || (dot + dir) > end - 1)
3644 if (isspace(*dot)) {
3645 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3647 if (isalnum(*dot) || *dot == '_') {
3648 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3649 } else if (ispunct(*dot)) {
3650 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3653 case 'c': // c- change something
3654 case 'd': // d- delete something
3655 #if ENABLE_FEATURE_VI_YANKMARK
3656 case 'y': // y- yank something
3657 case 'Y': // Y- Yank a line
3660 int yf, ml, whole = 0;
3661 yf = YANKDEL; // assume either "c" or "d"
3662 #if ENABLE_FEATURE_VI_YANKMARK
3663 if (c == 'y' || c == 'Y')
3668 c1 = get_one_char(); // get the type of thing to delete
3669 // determine range, and whether it spans lines
3670 ml = find_range(&p, &q, c1);
3671 if (c1 == 27) { // ESC- user changed mind and wants out
3672 c = c1 = 27; // Escape- do nothing
3673 } else if (strchr("wW", c1)) {
3675 // don't include trailing WS as part of word
3676 while (isblank(*q)) {
3677 if (q <= text || q[-1] == '\n')
3682 dot = yank_delete(p, q, ml, yf); // delete word
3683 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3684 // partial line copy text into a register and delete
3685 dot = yank_delete(p, q, ml, yf); // delete word
3686 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3687 // whole line copy text into a register and delete
3688 dot = yank_delete(p, q, ml, yf); // delete lines
3691 // could not recognize object
3692 c = c1 = 27; // error-
3698 dot = char_insert(dot, '\n');
3699 // on the last line of file don't move to prev line
3700 if (whole && dot != (end-1)) {
3703 } else if (c == 'd') {
3709 // if CHANGING, not deleting, start inserting after the delete
3711 strcpy(buf, "Change");
3712 goto dc_i; // start inserting
3715 strcpy(buf, "Delete");
3717 #if ENABLE_FEATURE_VI_YANKMARK
3718 if (c == 'y' || c == 'Y') {
3719 strcpy(buf, "Yank");
3723 for (cnt = 0; p <= q; p++) {
3727 status_line("%s %d lines (%d chars) using [%c]",
3728 buf, cnt, strlen(reg[YDreg]), what_reg());
3730 end_cmd_q(); // stop adding to q
3734 case 'k': // k- goto prev line, same col
3735 case VI_K_UP: // cursor key Up
3740 dot = move_to_col(dot, ccol + offset); // try stay in same col
3742 case 'r': // r- replace the current char with user input
3743 c1 = get_one_char(); // get the replacement char
3746 file_modified++; // has the file been modified
3748 end_cmd_q(); // stop adding to q
3750 case 't': // t- move to char prior to next x
3751 last_forward_char = get_one_char();
3753 if (*dot == last_forward_char)
3755 last_forward_char= 0;
3757 case 'w': // w- forward a word
3761 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3762 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3763 } else if (ispunct(*dot)) { // we are on PUNCT
3764 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3767 dot++; // move over word
3768 if (isspace(*dot)) {
3769 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3773 c1 = get_one_char(); // get the replacement char
3776 cnt = (rows - 2) / 2; // put dot at center
3778 cnt = rows - 2; // put dot at bottom
3779 screenbegin = begin_line(dot); // start dot at top
3780 dot_scroll(cnt, -1);
3782 case '|': // |- move to column "cmdcnt"
3783 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3785 case '~': // ~- flip the case of letters a-z -> A-Z
3789 if (islower(*dot)) {
3790 *dot = toupper(*dot);
3791 file_modified++; // has the file been modified
3792 } else if (isupper(*dot)) {
3793 *dot = tolower(*dot);
3794 file_modified++; // has the file been modified
3797 end_cmd_q(); // stop adding to q
3799 //----- The Cursor and Function Keys -----------------------------
3800 case VI_K_HOME: // Cursor Key Home
3803 // The Fn keys could point to do_macro which could translate them
3804 case VI_K_FUN1: // Function Key F1
3805 case VI_K_FUN2: // Function Key F2
3806 case VI_K_FUN3: // Function Key F3
3807 case VI_K_FUN4: // Function Key F4
3808 case VI_K_FUN5: // Function Key F5
3809 case VI_K_FUN6: // Function Key F6
3810 case VI_K_FUN7: // Function Key F7
3811 case VI_K_FUN8: // Function Key F8
3812 case VI_K_FUN9: // Function Key F9
3813 case VI_K_FUN10: // Function Key F10
3814 case VI_K_FUN11: // Function Key F11
3815 case VI_K_FUN12: // Function Key F12
3820 // if text[] just became empty, add back an empty line
3822 char_insert(text, '\n'); // start empty buf with dummy line
3825 // it is OK for dot to exactly equal to end, otherwise check dot validity
3827 dot = bound_dot(dot); // make sure "dot" is valid
3829 #if ENABLE_FEATURE_VI_YANKMARK
3830 check_context(c); // update the current context
3834 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3835 cnt = dot - begin_line(dot);
3836 // Try to stay off of the Newline
3837 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3841 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3842 #if ENABLE_FEATURE_VI_CRASHME
3843 static int totalcmds = 0;
3844 static int Mp = 85; // Movement command Probability
3845 static int Np = 90; // Non-movement command Probability
3846 static int Dp = 96; // Delete command Probability
3847 static int Ip = 97; // Insert command Probability
3848 static int Yp = 98; // Yank command Probability
3849 static int Pp = 99; // Put command Probability
3850 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3851 static const char chars[20] = "\t012345 abcdABCD-=.$";
3852 static const char *const words[20] = {
3853 "this", "is", "a", "test",
3854 "broadcast", "the", "emergency", "of",
3855 "system", "quick", "brown", "fox",
3856 "jumped", "over", "lazy", "dogs",
3857 "back", "January", "Febuary", "March"
3859 static const char *const lines[20] = {
3860 "You should have received a copy of the GNU General Public License\n",
3861 "char c, cm, *cmd, *cmd1;\n",
3862 "generate a command by percentages\n",
3863 "Numbers may be typed as a prefix to some commands.\n",
3864 "Quit, discarding changes!\n",
3865 "Forced write, if permission originally not valid.\n",
3866 "In general, any ex or ed command (such as substitute or delete).\n",
3867 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3868 "Please get w/ me and I will go over it with you.\n",
3869 "The following is a list of scheduled, committed changes.\n",
3870 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3871 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3872 "Any question about transactions please contact Sterling Huxley.\n",
3873 "I will try to get back to you by Friday, December 31.\n",
3874 "This Change will be implemented on Friday.\n",
3875 "Let me know if you have problems accessing this;\n",
3876 "Sterling Huxley recently added you to the access list.\n",
3877 "Would you like to go to lunch?\n",
3878 "The last command will be automatically run.\n",
3879 "This is too much english for a computer geek.\n",
3881 static char *multilines[20] = {
3882 "You should have received a copy of the GNU General Public License\n",
3883 "char c, cm, *cmd, *cmd1;\n",
3884 "generate a command by percentages\n",
3885 "Numbers may be typed as a prefix to some commands.\n",
3886 "Quit, discarding changes!\n",
3887 "Forced write, if permission originally not valid.\n",
3888 "In general, any ex or ed command (such as substitute or delete).\n",
3889 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3890 "Please get w/ me and I will go over it with you.\n",
3891 "The following is a list of scheduled, committed changes.\n",
3892 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3893 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3894 "Any question about transactions please contact Sterling Huxley.\n",
3895 "I will try to get back to you by Friday, December 31.\n",
3896 "This Change will be implemented on Friday.\n",
3897 "Let me know if you have problems accessing this;\n",
3898 "Sterling Huxley recently added you to the access list.\n",
3899 "Would you like to go to lunch?\n",
3900 "The last command will be automatically run.\n",
3901 "This is too much english for a computer geek.\n",
3904 // create a random command to execute
3905 static void crash_dummy()
3907 static int sleeptime; // how long to pause between commands
3908 char c, cm, *cmd, *cmd1;
3909 int i, cnt, thing, rbi, startrbi, percent;
3911 // "dot" movement commands
3912 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3914 // is there already a command running?
3915 if (chars_to_parse > 0)
3919 sleeptime = 0; // how long to pause between commands
3920 memset(readbuffer, '\0', sizeof(readbuffer));
3921 // generate a command by percentages
3922 percent = (int) lrand48() % 100; // get a number from 0-99
3923 if (percent < Mp) { // Movement commands
3924 // available commands
3927 } else if (percent < Np) { // non-movement commands
3928 cmd = "mz<>\'\""; // available commands
3930 } else if (percent < Dp) { // Delete commands
3931 cmd = "dx"; // available commands
3933 } else if (percent < Ip) { // Inset commands
3934 cmd = "iIaAsrJ"; // available commands
3936 } else if (percent < Yp) { // Yank commands
3937 cmd = "yY"; // available commands
3939 } else if (percent < Pp) { // Put commands
3940 cmd = "pP"; // available commands
3943 // We do not know how to handle this command, try again
3947 // randomly pick one of the available cmds from "cmd[]"
3948 i = (int) lrand48() % strlen(cmd);
3950 if (strchr(":\024", cm))
3951 goto cd0; // dont allow colon or ctrl-T commands
3952 readbuffer[rbi++] = cm; // put cmd into input buffer
3954 // now we have the command-
3955 // there are 1, 2, and multi char commands
3956 // find out which and generate the rest of command as necessary
3957 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3958 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3959 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3960 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3962 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3964 readbuffer[rbi++] = c; // add movement to input buffer
3966 if (strchr("iIaAsc", cm)) { // multi-char commands
3968 // change some thing
3969 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3971 readbuffer[rbi++] = c; // add movement to input buffer
3973 thing = (int) lrand48() % 4; // what thing to insert
3974 cnt = (int) lrand48() % 10; // how many to insert
3975 for (i = 0; i < cnt; i++) {
3976 if (thing == 0) { // insert chars
3977 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3978 } else if (thing == 1) { // insert words
3979 strcat(readbuffer, words[(int) lrand48() % 20]);
3980 strcat(readbuffer, " ");
3981 sleeptime = 0; // how fast to type
3982 } else if (thing == 2) { // insert lines
3983 strcat(readbuffer, lines[(int) lrand48() % 20]);
3984 sleeptime = 0; // how fast to type
3985 } else { // insert multi-lines
3986 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3987 sleeptime = 0; // how fast to type
3990 strcat(readbuffer, "\033");
3992 chars_to_parse = strlen(readbuffer);
3996 mysleep(sleeptime); // sleep 1/100 sec
3999 // test to see if there are any errors
4000 static void crash_test()
4002 static time_t oldtim;
4009 strcat(msg, "end<text ");
4011 if (end > textend) {
4012 strcat(msg, "end>textend ");
4015 strcat(msg, "dot<text ");
4018 strcat(msg, "dot>end ");
4020 if (screenbegin < text) {
4021 strcat(msg, "screenbegin<text ");
4023 if (screenbegin > end - 1) {
4024 strcat(msg, "screenbegin>end-1 ");
4028 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4029 totalcmds, last_input_char, msg, SOs, SOn);
4031 while (safe_read(0, d, 1) > 0) {
4032 if (d[0] == '\n' || d[0] == '\r')
4037 if (tim >= (oldtim + 3)) {
4038 sprintf(status_buffer,
4039 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4040 totalcmds, M, N, I, D, Y, P, U, end - text + 1);