1/* vi: set sw=4 ts=4: */ 2/* 3 * tiny vi.c: A small 'vi' clone 4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com> 5 * 6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. 7 */ 8 9/* 10 * Things To Do: 11 * EXINIT 12 * $HOME/.exrc and ./.exrc 13 * add magic to search /foo.*bar 14 * add :help command 15 * :map macros 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" 22 */ 23 24#include "libbb.h" 25 26/* the CRASHME code is unmaintained, and doesn't currently build */ 27#define ENABLE_FEATURE_VI_CRASHME 0 28 29 30#if ENABLE_LOCALE_SUPPORT 31 32#if ENABLE_FEATURE_VI_8BIT 33//FIXME: this does not work properly for Unicode anyway 34# define Isprint(c) (isprint)(c) 35#else 36# define Isprint(c) isprint_asciionly(c) 37#endif 38 39#else 40 41/* 0x9b is Meta-ESC */ 42#if ENABLE_FEATURE_VI_8BIT 43#define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b) 44#else 45#define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f) 46#endif 47 48#endif 49 50 51enum { 52 MAX_TABSTOP = 32, // sanity limit 53 // User input len. Need not be extra big. 54 // Lines in file being edited *can* be bigger than this. 55 MAX_INPUT_LEN = 128, 56 // Sanity limits. We have only one buffer of this size. 57 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN, 58 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN, 59}; 60 61/* vt102 typical ESC sequence */ 62/* terminal standout start/normal ESC sequence */ 63#define SOs "\033[7m" 64#define SOn "\033[0m" 65/* terminal bell sequence */ 66#define bell "\007" 67/* Clear-end-of-line and Clear-end-of-screen ESC sequence */ 68#define Ceol "\033[K" 69#define Ceos "\033[J" 70/* Cursor motion arbitrary destination ESC sequence */ 71#define CMrc "\033[%u;%uH" 72/* Cursor motion up and down ESC sequence */ 73#define CMup "\033[A" 74#define CMdown "\n" 75 76#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK 77// cmds modifying text[] 78// vda: removed "aAiIs" as they switch us into insert mode 79// and remembering input for replay after them makes no sense 80static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~"; 81#endif 82 83enum { 84 YANKONLY = FALSE, 85 YANKDEL = TRUE, 86 FORWARD = 1, // code depends on "1" for array index 87 BACK = -1, // code depends on "-1" for array index 88 LIMITED = 0, // how much of text[] in char_search 89 FULL = 1, // how much of text[] in char_search 90 91 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot" 92 S_TO_WS = 2, // used in skip_thing() for moving "dot" 93 S_OVER_WS = 3, // used in skip_thing() for moving "dot" 94 S_END_PUNCT = 4, // used in skip_thing() for moving "dot" 95 S_END_ALNUM = 5, // used in skip_thing() for moving "dot" 96}; 97 98 99/* vi.c expects chars to be unsigned. */ 100/* busybox build system provides that, but it's better */ 101/* to audit and fix the source */ 102 103struct globals { 104 /* many references - keep near the top of globals */ 105 char *text, *end; // pointers to the user data in memory 106 char *dot; // where all the action takes place 107 int text_size; // size of the allocated buffer 108 109 /* the rest */ 110 smallint vi_setops; 111#define VI_AUTOINDENT 1 112#define VI_SHOWMATCH 2 113#define VI_IGNORECASE 4 114#define VI_ERR_METHOD 8 115#define autoindent (vi_setops & VI_AUTOINDENT) 116#define showmatch (vi_setops & VI_SHOWMATCH ) 117#define ignorecase (vi_setops & VI_IGNORECASE) 118/* indicate error with beep or flash */ 119#define err_method (vi_setops & VI_ERR_METHOD) 120 121#if ENABLE_FEATURE_VI_READONLY 122 smallint readonly_mode; 123#define SET_READONLY_FILE(flags) ((flags) |= 0x01) 124#define SET_READONLY_MODE(flags) ((flags) |= 0x02) 125#define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe) 126#else 127#define SET_READONLY_FILE(flags) ((void)0) 128#define SET_READONLY_MODE(flags) ((void)0) 129#define UNSET_READONLY_FILE(flags) ((void)0) 130#endif 131 132 smallint editing; // >0 while we are editing a file 133 // [code audit says "can be 0, 1 or 2 only"] 134 smallint cmd_mode; // 0=command 1=insert 2=replace 135 int file_modified; // buffer contents changed (counter, not flag!) 136 int last_file_modified; // = -1; 137 int fn_start; // index of first cmd line file name 138 int save_argc; // how many file names on cmd line 139 int cmdcnt; // repetition count 140 unsigned rows, columns; // the terminal screen is this size 141#if ENABLE_FEATURE_VI_ASK_TERMINAL 142 int get_rowcol_error; 143#endif 144 int crow, ccol; // cursor is on Crow x Ccol 145 int offset; // chars scrolled off the screen to the left 146 int have_status_msg; // is default edit status needed? 147 // [don't make smallint!] 148 int last_status_cksum; // hash of current status line 149 char *current_filename; 150 char *screenbegin; // index into text[], of top line on the screen 151 char *screen; // pointer to the virtual screen buffer 152 int screensize; // and its size 153 int tabstop; 154 int last_forward_char; // last char searched for with 'f' (int because of Unicode) 155 char erase_char; // the users erase character 156 char last_input_char; // last char read from user 157 158#if ENABLE_FEATURE_VI_DOT_CMD 159 smallint adding2q; // are we currently adding user input to q 160 int lmc_len; // length of last_modifying_cmd 161 char *ioq, *ioq_start; // pointer to string for get_one_char to "read" 162#endif 163#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR 164 int last_row; // where the cursor was last moved to 165#endif 166#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME 167 int my_pid; 168#endif 169#if ENABLE_FEATURE_VI_SEARCH 170 char *last_search_pattern; // last pattern from a '/' or '?' search 171#endif 172 173 /* former statics */ 174#if ENABLE_FEATURE_VI_YANKMARK 175 char *edit_file__cur_line; 176#endif 177 int refresh__old_offset; 178 int format_edit_status__tot; 179 180 /* a few references only */ 181#if ENABLE_FEATURE_VI_YANKMARK 182 int YDreg, Ureg; // default delete register and orig line for "U" 183 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27 184 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context '' 185 char *context_start, *context_end; 186#endif 187#if ENABLE_FEATURE_VI_USE_SIGNALS 188 sigjmp_buf restart; // catch_sig() 189#endif 190 struct termios term_orig, term_vi; // remember what the cooked mode was 191#if ENABLE_FEATURE_VI_COLON 192 char *initial_cmds[3]; // currently 2 entries, NULL terminated 193#endif 194 // Should be just enough to hold a key sequence, 195 // but CRASHME mode uses it as generated command buffer too 196#if ENABLE_FEATURE_VI_CRASHME 197 char readbuffer[128]; 198#else 199 char readbuffer[KEYCODE_BUFFER_SIZE]; 200#endif 201#define STATUS_BUFFER_LEN 200 202 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user 203#if ENABLE_FEATURE_VI_DOT_CMD 204 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "." 205#endif 206 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ 207 208 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; 209}; 210#define G (*ptr_to_globals) 211#define text (G.text ) 212#define text_size (G.text_size ) 213#define end (G.end ) 214#define dot (G.dot ) 215#define reg (G.reg ) 216 217#define vi_setops (G.vi_setops ) 218#define editing (G.editing ) 219#define cmd_mode (G.cmd_mode ) 220#define file_modified (G.file_modified ) 221#define last_file_modified (G.last_file_modified ) 222#define fn_start (G.fn_start ) 223#define save_argc (G.save_argc ) 224#define cmdcnt (G.cmdcnt ) 225#define rows (G.rows ) 226#define columns (G.columns ) 227#define crow (G.crow ) 228#define ccol (G.ccol ) 229#define offset (G.offset ) 230#define status_buffer (G.status_buffer ) 231#define have_status_msg (G.have_status_msg ) 232#define last_status_cksum (G.last_status_cksum ) 233#define current_filename (G.current_filename ) 234#define screen (G.screen ) 235#define screensize (G.screensize ) 236#define screenbegin (G.screenbegin ) 237#define tabstop (G.tabstop ) 238#define last_forward_char (G.last_forward_char ) 239#define erase_char (G.erase_char ) 240#define last_input_char (G.last_input_char ) 241#if ENABLE_FEATURE_VI_READONLY 242#define readonly_mode (G.readonly_mode ) 243#else 244#define readonly_mode 0 245#endif 246#define adding2q (G.adding2q ) 247#define lmc_len (G.lmc_len ) 248#define ioq (G.ioq ) 249#define ioq_start (G.ioq_start ) 250#define last_row (G.last_row ) 251#define my_pid (G.my_pid ) 252#define last_search_pattern (G.last_search_pattern) 253 254#define edit_file__cur_line (G.edit_file__cur_line) 255#define refresh__old_offset (G.refresh__old_offset) 256#define format_edit_status__tot (G.format_edit_status__tot) 257 258#define YDreg (G.YDreg ) 259#define Ureg (G.Ureg ) 260#define mark (G.mark ) 261#define context_start (G.context_start ) 262#define context_end (G.context_end ) 263#define restart (G.restart ) 264#define term_orig (G.term_orig ) 265#define term_vi (G.term_vi ) 266#define initial_cmds (G.initial_cmds ) 267#define readbuffer (G.readbuffer ) 268#define scr_out_buf (G.scr_out_buf ) 269#define last_modifying_cmd (G.last_modifying_cmd ) 270#define get_input_line__buf (G.get_input_line__buf) 271 272#define INIT_G() do { \ 273 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ 274 last_file_modified = -1; \ 275 /* "" but has space for 2 chars: */ \ 276 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \ 277} while (0) 278 279 280static int init_text_buffer(char *); // init from file or create new 281static void edit_file(char *); // edit one file 282static void do_cmd(int); // execute a command 283static int next_tabstop(int); 284static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot 285static char *begin_line(char *); // return pointer to cur line B-o-l 286static char *end_line(char *); // return pointer to cur line E-o-l 287static char *prev_line(char *); // return pointer to prev line B-o-l 288static char *next_line(char *); // return pointer to next line B-o-l 289static char *end_screen(void); // get pointer to last char on screen 290static int count_lines(char *, char *); // count line from start to stop 291static char *find_line(int); // find begining of line #li 292static char *move_to_col(char *, int); // move "p" to column l 293static void dot_left(void); // move dot left- dont leave line 294static void dot_right(void); // move dot right- dont leave line 295static void dot_begin(void); // move dot to B-o-l 296static void dot_end(void); // move dot to E-o-l 297static void dot_next(void); // move dot to next line B-o-l 298static void dot_prev(void); // move dot to prev line B-o-l 299static void dot_scroll(int, int); // move the screen up or down 300static void dot_skip_over_ws(void); // move dot pat WS 301static void dot_delete(void); // delete the char at 'dot' 302static char *bound_dot(char *); // make sure text[0] <= P < "end" 303static char *new_screen(int, int); // malloc virtual screen memory 304static char *char_insert(char *, char); // insert the char c at 'p' 305// might reallocate text[]! use p += stupid_insert(p, ...), 306// and be careful to not use pointers into potentially freed text[]! 307static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' 308static int find_range(char **, char **, char); // return pointers for an object 309static int st_test(char *, int, int, char *); // helper for skip_thing() 310static char *skip_thing(char *, int, int, int); // skip some object 311static char *find_pair(char *, char); // find matching pair () [] {} 312static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole 313// might reallocate text[]! use p += text_hole_make(p, ...), 314// and be careful to not use pointers into potentially freed text[]! 315static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole 316static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete 317static void show_help(void); // display some help info 318static void rawmode(void); // set "raw" mode on tty 319static void cookmode(void); // return to "cooked" mode on tty 320// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) 321static int mysleep(int); 322static int readit(void); // read (maybe cursor) key from stdin 323static int get_one_char(void); // read 1 char from stdin 324static int file_size(const char *); // what is the byte size of "fn" 325#if !ENABLE_FEATURE_VI_READONLY 326#define file_insert(fn, p, update_ro_status) file_insert(fn, p) 327#endif 328// file_insert might reallocate text[]! 329static int file_insert(const char *, char *, int); 330static int file_write(char *, char *, char *); 331#if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR 332#define place_cursor(a, b, optimize) place_cursor(a, b) 333#endif 334static void place_cursor(int, int, int); 335static void screen_erase(void); 336static void clear_to_eol(void); 337static void clear_to_eos(void); 338static void go_bottom_and_clear_to_eol(void); 339static void standout_start(void); // send "start reverse video" sequence 340static void standout_end(void); // send "end reverse video" sequence 341static void flash(int); // flash the terminal screen 342static void show_status_line(void); // put a message on the bottom line 343static void status_line(const char *, ...); // print to status buf 344static void status_line_bold(const char *, ...); 345static void not_implemented(const char *); // display "Not implemented" message 346static int format_edit_status(void); // format file status on status line 347static void redraw(int); // force a full screen refresh 348static char* format_line(char* /*, int*/); 349static void refresh(int); // update the terminal from screen[] 350 351static void Indicate_Error(void); // use flash or beep to indicate error 352#define indicate_error(c) Indicate_Error() 353static void Hit_Return(void); 354 355#if ENABLE_FEATURE_VI_SEARCH 356static char *char_search(char *, const char *, int, int); // search for pattern starting at p 357static int mycmp(const char *, const char *, int); // string cmp based in "ignorecase" 358#endif 359#if ENABLE_FEATURE_VI_COLON 360static char *get_one_address(char *, int *); // get colon addr, if present 361static char *get_address(char *, int *, int *); // get two colon addrs, if present 362static void colon(char *); // execute the "colon" mode cmds 363#endif 364#if ENABLE_FEATURE_VI_USE_SIGNALS 365static void winch_sig(int); // catch window size changes 366static void suspend_sig(int); // catch ctrl-Z 367static void catch_sig(int); // catch ctrl-C and alarm time-outs 368#endif 369#if ENABLE_FEATURE_VI_DOT_CMD 370static void start_new_cmd_q(char); // new queue for command 371static void end_cmd_q(void); // stop saving input chars 372#else 373#define end_cmd_q() ((void)0) 374#endif 375#if ENABLE_FEATURE_VI_SETOPTS 376static void showmatching(char *); // show the matching pair () [] {} 377#endif 378#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME 379// might reallocate text[]! use p += string_insert(p, ...), 380// and be careful to not use pointers into potentially freed text[]! 381static uintptr_t string_insert(char *, const char *); // insert the string at 'p' 382#endif 383#if ENABLE_FEATURE_VI_YANKMARK 384static char *text_yank(char *, char *, int); // save copy of "p" into a register 385static char what_reg(void); // what is letter of current YDreg 386static void check_context(char); // remember context for '' command 387#endif 388#if ENABLE_FEATURE_VI_CRASHME 389static void crash_dummy(); 390static void crash_test(); 391static int crashme = 0; 392#endif 393 394 395static void write1(const char *out) 396{ 397 fputs(out, stdout); 398} 399 400int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 401int vi_main(int argc, char **argv) 402{ 403 int c; 404 405 INIT_G(); 406 407#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME 408 my_pid = getpid(); 409#endif 410#if ENABLE_FEATURE_VI_CRASHME 411 srand((long) my_pid); 412#endif 413#ifdef NO_SUCH_APPLET_YET 414 /* If we aren't "vi", we are "view" */ 415 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) { 416 SET_READONLY_MODE(readonly_mode); 417 } 418#endif 419 420 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE; 421 // 1- process $HOME/.exrc file (not inplemented yet) 422 // 2- process EXINIT variable from environment 423 // 3- process command line args 424#if ENABLE_FEATURE_VI_COLON 425 { 426 char *p = getenv("EXINIT"); 427 if (p && *p) 428 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN); 429 } 430#endif 431 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) { 432 switch (c) { 433#if ENABLE_FEATURE_VI_CRASHME 434 case 'C': 435 crashme = 1; 436 break; 437#endif 438#if ENABLE_FEATURE_VI_READONLY 439 case 'R': // Read-only flag 440 SET_READONLY_MODE(readonly_mode); 441 break; 442#endif 443#if ENABLE_FEATURE_VI_COLON 444 case 'c': // cmd line vi command 445 if (*optarg) 446 initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN); 447 break; 448#endif 449 case 'H': 450 show_help(); 451 /* fall through */ 452 default: 453 bb_show_usage(); 454 return 1; 455 } 456 } 457 458 // The argv array can be used by the ":next" and ":rewind" commands 459 // save optind. 460 fn_start = optind; // remember first file name for :next and :rew 461 save_argc = argc; 462 463 //----- This is the main file handling loop -------------- 464 while (1) { 465 edit_file(argv[optind]); /* param might be NULL */ 466 if (++optind >= argc) 467 break; 468 } 469 //----------------------------------------------------------- 470 471 return 0; 472} 473 474/* read text from file or create an empty buf */ 475/* will also update current_filename */ 476static int init_text_buffer(char *fn) 477{ 478 int rc; 479 int size = file_size(fn); // file size. -1 means does not exist. 480 481 /* allocate/reallocate text buffer */ 482 free(text); 483 text_size = size + 10240; 484 screenbegin = dot = end = text = xzalloc(text_size); 485 486 if (fn != current_filename) { 487 free(current_filename); 488 current_filename = xstrdup(fn); 489 } 490 if (size < 0) { 491 // file dont exist. Start empty buf with dummy line 492 char_insert(text, '\n'); 493 rc = 0; 494 } else { 495 rc = file_insert(fn, text, 1); 496 } 497 file_modified = 0; 498 last_file_modified = -1; 499#if ENABLE_FEATURE_VI_YANKMARK 500 /* init the marks. */ 501 memset(mark, 0, sizeof(mark)); 502#endif 503 return rc; 504} 505 506#if ENABLE_FEATURE_VI_WIN_RESIZE 507static int query_screen_dimensions(void) 508{ 509 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows); 510 if (rows > MAX_SCR_ROWS) 511 rows = MAX_SCR_ROWS; 512 if (columns > MAX_SCR_COLS) 513 columns = MAX_SCR_COLS; 514 return err; 515} 516#else 517# define query_screen_dimensions() (0) 518#endif 519 520static void edit_file(char *fn) 521{ 522#if ENABLE_FEATURE_VI_YANKMARK 523#define cur_line edit_file__cur_line 524#endif 525 int c; 526 int size; 527#if ENABLE_FEATURE_VI_USE_SIGNALS 528 int sig; 529#endif 530 531 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files 532 rawmode(); 533 rows = 24; 534 columns = 80; 535 size = 0; 536 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions(); 537#if ENABLE_FEATURE_VI_ASK_TERMINAL 538 if (G.get_rowcol_error /* TODO? && no input on stdin */) { 539 uint64_t k; 540 write1("\033[999;999H" "\033[6n"); 541 fflush_all(); 542 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100); 543 if ((int32_t)k == KEYCODE_CURSOR_POS) { 544 uint32_t rc = (k >> 32); 545 columns = (rc & 0x7fff); 546 if (columns > MAX_SCR_COLS) 547 columns = MAX_SCR_COLS; 548 rows = ((rc >> 16) & 0x7fff); 549 if (rows > MAX_SCR_ROWS) 550 rows = MAX_SCR_ROWS; 551 } 552 } 553#endif 554 new_screen(rows, columns); // get memory for virtual screen 555 init_text_buffer(fn); 556 557#if ENABLE_FEATURE_VI_YANKMARK 558 YDreg = 26; // default Yank/Delete reg 559 Ureg = 27; // hold orig line for "U" cmd 560 mark[26] = mark[27] = text; // init "previous context" 561#endif 562 563 last_forward_char = last_input_char = '\0'; 564 crow = 0; 565 ccol = 0; 566 567#if ENABLE_FEATURE_VI_USE_SIGNALS 568 signal(SIGINT, catch_sig); 569 signal(SIGWINCH, winch_sig); 570 signal(SIGTSTP, suspend_sig); 571 sig = sigsetjmp(restart, 1); 572 if (sig != 0) { 573 screenbegin = dot = text; 574 } 575#endif 576 577 cmd_mode = 0; // 0=command 1=insert 2='R'eplace 578 cmdcnt = 0; 579 tabstop = 8; 580 offset = 0; // no horizontal offset 581 c = '\0'; 582#if ENABLE_FEATURE_VI_DOT_CMD 583 free(ioq_start); 584 ioq = ioq_start = NULL; 585 lmc_len = 0; 586 adding2q = 0; 587#endif 588 589#if ENABLE_FEATURE_VI_COLON 590 { 591 char *p, *q; 592 int n = 0; 593 594 while ((p = initial_cmds[n]) != NULL) { 595 do { 596 q = p; 597 p = strchr(q, '\n'); 598 if (p) 599 while (*p == '\n') 600 *p++ = '\0'; 601 if (*q) 602 colon(q); 603 } while (p); 604 free(initial_cmds[n]); 605 initial_cmds[n] = NULL; 606 n++; 607 } 608 } 609#endif 610 redraw(FALSE); // dont force every col re-draw 611 //------This is the main Vi cmd handling loop ----------------------- 612 while (editing > 0) { 613#if ENABLE_FEATURE_VI_CRASHME 614 if (crashme > 0) { 615 if ((end - text) > 1) { 616 crash_dummy(); // generate a random command 617 } else { 618 crashme = 0; 619 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string 620 dot = text; 621 refresh(FALSE); 622 } 623 } 624#endif 625 last_input_char = c = get_one_char(); // get a cmd from user 626#if ENABLE_FEATURE_VI_YANKMARK 627 // save a copy of the current line- for the 'U" command 628 if (begin_line(dot) != cur_line) { 629 cur_line = begin_line(dot); 630 text_yank(begin_line(dot), end_line(dot), Ureg); 631 } 632#endif 633#if ENABLE_FEATURE_VI_DOT_CMD 634 // These are commands that change text[]. 635 // Remember the input for the "." command 636 if (!adding2q && ioq_start == NULL 637 && cmd_mode == 0 // command mode 638 && c > '\0' // exclude NUL and non-ASCII chars 639 && c < 0x7f // (Unicode and such) 640 && strchr(modifying_cmds, c) 641 ) { 642 start_new_cmd_q(c); 643 } 644#endif 645 do_cmd(c); // execute the user command 646 647 // poll to see if there is input already waiting. if we are 648 // not able to display output fast enough to keep up, skip 649 // the display update until we catch up with input. 650 if (!readbuffer[0] && mysleep(0) == 0) { 651 // no input pending - so update output 652 refresh(FALSE); 653 show_status_line(); 654 } 655#if ENABLE_FEATURE_VI_CRASHME 656 if (crashme > 0) 657 crash_test(); // test editor variables 658#endif 659 } 660 //------------------------------------------------------------------- 661 662 go_bottom_and_clear_to_eol(); 663 cookmode(); 664#undef cur_line 665} 666 667//----- The Colon commands ------------------------------------- 668#if ENABLE_FEATURE_VI_COLON 669static char *get_one_address(char *p, int *addr) // get colon addr, if present 670{ 671 int st; 672 char *q; 673 IF_FEATURE_VI_YANKMARK(char c;) 674 IF_FEATURE_VI_SEARCH(char *pat;) 675 676 *addr = -1; // assume no addr 677 if (*p == '.') { // the current line 678 p++; 679 q = begin_line(dot); 680 *addr = count_lines(text, q); 681 } 682#if ENABLE_FEATURE_VI_YANKMARK 683 else if (*p == '\'') { // is this a mark addr 684 p++; 685 c = tolower(*p); 686 p++; 687 if (c >= 'a' && c <= 'z') { 688 // we have a mark 689 c = c - 'a'; 690 q = mark[(unsigned char) c]; 691 if (q != NULL) { // is mark valid 692 *addr = count_lines(text, q); 693 } 694 } 695 } 696#endif 697#if ENABLE_FEATURE_VI_SEARCH 698 else if (*p == '/') { // a search pattern 699 q = strchrnul(++p, '/'); 700 pat = xstrndup(p, q - p); // save copy of pattern 701 p = q; 702 if (*p == '/') 703 p++; 704 q = char_search(dot, pat, FORWARD, FULL); 705 if (q != NULL) { 706 *addr = count_lines(text, q); 707 } 708 free(pat); 709 } 710#endif 711 else if (*p == '$') { // the last line in file 712 p++; 713 q = begin_line(end - 1); 714 *addr = count_lines(text, q); 715 } else if (isdigit(*p)) { // specific line number 716 sscanf(p, "%d%n", addr, &st); 717 p += st; 718 } else { 719 // unrecognized address - assume -1 720 *addr = -1; 721 } 722 return p; 723} 724 725static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present 726{ 727 //----- get the address' i.e., 1,3 'a,'b ----- 728 // get FIRST addr, if present 729 while (isblank(*p)) 730 p++; // skip over leading spaces 731 if (*p == '%') { // alias for 1,$ 732 p++; 733 *b = 1; 734 *e = count_lines(text, end-1); 735 goto ga0; 736 } 737 p = get_one_address(p, b); 738 while (isblank(*p)) 739 p++; 740 if (*p == ',') { // is there a address separator 741 p++; 742 while (isblank(*p)) 743 p++; 744 // get SECOND addr, if present 745 p = get_one_address(p, e); 746 } 747 ga0: 748 while (isblank(*p)) 749 p++; // skip over trailing spaces 750 return p; 751} 752 753#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS 754static void setops(const char *args, const char *opname, int flg_no, 755 const char *short_opname, int opt) 756{ 757 const char *a = args + flg_no; 758 int l = strlen(opname) - 1; /* opname have + ' ' */ 759 760 // maybe strncmp? we had tons of erroneous strncasecmp's... 761 if (strncasecmp(a, opname, l) == 0 762 || strncasecmp(a, short_opname, 2) == 0 763 ) { 764 if (flg_no) 765 vi_setops &= ~opt; 766 else 767 vi_setops |= opt; 768 } 769} 770#endif 771 772// buf must be no longer than MAX_INPUT_LEN! 773static void colon(char *buf) 774{ 775 char c, *orig_buf, *buf1, *q, *r; 776 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; 777 int i, l, li, ch, b, e; 778 int useforce, forced = FALSE; 779 780 // :3154 // if (-e line 3154) goto it else stay put 781 // :4,33w! foo // write a portion of buffer to file "foo" 782 // :w // write all of buffer to current file 783 // :q // quit 784 // :q! // quit- dont care about modified file 785 // :'a,'z!sort -u // filter block through sort 786 // :'f // goto mark "f" 787 // :'fl // list literal the mark "f" line 788 // :.r bar // read file "bar" into buffer before dot 789 // :/123/,/abc/d // delete lines from "123" line to "abc" line 790 // :/xyz/ // goto the "xyz" line 791 // :s/find/replace/ // substitute pattern "find" with "replace" 792 // :!<cmd> // run <cmd> then return 793 // 794 795 if (!buf[0]) 796 goto ret; 797 if (*buf == ':') 798 buf++; // move past the ':' 799 800 li = ch = i = 0; 801 b = e = -1; 802 q = text; // assume 1,$ for the range 803 r = end - 1; 804 li = count_lines(text, end - 1); 805 fn = current_filename; 806 807 // look for optional address(es) :. :1 :1,9 :'q,'a :% 808 buf = get_address(buf, &b, &e); 809 810 // remember orig command line 811 orig_buf = buf; 812 813 // get the COMMAND into cmd[] 814 buf1 = cmd; 815 while (*buf != '\0') { 816 if (isspace(*buf)) 817 break; 818 *buf1++ = *buf++; 819 } 820 *buf1 = '\0'; 821 // get any ARGuments 822 while (isblank(*buf)) 823 buf++; 824 strcpy(args, buf); 825 useforce = FALSE; 826 buf1 = last_char_is(cmd, '!'); 827 if (buf1) { 828 useforce = TRUE; 829 *buf1 = '\0'; // get rid of ! 830 } 831 if (b >= 0) { 832 // if there is only one addr, then the addr 833 // is the line number of the single line the 834 // user wants. So, reset the end 835 // pointer to point at end of the "b" line 836 q = find_line(b); // what line is #b 837 r = end_line(q); 838 li = 1; 839 } 840 if (e >= 0) { 841 // we were given two addrs. change the 842 // end pointer to the addr given by user. 843 r = find_line(e); // what line is #e 844 r = end_line(r); 845 li = e - b + 1; 846 } 847 // ------------ now look for the command ------------ 848 i = strlen(cmd); 849 if (i == 0) { // :123CR goto line #123 850 if (b >= 0) { 851 dot = find_line(b); // what line is #b 852 dot_skip_over_ws(); 853 } 854 } 855#if ENABLE_FEATURE_ALLOW_EXEC 856 else if (cmd[0] == '!') { // run a cmd 857 int retcode; 858 // :!ls run the <cmd> 859 go_bottom_and_clear_to_eol(); 860 cookmode(); 861 retcode = system(orig_buf + 1); // run the cmd 862 if (retcode) 863 printf("\nshell returned %i\n\n", retcode); 864 rawmode(); 865 Hit_Return(); // let user see results 866 } 867#endif 868 else if (cmd[0] == '=' && !cmd[1]) { // where is the address 869 if (b < 0) { // no addr given- use defaults 870 b = e = count_lines(text, dot); 871 } 872 status_line("%d", b); 873 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines 874 if (b < 0) { // no addr given- use defaults 875 q = begin_line(dot); // assume .,. for the range 876 r = end_line(dot); 877 } 878 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines 879 dot_skip_over_ws(); 880 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file 881 // don't edit, if the current file has been modified 882 if (file_modified && !useforce) { 883 status_line_bold("No write since last change (:edit! overrides)"); 884 goto ret; 885 } 886 if (args[0]) { 887 // the user supplied a file name 888 fn = args; 889 } else if (current_filename && current_filename[0]) { 890 // no user supplied name- use the current filename 891 // fn = current_filename; was set by default 892 } else { 893 // no user file name, no current name- punt 894 status_line_bold("No current filename"); 895 goto ret; 896 } 897 898 if (init_text_buffer(fn) < 0) 899 goto ret; 900 901#if ENABLE_FEATURE_VI_YANKMARK 902 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) { 903 free(reg[Ureg]); // free orig line reg- for 'U' 904 reg[Ureg]= 0; 905 } 906 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) { 907 free(reg[YDreg]); // free default yank/delete register 908 reg[YDreg]= 0; 909 } 910#endif 911 // how many lines in text[]? 912 li = count_lines(text, end - 1); 913 status_line("\"%s\"%s" 914 IF_FEATURE_VI_READONLY("%s") 915 " %dL, %dC", current_filename, 916 (file_size(fn) < 0 ? " [New file]" : ""), 917 IF_FEATURE_VI_READONLY( 918 ((readonly_mode) ? " [Readonly]" : ""), 919 ) 920 li, ch); 921 } else if (strncmp(cmd, "file", i) == 0) { // what File is this 922 if (b != -1 || e != -1) { 923 status_line_bold("No address allowed on this command"); 924 goto ret; 925 } 926 if (args[0]) { 927 // user wants a new filename 928 free(current_filename); 929 current_filename = xstrdup(args); 930 } else { 931 // user wants file status info 932 last_status_cksum = 0; // force status update 933 } 934 } else if (strncmp(cmd, "features", i) == 0) { // what features are available 935 // print out values of all features 936 go_bottom_and_clear_to_eol(); 937 cookmode(); 938 show_help(); 939 rawmode(); 940 Hit_Return(); 941 } else if (strncmp(cmd, "list", i) == 0) { // literal print line 942 if (b < 0) { // no addr given- use defaults 943 q = begin_line(dot); // assume .,. for the range 944 r = end_line(dot); 945 } 946 go_bottom_and_clear_to_eol(); 947 puts("\r"); 948 for (; q <= r; q++) { 949 int c_is_no_print; 950 951 c = *q; 952 c_is_no_print = (c & 0x80) && !Isprint(c); 953 if (c_is_no_print) { 954 c = '.'; 955 standout_start(); 956 } 957 if (c == '\n') { 958 write1("$\r"); 959 } else if (c < ' ' || c == 127) { 960 bb_putchar('^'); 961 if (c == 127) 962 c = '?'; 963 else 964 c += '@'; 965 } 966 bb_putchar(c); 967 if (c_is_no_print) 968 standout_end(); 969 } 970 Hit_Return(); 971 } else if (strncmp(cmd, "quit", i) == 0 // quit 972 || strncmp(cmd, "next", i) == 0 // edit next file 973 ) { 974 int n; 975 if (useforce) { 976 // force end of argv list 977 if (*cmd == 'q') { 978 optind = save_argc; 979 } 980 editing = 0; 981 goto ret; 982 } 983 // don't exit if the file been modified 984 if (file_modified) { 985 status_line_bold("No write since last change (:%s! overrides)", 986 (*cmd == 'q' ? "quit" : "next")); 987 goto ret; 988 } 989 // are there other file to edit 990 n = save_argc - optind - 1; 991 if (*cmd == 'q' && n > 0) { 992 status_line_bold("%d more file(s) to edit", n); 993 goto ret; 994 } 995 if (*cmd == 'n' && n <= 0) { 996 status_line_bold("No more files to edit"); 997 goto ret; 998 } 999 editing = 0; 1000 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] 1001 fn = args; 1002 if (!fn[0]) { 1003 status_line_bold("No filename given"); 1004 goto ret; 1005 } 1006 if (b < 0) { // no addr given- use defaults 1007 q = begin_line(dot); // assume "dot" 1008 } 1009 // read after current line- unless user said ":0r foo" 1010 if (b != 0) 1011 q = next_line(q); 1012 { // dance around potentially-reallocated text[] 1013 uintptr_t ofs = q - text; 1014 ch = file_insert(fn, q, 0); 1015 q = text + ofs; 1016 } 1017 if (ch < 0) 1018 goto ret; // nothing was inserted 1019 // how many lines in text[]? 1020 li = count_lines(q, q + ch - 1); 1021 status_line("\"%s\"" 1022 IF_FEATURE_VI_READONLY("%s") 1023 " %dL, %dC", fn, 1024 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) 1025 li, ch); 1026 if (ch > 0) { 1027 // if the insert is before "dot" then we need to update 1028 if (q <= dot) 1029 dot += ch; 1030 /*file_modified++; - done by file_insert */ 1031 } 1032 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args 1033 if (file_modified && !useforce) { 1034 status_line_bold("No write since last change (:rewind! overrides)"); 1035 } else { 1036 // reset the filenames to edit 1037 optind = fn_start - 1; 1038 editing = 0; 1039 } 1040#if ENABLE_FEATURE_VI_SET 1041 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features 1042#if ENABLE_FEATURE_VI_SETOPTS 1043 char *argp; 1044#endif 1045 i = 0; // offset into args 1046 // only blank is regarded as args delmiter. What about tab '\t' ? 1047 if (!args[0] || strcasecmp(args, "all") == 0) { 1048 // print out values of all options 1049#if ENABLE_FEATURE_VI_SETOPTS 1050 status_line_bold( 1051 "%sautoindent " 1052 "%sflash " 1053 "%signorecase " 1054 "%sshowmatch " 1055 "tabstop=%u", 1056 autoindent ? "" : "no", 1057 err_method ? "" : "no", 1058 ignorecase ? "" : "no", 1059 showmatch ? "" : "no", 1060 tabstop 1061 ); 1062#endif 1063 goto ret; 1064 } 1065#if ENABLE_FEATURE_VI_SETOPTS 1066 argp = args; 1067 while (*argp) { 1068 if (strncmp(argp, "no", 2) == 0) 1069 i = 2; // ":set noautoindent" 1070 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT); 1071 setops(argp, "flash " , i, "fl", VI_ERR_METHOD); 1072 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE); 1073 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH ); 1074 if (strncmp(argp + i, "tabstop=", 8) == 0) { 1075 int t = 0; 1076 sscanf(argp + i+8, "%u", &t); 1077 if (t > 0 && t <= MAX_TABSTOP) 1078 tabstop = t; 1079 } 1080 argp = skip_non_whitespace(argp); 1081 argp = skip_whitespace(argp); 1082 } 1083#endif /* FEATURE_VI_SETOPTS */ 1084#endif /* FEATURE_VI_SET */ 1085#if ENABLE_FEATURE_VI_SEARCH 1086 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern 1087 char *ls, *F, *R; 1088 int gflag; 1089 1090 // F points to the "find" pattern 1091 // R points to the "replace" pattern 1092 // replace the cmd line delimiters "/" with NULLs 1093 gflag = 0; // global replace flag 1094 c = orig_buf[1]; // what is the delimiter 1095 F = orig_buf + 2; // start of "find" 1096 R = strchr(F, c); // middle delimiter 1097 if (!R) 1098 goto colon_s_fail; 1099 *R++ = '\0'; // terminate "find" 1100 buf1 = strchr(R, c); 1101 if (!buf1) 1102 goto colon_s_fail; 1103 *buf1++ = '\0'; // terminate "replace" 1104 if (*buf1 == 'g') { // :s/foo/bar/g 1105 buf1++; 1106 gflag++; // turn on gflag 1107 } 1108 q = begin_line(q); 1109 if (b < 0) { // maybe :s/foo/bar/ 1110 q = begin_line(dot); // start with cur line 1111 b = count_lines(text, q); // cur line number 1112 } 1113 if (e < 0) 1114 e = b; // maybe :.s/foo/bar/ 1115 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 1116 ls = q; // orig line start 1117 vc4: 1118 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find" 1119 if (buf1) { 1120 uintptr_t bias; 1121 // we found the "find" pattern - delete it 1122 text_hole_delete(buf1, buf1 + strlen(F) - 1); 1123 // inset the "replace" patern 1124 bias = string_insert(buf1, R); // insert the string 1125 buf1 += bias; 1126 ls += bias; 1127 /*q += bias; - recalculated anyway */ 1128 // check for "global" :s/foo/bar/g 1129 if (gflag == 1) { 1130 if ((buf1 + strlen(R)) < end_line(ls)) { 1131 q = buf1 + strlen(R); 1132 goto vc4; // don't let q move past cur line 1133 } 1134 } 1135 } 1136 q = next_line(ls); 1137 } 1138#endif /* FEATURE_VI_SEARCH */ 1139 } else if (strncmp(cmd, "version", i) == 0) { // show software version 1140 status_line(BB_VER " " BB_BT); 1141 } else if (strncmp(cmd, "write", i) == 0 // write text to file 1142 || strncmp(cmd, "wq", i) == 0 1143 || strncmp(cmd, "wn", i) == 0 1144 || (cmd[0] == 'x' && !cmd[1]) 1145 ) { 1146 // is there a file name to write to? 1147 if (args[0]) { 1148 fn = args; 1149 } 1150#if ENABLE_FEATURE_VI_READONLY 1151 if (readonly_mode && !useforce) { 1152 status_line_bold("\"%s\" File is read only", fn); 1153 goto ret; 1154 } 1155#endif 1156 // how many lines in text[]? 1157 li = count_lines(q, r); 1158 ch = r - q + 1; 1159 // see if file exists- if not, its just a new file request 1160 if (useforce) { 1161 // if "fn" is not write-able, chmod u+w 1162 // sprintf(syscmd, "chmod u+w %s", fn); 1163 // system(syscmd); 1164 forced = TRUE; 1165 } 1166 l = file_write(fn, q, r); 1167 if (useforce && forced) { 1168 // chmod u-w 1169 // sprintf(syscmd, "chmod u-w %s", fn); 1170 // system(syscmd); 1171 forced = FALSE; 1172 } 1173 if (l < 0) { 1174 if (l == -1) 1175 status_line_bold("\"%s\" %s", fn, strerror(errno)); 1176 } else { 1177 status_line("\"%s\" %dL, %dC", fn, li, l); 1178 if (q == text && r == end - 1 && l == ch) { 1179 file_modified = 0; 1180 last_file_modified = -1; 1181 } 1182 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' 1183 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N' 1184 ) 1185 && l == ch 1186 ) { 1187 editing = 0; 1188 } 1189 } 1190#if ENABLE_FEATURE_VI_YANKMARK 1191 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines 1192 if (b < 0) { // no addr given- use defaults 1193 q = begin_line(dot); // assume .,. for the range 1194 r = end_line(dot); 1195 } 1196 text_yank(q, r, YDreg); 1197 li = count_lines(q, r); 1198 status_line("Yank %d lines (%d chars) into [%c]", 1199 li, strlen(reg[YDreg]), what_reg()); 1200#endif 1201 } else { 1202 // cmd unknown 1203 not_implemented(cmd); 1204 } 1205 ret: 1206 dot = bound_dot(dot); // make sure "dot" is valid 1207 return; 1208#if ENABLE_FEATURE_VI_SEARCH 1209 colon_s_fail: 1210 status_line(":s expression missing delimiters"); 1211#endif 1212} 1213 1214#endif /* FEATURE_VI_COLON */ 1215 1216static void Hit_Return(void) 1217{ 1218 int c; 1219 1220 standout_start(); 1221 write1("[Hit return to continue]"); 1222 standout_end(); 1223 while ((c = get_one_char()) != '\n' && c != '\r') 1224 continue; 1225 redraw(TRUE); // force redraw all 1226} 1227 1228static int next_tabstop(int col) 1229{ 1230 return col + ((tabstop - 1) - (col % tabstop)); 1231} 1232 1233//----- Synchronize the cursor to Dot -------------------------- 1234static NOINLINE void sync_cursor(char *d, int *row, int *col) 1235{ 1236 char *beg_cur; // begin and end of "d" line 1237 char *tp; 1238 int cnt, ro, co; 1239 1240 beg_cur = begin_line(d); // first char of cur line 1241 1242 if (beg_cur < screenbegin) { 1243 // "d" is before top line on screen 1244 // how many lines do we have to move 1245 cnt = count_lines(beg_cur, screenbegin); 1246 sc1: 1247 screenbegin = beg_cur; 1248 if (cnt > (rows - 1) / 2) { 1249 // we moved too many lines. put "dot" in middle of screen 1250 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { 1251 screenbegin = prev_line(screenbegin); 1252 } 1253 } 1254 } else { 1255 char *end_scr; // begin and end of screen 1256 end_scr = end_screen(); // last char of screen 1257 if (beg_cur > end_scr) { 1258 // "d" is after bottom line on screen 1259 // how many lines do we have to move 1260 cnt = count_lines(end_scr, beg_cur); 1261 if (cnt > (rows - 1) / 2) 1262 goto sc1; // too many lines 1263 for (ro = 0; ro < cnt - 1; ro++) { 1264 // move screen begin the same amount 1265 screenbegin = next_line(screenbegin); 1266 // now, move the end of screen 1267 end_scr = next_line(end_scr); 1268 end_scr = end_line(end_scr); 1269 } 1270 } 1271 } 1272 // "d" is on screen- find out which row 1273 tp = screenbegin; 1274 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row 1275 if (tp == beg_cur) 1276 break; 1277 tp = next_line(tp); 1278 } 1279 1280 // find out what col "d" is on 1281 co = 0; 1282 while (tp < d) { // drive "co" to correct column 1283 if (*tp == '\n') //vda || *tp == '\0') 1284 break; 1285 if (*tp == '\t') { 1286 // handle tabs like real vi 1287 if (d == tp && cmd_mode) { 1288 break; 1289 } 1290 co = next_tabstop(co); 1291 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) { 1292 co++; // display as ^X, use 2 columns 1293 } 1294 co++; 1295 tp++; 1296 } 1297 1298 // "co" is the column where "dot" is. 1299 // The screen has "columns" columns. 1300 // The currently displayed columns are 0+offset -- columns+ofset 1301 // |-------------------------------------------------------------| 1302 // ^ ^ ^ 1303 // offset | |------- columns ----------------| 1304 // 1305 // If "co" is already in this range then we do not have to adjust offset 1306 // but, we do have to subtract the "offset" bias from "co". 1307 // If "co" is outside this range then we have to change "offset". 1308 // If the first char of a line is a tab the cursor will try to stay 1309 // in column 7, but we have to set offset to 0. 1310 1311 if (co < 0 + offset) { 1312 offset = co; 1313 } 1314 if (co >= columns + offset) { 1315 offset = co - columns + 1; 1316 } 1317 // if the first char of the line is a tab, and "dot" is sitting on it 1318 // force offset to 0. 1319 if (d == beg_cur && *d == '\t') { 1320 offset = 0; 1321 } 1322 co -= offset; 1323 1324 *row = ro; 1325 *col = co; 1326} 1327 1328//----- Text Movement Routines --------------------------------- 1329static char *begin_line(char *p) // return pointer to first char cur line 1330{ 1331 if (p > text) { 1332 p = memrchr(text, '\n', p - text); 1333 if (!p) 1334 return text; 1335 return p + 1; 1336 } 1337 return p; 1338} 1339 1340static char *end_line(char *p) // return pointer to NL of cur line 1341{ 1342 if (p < end - 1) { 1343 p = memchr(p, '\n', end - p - 1); 1344 if (!p) 1345 return end - 1; 1346 } 1347 return p; 1348} 1349 1350static char *dollar_line(char *p) // return pointer to just before NL line 1351{ 1352 p = end_line(p); 1353 // Try to stay off of the Newline 1354 if (*p == '\n' && (p - begin_line(p)) > 0) 1355 p--; 1356 return p; 1357} 1358 1359static char *prev_line(char *p) // return pointer first char prev line 1360{ 1361 p = begin_line(p); // goto begining of cur line 1362 if (p > text && p[-1] == '\n') 1363 p--; // step to prev line 1364 p = begin_line(p); // goto begining of prev line 1365 return p; 1366} 1367 1368static char *next_line(char *p) // return pointer first char next line 1369{ 1370 p = end_line(p); 1371 if (p < end - 1 && *p == '\n') 1372 p++; // step to next line 1373 return p; 1374} 1375 1376//----- Text Information Routines ------------------------------ 1377static char *end_screen(void) 1378{ 1379 char *q; 1380 int cnt; 1381 1382 // find new bottom line 1383 q = screenbegin; 1384 for (cnt = 0; cnt < rows - 2; cnt++) 1385 q = next_line(q); 1386 q = end_line(q); 1387 return q; 1388} 1389 1390// count line from start to stop 1391static int count_lines(char *start, char *stop) 1392{ 1393 char *q; 1394 int cnt; 1395 1396 if (stop < start) { // start and stop are backwards- reverse them 1397 q = start; 1398 start = stop; 1399 stop = q; 1400 } 1401 cnt = 0; 1402 stop = end_line(stop); 1403 while (start <= stop && start <= end - 1) { 1404 start = end_line(start); 1405 if (*start == '\n') 1406 cnt++; 1407 start++; 1408 } 1409 return cnt; 1410} 1411 1412static char *find_line(int li) // find begining of line #li 1413{ 1414 char *q; 1415 1416 for (q = text; li > 1; li--) { 1417 q = next_line(q); 1418 } 1419 return q; 1420} 1421 1422//----- Dot Movement Routines ---------------------------------- 1423static void dot_left(void) 1424{ 1425 if (dot > text && dot[-1] != '\n') 1426 dot--; 1427} 1428 1429static void dot_right(void) 1430{ 1431 if (dot < end - 1 && *dot != '\n') 1432 dot++; 1433} 1434 1435static void dot_begin(void) 1436{ 1437 dot = begin_line(dot); // return pointer to first char cur line 1438} 1439 1440static void dot_end(void) 1441{ 1442 dot = end_line(dot); // return pointer to last char cur line 1443} 1444 1445static char *move_to_col(char *p, int l) 1446{ 1447 int co; 1448 1449 p = begin_line(p); 1450 co = 0; 1451 while (co < l && p < end) { 1452 if (*p == '\n') //vda || *p == '\0') 1453 break; 1454 if (*p == '\t') { 1455 co = next_tabstop(co); 1456 } else if (*p < ' ' || *p == 127) { 1457 co++; // display as ^X, use 2 columns 1458 } 1459 co++; 1460 p++; 1461 } 1462 return p; 1463} 1464 1465static void dot_next(void) 1466{ 1467 dot = next_line(dot); 1468} 1469 1470static void dot_prev(void) 1471{ 1472 dot = prev_line(dot); 1473} 1474 1475static void dot_scroll(int cnt, int dir) 1476{ 1477 char *q; 1478 1479 for (; cnt > 0; cnt--) { 1480 if (dir < 0) { 1481 // scroll Backwards 1482 // ctrl-Y scroll up one line 1483 screenbegin = prev_line(screenbegin); 1484 } else { 1485 // scroll Forwards 1486 // ctrl-E scroll down one line 1487 screenbegin = next_line(screenbegin); 1488 } 1489 } 1490 // make sure "dot" stays on the screen so we dont scroll off 1491 if (dot < screenbegin) 1492 dot = screenbegin; 1493 q = end_screen(); // find new bottom line 1494 if (dot > q) 1495 dot = begin_line(q); // is dot is below bottom line? 1496 dot_skip_over_ws(); 1497} 1498 1499static void dot_skip_over_ws(void) 1500{ 1501 // skip WS 1502 while (isspace(*dot) && *dot != '\n' && dot < end - 1) 1503 dot++; 1504} 1505 1506static void dot_delete(void) // delete the char at 'dot' 1507{ 1508 text_hole_delete(dot, dot); 1509} 1510 1511static char *bound_dot(char *p) // make sure text[0] <= P < "end" 1512{ 1513 if (p >= end && end > text) { 1514 p = end - 1; 1515 indicate_error('1'); 1516 } 1517 if (p < text) { 1518 p = text; 1519 indicate_error('2'); 1520 } 1521 return p; 1522} 1523 1524//----- Helper Utility Routines -------------------------------- 1525 1526//---------------------------------------------------------------- 1527//----- Char Routines -------------------------------------------- 1528/* Chars that are part of a word- 1529 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1530 * Chars that are Not part of a word (stoppers) 1531 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ 1532 * Chars that are WhiteSpace 1533 * TAB NEWLINE VT FF RETURN SPACE 1534 * DO NOT COUNT NEWLINE AS WHITESPACE 1535 */ 1536 1537static char *new_screen(int ro, int co) 1538{ 1539 int li; 1540 1541 free(screen); 1542 screensize = ro * co + 8; 1543 screen = xmalloc(screensize); 1544 // initialize the new screen. assume this will be a empty file. 1545 screen_erase(); 1546 // non-existent text[] lines start with a tilde (~). 1547 for (li = 1; li < ro - 1; li++) { 1548 screen[(li * co) + 0] = '~'; 1549 } 1550 return screen; 1551} 1552 1553#if ENABLE_FEATURE_VI_SEARCH 1554static int mycmp(const char *s1, const char *s2, int len) 1555{ 1556 if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) { 1557 return strncasecmp(s1, s2, len); 1558 } 1559 return strncmp(s1, s2, len); 1560} 1561 1562// search for pattern starting at p 1563static char *char_search(char *p, const char *pat, int dir, int range) 1564{ 1565#ifndef REGEX_SEARCH 1566 char *start, *stop; 1567 int len; 1568 1569 len = strlen(pat); 1570 if (dir == FORWARD) { 1571 stop = end - 1; // assume range is p - end-1 1572 if (range == LIMITED) 1573 stop = next_line(p); // range is to next line 1574 for (start = p; start < stop; start++) { 1575 if (mycmp(start, pat, len) == 0) { 1576 return start; 1577 } 1578 } 1579 } else if (dir == BACK) { 1580 stop = text; // assume range is text - p 1581 if (range == LIMITED) 1582 stop = prev_line(p); // range is to prev line 1583 for (start = p - len; start >= stop; start--) { 1584 if (mycmp(start, pat, len) == 0) { 1585 return start; 1586 } 1587 } 1588 } 1589 // pattern not found 1590 return NULL; 1591#else /* REGEX_SEARCH */ 1592 char *q; 1593 struct re_pattern_buffer preg; 1594 int i; 1595 int size, range; 1596 1597 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; 1598 preg.translate = 0; 1599 preg.fastmap = 0; 1600 preg.buffer = 0; 1601 preg.allocated = 0; 1602 1603 // assume a LIMITED forward search 1604 q = next_line(p); 1605 q = end_line(q); 1606 q = end - 1; 1607 if (dir == BACK) { 1608 q = prev_line(p); 1609 q = text; 1610 } 1611 // count the number of chars to search over, forward or backward 1612 size = q - p; 1613 if (size < 0) 1614 size = p - q; 1615 // RANGE could be negative if we are searching backwards 1616 range = q - p; 1617 1618 q = re_compile_pattern(pat, strlen(pat), &preg); 1619 if (q != 0) { 1620 // The pattern was not compiled 1621 status_line_bold("bad search pattern: \"%s\": %s", pat, q); 1622 i = 0; // return p if pattern not compiled 1623 goto cs1; 1624 } 1625 1626 q = p; 1627 if (range < 0) { 1628 q = p - size; 1629 if (q < text) 1630 q = text; 1631 } 1632 // search for the compiled pattern, preg, in p[] 1633 // range < 0- search backward 1634 // range > 0- search forward 1635 // 0 < start < size 1636 // re_search() < 0 not found or error 1637 // re_search() > 0 index of found pattern 1638 // struct pattern char int int int struct reg 1639 // re_search (*pattern_buffer, *string, size, start, range, *regs) 1640 i = re_search(&preg, q, size, 0, range, 0); 1641 if (i == -1) { 1642 p = 0; 1643 i = 0; // return NULL if pattern not found 1644 } 1645 cs1: 1646 if (dir == FORWARD) { 1647 p = p + i; 1648 } else { 1649 p = p - i; 1650 } 1651 return p; 1652#endif /* REGEX_SEARCH */ 1653} 1654#endif /* FEATURE_VI_SEARCH */ 1655 1656static char *char_insert(char *p, char c) // insert the char c at 'p' 1657{ 1658 if (c == 22) { // Is this an ctrl-V? 1659 p += stupid_insert(p, '^'); // use ^ to indicate literal next 1660 refresh(FALSE); // show the ^ 1661 c = get_one_char(); 1662 *p = c; 1663 p++; 1664 file_modified++; 1665 } else if (c == 27) { // Is this an ESC? 1666 cmd_mode = 0; 1667 cmdcnt = 0; 1668 end_cmd_q(); // stop adding to q 1669 last_status_cksum = 0; // force status update 1670 if ((p[-1] != '\n') && (dot > text)) { 1671 p--; 1672 } 1673 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS 1674 // 123456789 1675 if ((p[-1] != '\n') && (dot>text)) { 1676 p--; 1677 p = text_hole_delete(p, p); // shrink buffer 1 char 1678 } 1679 } else { 1680 // insert a char into text[] 1681 char *sp; // "save p" 1682 1683 if (c == 13) 1684 c = '\n'; // translate \r to \n 1685 sp = p; // remember addr of insert 1686 p += 1 + stupid_insert(p, c); // insert the char 1687#if ENABLE_FEATURE_VI_SETOPTS 1688 if (showmatch && strchr(")]}", *sp) != NULL) { 1689 showmatching(sp); 1690 } 1691 if (autoindent && c == '\n') { // auto indent the new line 1692 char *q; 1693 size_t len; 1694 q = prev_line(p); // use prev line as template 1695 len = strspn(q, " \t"); // space or tab 1696 if (len) { 1697 uintptr_t bias; 1698 bias = text_hole_make(p, len); 1699 p += bias; 1700 q += bias; 1701 memcpy(p, q, len); 1702 p += len; 1703 } 1704 } 1705#endif 1706 } 1707 return p; 1708} 1709 1710// might reallocate text[]! use p += stupid_insert(p, ...), 1711// and be careful to not use pointers into potentially freed text[]! 1712static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p' 1713{ 1714 uintptr_t bias; 1715 bias = text_hole_make(p, 1); 1716 p += bias; 1717 *p = c; 1718 //file_modified++; - done by text_hole_make() 1719 return bias; 1720} 1721 1722static int find_range(char **start, char **stop, char c) 1723{ 1724 char *save_dot, *p, *q, *t; 1725 int cnt, multiline = 0; 1726 1727 save_dot = dot; 1728 p = q = dot; 1729 1730 if (strchr("cdy><", c)) { 1731 // these cmds operate on whole lines 1732 p = q = begin_line(p); 1733 for (cnt = 1; cnt < cmdcnt; cnt++) { 1734 q = next_line(q); 1735 } 1736 q = end_line(q); 1737 } else if (strchr("^%$0bBeEfth\b\177", c)) { 1738 // These cmds operate on char positions 1739 do_cmd(c); // execute movement cmd 1740 q = dot; 1741 } else if (strchr("wW", c)) { 1742 do_cmd(c); // execute movement cmd 1743 // if we are at the next word's first char 1744 // step back one char 1745 // but check the possibilities when it is true 1746 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0])) 1747 || (ispunct(dot[-1]) && !ispunct(dot[0])) 1748 || (isalnum(dot[-1]) && !isalnum(dot[0])))) 1749 dot--; // move back off of next word 1750 if (dot > text && *dot == '\n') 1751 dot--; // stay off NL 1752 q = dot; 1753 } else if (strchr("H-k{", c)) { 1754 // these operate on multi-lines backwards 1755 q = end_line(dot); // find NL 1756 do_cmd(c); // execute movement cmd 1757 dot_begin(); 1758 p = dot; 1759 } else if (strchr("L+j}\r\n", c)) { 1760 // these operate on multi-lines forwards 1761 p = begin_line(dot); 1762 do_cmd(c); // execute movement cmd 1763 dot_end(); // find NL 1764 q = dot; 1765 } else { 1766 // nothing -- this causes any other values of c to 1767 // represent the one-character range under the 1768 // cursor. this is correct for ' ' and 'l', but 1769 // perhaps no others. 1770 // 1771 } 1772 if (q < p) { 1773 t = q; 1774 q = p; 1775 p = t; 1776 } 1777 1778 // backward char movements don't include start position 1779 if (q > p && strchr("^0bBh\b\177", c)) q--; 1780 1781 multiline = 0; 1782 for (t = p; t <= q; t++) { 1783 if (*t == '\n') { 1784 multiline = 1; 1785 break; 1786 } 1787 } 1788 1789 *start = p; 1790 *stop = q; 1791 dot = save_dot; 1792 return multiline; 1793} 1794 1795static int st_test(char *p, int type, int dir, char *tested) 1796{ 1797 char c, c0, ci; 1798 int test, inc; 1799 1800 inc = dir; 1801 c = c0 = p[0]; 1802 ci = p[inc]; 1803 test = 0; 1804 1805 if (type == S_BEFORE_WS) { 1806 c = ci; 1807 test = (!isspace(c) || c == '\n'); 1808 } 1809 if (type == S_TO_WS) { 1810 c = c0; 1811 test = (!isspace(c) || c == '\n'); 1812 } 1813 if (type == S_OVER_WS) { 1814 c = c0; 1815 test = isspace(c); 1816 } 1817 if (type == S_END_PUNCT) { 1818 c = ci; 1819 test = ispunct(c); 1820 } 1821 if (type == S_END_ALNUM) { 1822 c = ci; 1823 test = (isalnum(c) || c == '_'); 1824 } 1825 *tested = c; 1826 return test; 1827} 1828 1829static char *skip_thing(char *p, int linecnt, int dir, int type) 1830{ 1831 char c; 1832 1833 while (st_test(p, type, dir, &c)) { 1834 // make sure we limit search to correct number of lines 1835 if (c == '\n' && --linecnt < 1) 1836 break; 1837 if (dir >= 0 && p >= end - 1) 1838 break; 1839 if (dir < 0 && p <= text) 1840 break; 1841 p += dir; // move to next char 1842 } 1843 return p; 1844} 1845 1846// find matching char of pair () [] {} 1847static char *find_pair(char *p, const char c) 1848{ 1849 char match, *q; 1850 int dir, level; 1851 1852 match = ')'; 1853 level = 1; 1854 dir = 1; // assume forward 1855 switch (c) { 1856 case '(': match = ')'; break; 1857 case '[': match = ']'; break; 1858 case '{': match = '}'; break; 1859 case ')': match = '('; dir = -1; break; 1860 case ']': match = '['; dir = -1; break; 1861 case '}': match = '{'; dir = -1; break; 1862 } 1863 for (q = p + dir; text <= q && q < end; q += dir) { 1864 // look for match, count levels of pairs (( )) 1865 if (*q == c) 1866 level++; // increase pair levels 1867 if (*q == match) 1868 level--; // reduce pair level 1869 if (level == 0) 1870 break; // found matching pair 1871 } 1872 if (level != 0) 1873 q = NULL; // indicate no match 1874 return q; 1875} 1876 1877#if ENABLE_FEATURE_VI_SETOPTS 1878// show the matching char of a pair, () [] {} 1879static void showmatching(char *p) 1880{ 1881 char *q, *save_dot; 1882 1883 // we found half of a pair 1884 q = find_pair(p, *p); // get loc of matching char 1885 if (q == NULL) { 1886 indicate_error('3'); // no matching char 1887 } else { 1888 // "q" now points to matching pair 1889 save_dot = dot; // remember where we are 1890 dot = q; // go to new loc 1891 refresh(FALSE); // let the user see it 1892 mysleep(40); // give user some time 1893 dot = save_dot; // go back to old loc 1894 refresh(FALSE); 1895 } 1896} 1897#endif /* FEATURE_VI_SETOPTS */ 1898 1899// open a hole in text[] 1900// might reallocate text[]! use p += text_hole_make(p, ...), 1901// and be careful to not use pointers into potentially freed text[]! 1902static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole 1903{ 1904 uintptr_t bias = 0; 1905 1906 if (size <= 0) 1907 return bias; 1908 end += size; // adjust the new END 1909 if (end >= (text + text_size)) { 1910 char *new_text; 1911 text_size += end - (text + text_size) + 10240; 1912 new_text = xrealloc(text, text_size); 1913 bias = (new_text - text); 1914 screenbegin += bias; 1915 dot += bias; 1916 end += bias; 1917 p += bias; 1918 text = new_text; 1919 } 1920 memmove(p + size, p, end - size - p); 1921 memset(p, ' ', size); // clear new hole 1922 file_modified++; 1923 return bias; 1924} 1925 1926// close a hole in text[] 1927static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive 1928{ 1929 char *src, *dest; 1930 int cnt, hole_size; 1931 1932 // move forwards, from beginning 1933 // assume p <= q 1934 src = q + 1; 1935 dest = p; 1936 if (q < p) { // they are backward- swap them 1937 src = p + 1; 1938 dest = q; 1939 } 1940 hole_size = q - p + 1; 1941 cnt = end - src; 1942 if (src < text || src > end) 1943 goto thd0; 1944 if (dest < text || dest >= end) 1945 goto thd0; 1946 if (src >= end) 1947 goto thd_atend; // just delete the end of the buffer 1948 memmove(dest, src, cnt); 1949 thd_atend: 1950 end = end - hole_size; // adjust the new END 1951 if (dest >= end) 1952 dest = end - 1; // make sure dest in below end-1 1953 if (end <= text) 1954 dest = end = text; // keep pointers valid 1955 file_modified++; 1956 thd0: 1957 return dest; 1958} 1959 1960// copy text into register, then delete text. 1961// if dist <= 0, do not include, or go past, a NewLine 1962// 1963static char *yank_delete(char *start, char *stop, int dist, int yf) 1964{ 1965 char *p; 1966 1967 // make sure start <= stop 1968 if (start > stop) { 1969 // they are backwards, reverse them 1970 p = start; 1971 start = stop; 1972 stop = p; 1973 } 1974 if (dist <= 0) { 1975 // we cannot cross NL boundaries 1976 p = start; 1977 if (*p == '\n') 1978 return p; 1979 // dont go past a NewLine 1980 for (; p + 1 <= stop; p++) { 1981 if (p[1] == '\n') { 1982 stop = p; // "stop" just before NewLine 1983 break; 1984 } 1985 } 1986 } 1987 p = start; 1988#if ENABLE_FEATURE_VI_YANKMARK 1989 text_yank(start, stop, YDreg); 1990#endif 1991 if (yf == YANKDEL) { 1992 p = text_hole_delete(start, stop); 1993 } // delete lines 1994 return p; 1995} 1996 1997static void show_help(void) 1998{ 1999 puts("These features are available:" 2000#if ENABLE_FEATURE_VI_SEARCH 2001 "\n\tPattern searches with / and ?" 2002#endif 2003#if ENABLE_FEATURE_VI_DOT_CMD 2004 "\n\tLast command repeat with \'.\'" 2005#endif 2006#if ENABLE_FEATURE_VI_YANKMARK 2007 "\n\tLine marking with 'x" 2008 "\n\tNamed buffers with \"x" 2009#endif 2010#if ENABLE_FEATURE_VI_READONLY 2011 "\n\tReadonly if vi is called as \"view\"" 2012 "\n\tReadonly with -R command line arg" 2013#endif 2014#if ENABLE_FEATURE_VI_SET 2015 "\n\tSome colon mode commands with \':\'" 2016#endif 2017#if ENABLE_FEATURE_VI_SETOPTS 2018 "\n\tSettable options with \":set\"" 2019#endif 2020#if ENABLE_FEATURE_VI_USE_SIGNALS 2021 "\n\tSignal catching- ^C" 2022 "\n\tJob suspend and resume with ^Z" 2023#endif 2024#if ENABLE_FEATURE_VI_WIN_RESIZE 2025 "\n\tAdapt to window re-sizes" 2026#endif 2027 ); 2028} 2029 2030#if ENABLE_FEATURE_VI_DOT_CMD 2031static void start_new_cmd_q(char c) 2032{ 2033 // get buffer for new cmd 2034 // if there is a current cmd count put it in the buffer first 2035 if (cmdcnt > 0) { 2036 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c); 2037 } else { // just save char c onto queue 2038 last_modifying_cmd[0] = c; 2039 lmc_len = 1; 2040 } 2041 adding2q = 1; 2042} 2043 2044static void end_cmd_q(void) 2045{ 2046#if ENABLE_FEATURE_VI_YANKMARK 2047 YDreg = 26; // go back to default Yank/Delete reg 2048#endif 2049 adding2q = 0; 2050} 2051#endif /* FEATURE_VI_DOT_CMD */ 2052 2053#if ENABLE_FEATURE_VI_YANKMARK \ 2054 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \ 2055 || ENABLE_FEATURE_VI_CRASHME 2056// might reallocate text[]! use p += string_insert(p, ...), 2057// and be careful to not use pointers into potentially freed text[]! 2058static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p' 2059{ 2060 uintptr_t bias; 2061 int i; 2062 2063 i = strlen(s); 2064 bias = text_hole_make(p, i); 2065 p += bias; 2066 memcpy(p, s, i); 2067#if ENABLE_FEATURE_VI_YANKMARK 2068 { 2069 int cnt; 2070 for (cnt = 0; *s != '\0'; s++) { 2071 if (*s == '\n') 2072 cnt++; 2073 } 2074 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); 2075 } 2076#endif 2077 return bias; 2078} 2079#endif 2080 2081#if ENABLE_FEATURE_VI_YANKMARK 2082static char *text_yank(char *p, char *q, int dest) // copy text into a register 2083{ 2084 int cnt = q - p; 2085 if (cnt < 0) { // they are backwards- reverse them 2086 p = q; 2087 cnt = -cnt; 2088 } 2089 free(reg[dest]); // if already a yank register, free it 2090 reg[dest] = xstrndup(p, cnt + 1); 2091 return p; 2092} 2093 2094static char what_reg(void) 2095{ 2096 char c; 2097 2098 c = 'D'; // default to D-reg 2099 if (0 <= YDreg && YDreg <= 25) 2100 c = 'a' + (char) YDreg; 2101 if (YDreg == 26) 2102 c = 'D'; 2103 if (YDreg == 27) 2104 c = 'U'; 2105 return c; 2106} 2107 2108static void check_context(char cmd) 2109{ 2110 // A context is defined to be "modifying text" 2111 // Any modifying command establishes a new context. 2112 2113 if (dot < context_start || dot > context_end) { 2114 if (strchr(modifying_cmds, cmd) != NULL) { 2115 // we are trying to modify text[]- make this the current context 2116 mark[27] = mark[26]; // move cur to prev 2117 mark[26] = dot; // move local to cur 2118 context_start = prev_line(prev_line(dot)); 2119 context_end = next_line(next_line(dot)); 2120 //loiter= start_loiter= now; 2121 } 2122 } 2123} 2124 2125static char *swap_context(char *p) // goto new context for '' command make this the current context 2126{ 2127 char *tmp; 2128 2129 // the current context is in mark[26] 2130 // the previous context is in mark[27] 2131 // only swap context if other context is valid 2132 if (text <= mark[27] && mark[27] <= end - 1) { 2133 tmp = mark[27]; 2134 mark[27] = mark[26]; 2135 mark[26] = tmp; 2136 p = mark[26]; // where we are going- previous context 2137 context_start = prev_line(prev_line(prev_line(p))); 2138 context_end = next_line(next_line(next_line(p))); 2139 } 2140 return p; 2141} 2142#endif /* FEATURE_VI_YANKMARK */ 2143 2144//----- Set terminal attributes -------------------------------- 2145static void rawmode(void) 2146{ 2147 tcgetattr(0, &term_orig); 2148 term_vi = term_orig; 2149 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's 2150 term_vi.c_iflag &= (~IXON & ~ICRNL); 2151 term_vi.c_oflag &= (~ONLCR); 2152 term_vi.c_cc[VMIN] = 1; 2153 term_vi.c_cc[VTIME] = 0; 2154 erase_char = term_vi.c_cc[VERASE]; 2155 tcsetattr_stdin_TCSANOW(&term_vi); 2156} 2157 2158static void cookmode(void) 2159{ 2160 fflush_all(); 2161 tcsetattr_stdin_TCSANOW(&term_orig); 2162} 2163 2164#if ENABLE_FEATURE_VI_USE_SIGNALS 2165//----- Come here when we get a window resize signal --------- 2166static void winch_sig(int sig UNUSED_PARAM) 2167{ 2168 int save_errno = errno; 2169 // FIXME: do it in main loop!!! 2170 signal(SIGWINCH, winch_sig); 2171 query_screen_dimensions(); 2172 new_screen(rows, columns); // get memory for virtual screen 2173 redraw(TRUE); // re-draw the screen 2174 errno = save_errno; 2175} 2176 2177//----- Come here when we get a continue signal ------------------- 2178static void cont_sig(int sig UNUSED_PARAM) 2179{ 2180 int save_errno = errno; 2181 rawmode(); // terminal to "raw" 2182 last_status_cksum = 0; // force status update 2183 redraw(TRUE); // re-draw the screen 2184 2185 signal(SIGTSTP, suspend_sig); 2186 signal(SIGCONT, SIG_DFL); 2187 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"... 2188 errno = save_errno; 2189} 2190 2191//----- Come here when we get a Suspend signal ------------------- 2192static void suspend_sig(int sig UNUSED_PARAM) 2193{ 2194 int save_errno = errno; 2195 go_bottom_and_clear_to_eol(); 2196 cookmode(); // terminal to "cooked" 2197 2198 signal(SIGCONT, cont_sig); 2199 signal(SIGTSTP, SIG_DFL); 2200 kill(my_pid, SIGTSTP); 2201 errno = save_errno; 2202} 2203 2204//----- Come here when we get a signal --------------------------- 2205static void catch_sig(int sig) 2206{ 2207 signal(SIGINT, catch_sig); 2208 siglongjmp(restart, sig); 2209} 2210#endif /* FEATURE_VI_USE_SIGNALS */ 2211 2212static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready 2213{ 2214 struct pollfd pfd[1]; 2215 2216 pfd[0].fd = STDIN_FILENO; 2217 pfd[0].events = POLLIN; 2218 return safe_poll(pfd, 1, hund*10) > 0; 2219} 2220 2221//----- IO Routines -------------------------------------------- 2222static int readit(void) // read (maybe cursor) key from stdin 2223{ 2224 int c; 2225 2226 fflush_all(); 2227 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2); 2228 if (c == -1) { // EOF/error 2229 go_bottom_and_clear_to_eol(); 2230 cookmode(); // terminal to "cooked" 2231 bb_error_msg_and_die("can't read user input"); 2232 } 2233 return c; 2234} 2235 2236//----- IO Routines -------------------------------------------- 2237static int get_one_char(void) 2238{ 2239 int c; 2240 2241#if ENABLE_FEATURE_VI_DOT_CMD 2242 if (!adding2q) { 2243 // we are not adding to the q. 2244 // but, we may be reading from a q 2245 if (ioq == 0) { 2246 // there is no current q, read from STDIN 2247 c = readit(); // get the users input 2248 } else { 2249 // there is a queue to get chars from first 2250 // careful with correct sign expansion! 2251 c = (unsigned char)*ioq++; 2252 if (c == '\0') { 2253 // the end of the q, read from STDIN 2254 free(ioq_start); 2255 ioq_start = ioq = 0; 2256 c = readit(); // get the users input 2257 } 2258 } 2259 } else { 2260 // adding STDIN chars to q 2261 c = readit(); // get the users input 2262 if (lmc_len >= MAX_INPUT_LEN - 1) { 2263 status_line_bold("last_modifying_cmd overrun"); 2264 } else { 2265 // add new char to q 2266 last_modifying_cmd[lmc_len++] = c; 2267 } 2268 } 2269#else 2270 c = readit(); // get the users input 2271#endif /* FEATURE_VI_DOT_CMD */ 2272 return c; 2273} 2274 2275// Get input line (uses "status line" area) 2276static char *get_input_line(const char *prompt) 2277{ 2278 // char [MAX_INPUT_LEN] 2279#define buf get_input_line__buf 2280 2281 int c; 2282 int i; 2283 2284 strcpy(buf, prompt); 2285 last_status_cksum = 0; // force status update 2286 go_bottom_and_clear_to_eol(); 2287 write1(prompt); // write out the :, /, or ? prompt 2288 2289 i = strlen(buf); 2290 while (i < MAX_INPUT_LEN) { 2291 c = get_one_char(); 2292 if (c == '\n' || c == '\r' || c == 27) 2293 break; // this is end of input 2294 if (c == erase_char || c == 8 || c == 127) { 2295 // user wants to erase prev char 2296 buf[--i] = '\0'; 2297 write1("\b \b"); // erase char on screen 2298 if (i <= 0) // user backs up before b-o-l, exit 2299 break; 2300 } else if (c > 0 && c < 256) { // exclude Unicode 2301 // (TODO: need to handle Unicode) 2302 buf[i] = c; 2303 buf[++i] = '\0'; 2304 bb_putchar(c); 2305 } 2306 } 2307 refresh(FALSE); 2308 return buf; 2309#undef buf 2310} 2311 2312static int file_size(const char *fn) // what is the byte size of "fn" 2313{ 2314 struct stat st_buf; 2315 int cnt; 2316 2317 cnt = -1; 2318 if (fn && stat(fn, &st_buf) == 0) // see if file exists 2319 cnt = (int) st_buf.st_size; 2320 return cnt; 2321} 2322 2323// might reallocate text[]! 2324static int file_insert(const char *fn, char *p, int update_ro_status) 2325{ 2326 int cnt = -1; 2327 int fd, size; 2328 struct stat statbuf; 2329 2330 /* Validate file */ 2331 if (stat(fn, &statbuf) < 0) { 2332 status_line_bold("\"%s\" %s", fn, strerror(errno)); 2333 goto fi0; 2334 } 2335 if (!S_ISREG(statbuf.st_mode)) { 2336 // This is not a regular file 2337 status_line_bold("\"%s\" Not a regular file", fn); 2338 goto fi0; 2339 } 2340 if (p < text || p > end) { 2341 status_line_bold("Trying to insert file outside of memory"); 2342 goto fi0; 2343 } 2344 2345 // read file to buffer 2346 fd = open(fn, O_RDONLY); 2347 if (fd < 0) { 2348 status_line_bold("\"%s\" %s", fn, strerror(errno)); 2349 goto fi0; 2350 } 2351 size = statbuf.st_size; 2352 p += text_hole_make(p, size); 2353 cnt = safe_read(fd, p, size); 2354 if (cnt < 0) { 2355 status_line_bold("\"%s\" %s", fn, strerror(errno)); 2356 p = text_hole_delete(p, p + size - 1); // un-do buffer insert 2357 } else if (cnt < size) { 2358 // There was a partial read, shrink unused space text[] 2359 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert 2360 status_line_bold("can't read all of file \"%s\"", fn); 2361 } 2362 if (cnt >= size) 2363 file_modified++; 2364 close(fd); 2365 fi0: 2366#if ENABLE_FEATURE_VI_READONLY 2367 if (update_ro_status 2368 && ((access(fn, W_OK) < 0) || 2369 /* root will always have access() 2370 * so we check fileperms too */ 2371 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) 2372 ) 2373 ) { 2374 SET_READONLY_FILE(readonly_mode); 2375 } 2376#endif 2377 return cnt; 2378} 2379 2380static int file_write(char *fn, char *first, char *last) 2381{ 2382 int fd, cnt, charcnt; 2383 2384 if (fn == 0) { 2385 status_line_bold("No current filename"); 2386 return -2; 2387 } 2388 /* By popular request we do not open file with O_TRUNC, 2389 * but instead ftruncate() it _after_ successful write. 2390 * Might reduce amount of data lost on power fail etc. 2391 */ 2392 fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666); // zzz 2393 if (fd < 0) 2394 return -1; 2395 cnt = last - first + 1; 2396 charcnt = full_write(fd, first, cnt); 2397/* ftruncate(fd, charcnt); buggy for us - zzz */ 2398 if (charcnt == cnt) { 2399 // good write 2400 //file_modified = FALSE; 2401 } else { 2402 charcnt = 0; 2403 } 2404 close(fd); 2405 return charcnt; 2406} 2407 2408//----- Terminal Drawing --------------------------------------- 2409// The terminal is made up of 'rows' line of 'columns' columns. 2410// classically this would be 24 x 80. 2411// screen coordinates 2412// 0,0 ... 0,79 2413// 1,0 ... 1,79 2414// . ... . 2415// . ... . 2416// 22,0 ... 22,79 2417// 23,0 ... 23,79 <- status line 2418 2419//----- Move the cursor to row x col (count from 0, not 1) ------- 2420static void place_cursor(int row, int col, int optimize) 2421{ 2422 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2]; 2423#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR 2424 enum { 2425 SZ_UP = sizeof(CMup), 2426 SZ_DN = sizeof(CMdown), 2427 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN, 2428 }; 2429 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size 2430#endif 2431 char *cm; 2432 2433 if (row < 0) row = 0; 2434 if (row >= rows) row = rows - 1; 2435 if (col < 0) col = 0; 2436 if (col >= columns) col = columns - 1; 2437 2438 //----- 1. Try the standard terminal ESC sequence 2439 sprintf(cm1, CMrc, row + 1, col + 1); 2440 cm = cm1; 2441 2442#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR 2443 if (optimize && col < 16) { 2444 char *screenp; 2445 int Rrow = last_row; 2446 int diff = Rrow - row; 2447 2448 if (diff < -5 || diff > 5) 2449 goto skip; 2450 2451 //----- find the minimum # of chars to move cursor ------------- 2452 //----- 2. Try moving with discreet chars (Newline, [back]space, ...) 2453 cm2[0] = '\0'; 2454 2455 // move to the correct row 2456 while (row < Rrow) { 2457 // the cursor has to move up 2458 strcat(cm2, CMup); 2459 Rrow--; 2460 } 2461 while (row > Rrow) { 2462 // the cursor has to move down 2463 strcat(cm2, CMdown); 2464 Rrow++; 2465 } 2466 2467 // now move to the correct column 2468 strcat(cm2, "\r"); // start at col 0 2469 // just send out orignal source char to get to correct place 2470 screenp = &screen[row * columns]; // start of screen line 2471 strncat(cm2, screenp, col); 2472 2473 // pick the shortest cursor motion to send out 2474 if (strlen(cm2) < strlen(cm)) { 2475 cm = cm2; 2476 } 2477 skip: ; 2478 } 2479 last_row = row; 2480#endif /* FEATURE_VI_OPTIMIZE_CURSOR */ 2481 write1(cm); 2482} 2483 2484//----- Erase from cursor to end of line ----------------------- 2485static void clear_to_eol(void) 2486{ 2487 write1(Ceol); // Erase from cursor to end of line 2488} 2489 2490static void go_bottom_and_clear_to_eol(void) 2491{ 2492 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen 2493 clear_to_eol(); // erase to end of line 2494} 2495 2496//----- Erase from cursor to end of screen ----------------------- 2497static void clear_to_eos(void) 2498{ 2499 write1(Ceos); // Erase from cursor to end of screen 2500} 2501 2502//----- Start standout mode ------------------------------------ 2503static void standout_start(void) // send "start reverse video" sequence 2504{ 2505 write1(SOs); // Start reverse video mode 2506} 2507 2508//----- End standout mode -------------------------------------- 2509static void standout_end(void) // send "end reverse video" sequence 2510{ 2511 write1(SOn); // End reverse video mode 2512} 2513 2514//----- Flash the screen -------------------------------------- 2515static void flash(int h) 2516{ 2517 standout_start(); // send "start reverse video" sequence 2518 redraw(TRUE); 2519 mysleep(h); 2520 standout_end(); // send "end reverse video" sequence 2521 redraw(TRUE); 2522} 2523 2524static void Indicate_Error(void) 2525{ 2526#if ENABLE_FEATURE_VI_CRASHME 2527 if (crashme > 0) 2528 return; // generate a random command 2529#endif 2530 if (!err_method) { 2531 write1(bell); // send out a bell character 2532 } else { 2533 flash(10); 2534 } 2535} 2536 2537//----- Screen[] Routines -------------------------------------- 2538//----- Erase the Screen[] memory ------------------------------ 2539static void screen_erase(void) 2540{ 2541 memset(screen, ' ', screensize); // clear new screen 2542} 2543 2544static int bufsum(char *buf, int count) 2545{ 2546 int sum = 0; 2547 char *e = buf + count; 2548 2549 while (buf < e) 2550 sum += (unsigned char) *buf++; 2551 return sum; 2552} 2553 2554//----- Draw the status line at bottom of the screen ------------- 2555static void show_status_line(void) 2556{ 2557 int cnt = 0, cksum = 0; 2558 2559 // either we already have an error or status message, or we 2560 // create one. 2561 if (!have_status_msg) { 2562 cnt = format_edit_status(); 2563 cksum = bufsum(status_buffer, cnt); 2564 } 2565 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { 2566 last_status_cksum = cksum; // remember if we have seen this line 2567 go_bottom_and_clear_to_eol(); 2568 write1(status_buffer); 2569 if (have_status_msg) { 2570 if (((int)strlen(status_buffer) - (have_status_msg - 1)) > 2571 (columns - 1) ) { 2572 have_status_msg = 0; 2573 Hit_Return(); 2574 } 2575 have_status_msg = 0; 2576 } 2577 place_cursor(crow, ccol, FALSE); // put cursor back in correct place 2578 } 2579 fflush_all(); 2580} 2581 2582//----- format the status buffer, the bottom line of screen ------ 2583// format status buffer, with STANDOUT mode 2584static void status_line_bold(const char *format, ...) 2585{ 2586 va_list args; 2587 2588 va_start(args, format); 2589 strcpy(status_buffer, SOs); // Terminal standout mode on 2590 vsprintf(status_buffer + sizeof(SOs)-1, format, args); 2591 strcat(status_buffer, SOn); // Terminal standout mode off 2592 va_end(args); 2593 2594 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2; 2595} 2596 2597// format status buffer 2598static void status_line(const char *format, ...) 2599{ 2600 va_list args; 2601 2602 va_start(args, format); 2603 vsprintf(status_buffer, format, args); 2604 va_end(args); 2605 2606 have_status_msg = 1; 2607} 2608 2609// copy s to buf, convert unprintable 2610static void print_literal(char *buf, const char *s) 2611{ 2612 char *d; 2613 unsigned char c; 2614 2615 buf[0] = '\0'; 2616 if (!s[0]) 2617 s = "(NULL)"; 2618 2619 d = buf; 2620 for (; *s; s++) { 2621 int c_is_no_print; 2622 2623 c = *s; 2624 c_is_no_print = (c & 0x80) && !Isprint(c); 2625 if (c_is_no_print) { 2626 strcpy(d, SOn); 2627 d += sizeof(SOn)-1; 2628 c = '.'; 2629 } 2630 if (c < ' ' || c == 0x7f) { 2631 *d++ = '^'; 2632 c |= '@'; /* 0x40 */ 2633 if (c == 0x7f) 2634 c = '?'; 2635 } 2636 *d++ = c; 2637 *d = '\0'; 2638 if (c_is_no_print) { 2639 strcpy(d, SOs); 2640 d += sizeof(SOs)-1; 2641 } 2642 if (*s == '\n') { 2643 *d++ = '$'; 2644 *d = '\0'; 2645 } 2646 if (d - buf > MAX_INPUT_LEN - 10) // paranoia 2647 break; 2648 } 2649} 2650 2651static void not_implemented(const char *s) 2652{ 2653 char buf[MAX_INPUT_LEN]; 2654 2655 print_literal(buf, s); 2656 status_line_bold("\'%s\' is not implemented", buf); 2657} 2658 2659// show file status on status line 2660static int format_edit_status(void) 2661{ 2662 static const char cmd_mode_indicator[] ALIGN1 = "-IR-"; 2663 2664#define tot format_edit_status__tot 2665 2666 int cur, percent, ret, trunc_at; 2667 2668 // file_modified is now a counter rather than a flag. this 2669 // helps reduce the amount of line counting we need to do. 2670 // (this will cause a mis-reporting of modified status 2671 // once every MAXINT editing operations.) 2672 2673 // it would be nice to do a similar optimization here -- if 2674 // we haven't done a motion that could have changed which line 2675 // we're on, then we shouldn't have to do this count_lines() 2676 cur = count_lines(text, dot); 2677 2678 // reduce counting -- the total lines can't have 2679 // changed if we haven't done any edits. 2680 if (file_modified != last_file_modified) { 2681 tot = cur + count_lines(dot, end - 1) - 1; 2682 last_file_modified = file_modified; 2683 } 2684 2685 // current line percent 2686 // ------------- ~~ ---------- 2687 // total lines 100 2688 if (tot > 0) { 2689 percent = (100 * cur) / tot; 2690 } else { 2691 cur = tot = 0; 2692 percent = 100; 2693 } 2694 2695 trunc_at = columns < STATUS_BUFFER_LEN-1 ? 2696 columns : STATUS_BUFFER_LEN-1; 2697 2698 ret = snprintf(status_buffer, trunc_at+1, 2699#if ENABLE_FEATURE_VI_READONLY 2700 "%c %s%s%s %d/%d %d%%", 2701#else 2702 "%c %s%s %d/%d %d%%", 2703#endif 2704 cmd_mode_indicator[cmd_mode & 3], 2705 (current_filename != NULL ? current_filename : "No file"), 2706#if ENABLE_FEATURE_VI_READONLY 2707 (readonly_mode ? " [Readonly]" : ""), 2708#endif 2709 (file_modified ? " [Modified]" : ""), 2710 cur, tot, percent); 2711 2712 if (ret >= 0 && ret < trunc_at) 2713 return ret; /* it all fit */ 2714 2715 return trunc_at; /* had to truncate */ 2716#undef tot 2717} 2718 2719//----- Force refresh of all Lines ----------------------------- 2720static void redraw(int full_screen) 2721{ 2722 place_cursor(0, 0, FALSE); // put cursor in correct place 2723 clear_to_eos(); // tell terminal to erase display 2724 screen_erase(); // erase the internal screen buffer 2725 last_status_cksum = 0; // force status update 2726 refresh(full_screen); // this will redraw the entire display 2727 show_status_line(); 2728} 2729 2730//----- Format a text[] line into a buffer --------------------- 2731static char* format_line(char *src /*, int li*/) 2732{ 2733 unsigned char c; 2734 int co; 2735 int ofs = offset; 2736 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2] 2737 2738 c = '~'; // char in col 0 in non-existent lines is '~' 2739 co = 0; 2740 while (co < columns + tabstop) { 2741 // have we gone past the end? 2742 if (src < end) { 2743 c = *src++; 2744 if (c == '\n') 2745 break; 2746 if ((c & 0x80) && !Isprint(c)) { 2747 c = '.'; 2748 } 2749 if (c < ' ' || c == 0x7f) { 2750 if (c == '\t') { 2751 c = ' '; 2752 // co % 8 != 7 2753 while ((co % tabstop) != (tabstop - 1)) { 2754 dest[co++] = c; 2755 } 2756 } else { 2757 dest[co++] = '^'; 2758 if (c == 0x7f) 2759 c = '?'; 2760 else 2761 c += '@'; // Ctrl-X -> 'X' 2762 } 2763 } 2764 } 2765 dest[co++] = c; 2766 // discard scrolled-off-to-the-left portion, 2767 // in tabstop-sized pieces 2768 if (ofs >= tabstop && co >= tabstop) { 2769 memmove(dest, dest + tabstop, co); 2770 co -= tabstop; 2771 ofs -= tabstop; 2772 } 2773 if (src >= end) 2774 break; 2775 } 2776 // check "short line, gigantic offset" case 2777 if (co < ofs) 2778 ofs = co; 2779 // discard last scrolled off part 2780 co -= ofs; 2781 dest += ofs; 2782 // fill the rest with spaces 2783 if (co < columns) 2784 memset(&dest[co], ' ', columns - co); 2785 return dest; 2786} 2787 2788//----- Refresh the changed screen lines ----------------------- 2789// Copy the source line from text[] into the buffer and note 2790// if the current screenline is different from the new buffer. 2791// If they differ then that line needs redrawing on the terminal. 2792// 2793static void refresh(int full_screen) 2794{ 2795#define old_offset refresh__old_offset 2796 2797 int li, changed; 2798 char *tp, *sp; // pointer into text[] and screen[] 2799 2800 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) { 2801 unsigned c = columns, r = rows; 2802 query_screen_dimensions(); 2803 full_screen |= (c - columns) | (r - rows); 2804 } 2805 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") 2806 tp = screenbegin; // index into text[] of top line 2807 2808 // compare text[] to screen[] and mark screen[] lines that need updating 2809 for (li = 0; li < rows - 1; li++) { 2810 int cs, ce; // column start & end 2811 char *out_buf; 2812 // format current text line 2813 out_buf = format_line(tp /*, li*/); 2814 2815 // skip to the end of the current text[] line 2816 if (tp < end) { 2817 char *t = memchr(tp, '\n', end - tp); 2818 if (!t) t = end - 1; 2819 tp = t + 1; 2820 } 2821 2822 // see if there are any changes between vitual screen and out_buf 2823 changed = FALSE; // assume no change 2824 cs = 0; 2825 ce = columns - 1; 2826 sp = &screen[li * columns]; // start of screen line 2827 if (full_screen) { 2828 // force re-draw of every single column from 0 - columns-1 2829 goto re0; 2830 } 2831 // compare newly formatted buffer with virtual screen 2832 // look forward for first difference between buf and screen 2833 for (; cs <= ce; cs++) { 2834 if (out_buf[cs] != sp[cs]) { 2835 changed = TRUE; // mark for redraw 2836 break; 2837 } 2838 } 2839 2840 // look backward for last difference between out_buf and screen 2841 for (; ce >= cs; ce--) { 2842 if (out_buf[ce] != sp[ce]) { 2843 changed = TRUE; // mark for redraw 2844 break; 2845 } 2846 } 2847 // now, cs is index of first diff, and ce is index of last diff 2848 2849 // if horz offset has changed, force a redraw 2850 if (offset != old_offset) { 2851 re0: 2852 changed = TRUE; 2853 } 2854 2855 // make a sanity check of columns indexes 2856 if (cs < 0) cs = 0; 2857 if (ce > columns - 1) ce = columns - 1; 2858 if (cs > ce) { cs = 0; ce = columns - 1; } 2859 // is there a change between vitual screen and out_buf 2860 if (changed) { 2861 // copy changed part of buffer to virtual screen 2862 memcpy(sp+cs, out_buf+cs, ce-cs+1); 2863 2864 // move cursor to column of first change 2865 //if (offset != old_offset) { 2866 // // place_cursor is still too stupid 2867 // // to handle offsets correctly 2868 // place_cursor(li, cs, FALSE); 2869 //} else { 2870 place_cursor(li, cs, TRUE); 2871 //} 2872 2873 // write line out to terminal 2874 fwrite(&sp[cs], ce - cs + 1, 1, stdout); 2875 } 2876 } 2877 2878 place_cursor(crow, ccol, TRUE); 2879 2880 old_offset = offset; 2881#undef old_offset 2882} 2883 2884//--------------------------------------------------------------------- 2885//----- the Ascii Chart ----------------------------------------------- 2886// 2887// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel 2888// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si 2889// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb 2890// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us 2891// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' 2892// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / 2893// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 2894// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? 2895// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G 2896// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O 2897// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W 2898// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ 2899// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g 2900// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o 2901// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w 2902// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del 2903//--------------------------------------------------------------------- 2904 2905//----- Execute a Vi Command ----------------------------------- 2906static void do_cmd(int c) 2907{ 2908 const char *msg = msg; // for compiler 2909 char *p, *q, *save_dot; 2910 char buf[12]; 2911 int dir; 2912 int cnt, i, j; 2913 int c1; 2914 2915// c1 = c; // quiet the compiler 2916// cnt = yf = 0; // quiet the compiler 2917// msg = p = q = save_dot = buf; // quiet the compiler 2918 memset(buf, '\0', 12); 2919 2920 show_status_line(); 2921 2922 /* if this is a cursor key, skip these checks */ 2923 switch (c) { 2924 case KEYCODE_UP: 2925 case KEYCODE_DOWN: 2926 case KEYCODE_LEFT: 2927 case KEYCODE_RIGHT: 2928 case KEYCODE_HOME: 2929 case KEYCODE_END: 2930 case KEYCODE_PAGEUP: 2931 case KEYCODE_PAGEDOWN: 2932 case KEYCODE_DELETE: 2933 goto key_cmd_mode; 2934 } 2935 2936 if (cmd_mode == 2) { 2937 // flip-flop Insert/Replace mode 2938 if (c == KEYCODE_INSERT) 2939 goto dc_i; 2940 // we are 'R'eplacing the current *dot with new char 2941 if (*dot == '\n') { 2942 // don't Replace past E-o-l 2943 cmd_mode = 1; // convert to insert 2944 } else { 2945 if (1 <= c || Isprint(c)) { 2946 if (c != 27) 2947 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char 2948 dot = char_insert(dot, c); // insert new char 2949 } 2950 goto dc1; 2951 } 2952 } 2953 if (cmd_mode == 1) { 2954 // hitting "Insert" twice means "R" replace mode 2955 if (c == KEYCODE_INSERT) goto dc5; 2956 // insert the char c at "dot" 2957 if (1 <= c || Isprint(c)) { 2958 dot = char_insert(dot, c); 2959 } 2960 goto dc1; 2961 } 2962 2963 key_cmd_mode: 2964 switch (c) { 2965 //case 0x01: // soh 2966 //case 0x09: // ht 2967 //case 0x0b: // vt 2968 //case 0x0e: // so 2969 //case 0x0f: // si 2970 //case 0x10: // dle 2971 //case 0x11: // dc1 2972 //case 0x13: // dc3 2973#if ENABLE_FEATURE_VI_CRASHME 2974 case 0x14: // dc4 ctrl-T 2975 crashme = (crashme == 0) ? 1 : 0; 2976 break; 2977#endif 2978 //case 0x16: // syn 2979 //case 0x17: // etb 2980 //case 0x18: // can 2981 //case 0x1c: // fs 2982 //case 0x1d: // gs 2983 //case 0x1e: // rs 2984 //case 0x1f: // us 2985 //case '!': // !- 2986 //case '#': // #- 2987 //case '&': // &- 2988 //case '(': // (- 2989 //case ')': // )- 2990 //case '*': // *- 2991 //case '=': // =- 2992 //case '@': // @- 2993 //case 'F': // F- 2994 //case 'K': // K- 2995 //case 'Q': // Q- 2996 //case 'S': // S- 2997 //case 'T': // T- 2998 //case 'V': // V- 2999 //case '[': // [- 3000 //case '\\': // \- 3001 //case ']': // ]- 3002 //case '_': // _- 3003 //case '`': // `- 3004 //case 'u': // u- FIXME- there is no undo 3005 //case 'v': // v- 3006 default: // unrecognized command 3007 buf[0] = c; 3008 buf[1] = '\0'; 3009 not_implemented(buf); 3010 end_cmd_q(); // stop adding to q 3011 case 0x00: // nul- ignore 3012 break; 3013 case 2: // ctrl-B scroll up full screen 3014 case KEYCODE_PAGEUP: // Cursor Key Page Up 3015 dot_scroll(rows - 2, -1); 3016 break; 3017 case 4: // ctrl-D scroll down half screen 3018 dot_scroll((rows - 2) / 2, 1); 3019 break; 3020 case 5: // ctrl-E scroll down one line 3021 dot_scroll(1, 1); 3022 break; 3023 case 6: // ctrl-F scroll down full screen 3024 case KEYCODE_PAGEDOWN: // Cursor Key Page Down 3025 dot_scroll(rows - 2, 1); 3026 break; 3027 case 7: // ctrl-G show current status 3028 last_status_cksum = 0; // force status update 3029 break; 3030 case 'h': // h- move left 3031 case KEYCODE_LEFT: // cursor key Left 3032 case 8: // ctrl-H- move left (This may be ERASE char) 3033 case 0x7f: // DEL- move left (This may be ERASE char) 3034 if (--cmdcnt > 0) { 3035 do_cmd(c); 3036 } 3037 dot_left(); 3038 break; 3039 case 10: // Newline ^J 3040 case 'j': // j- goto next line, same col 3041 case KEYCODE_DOWN: // cursor key Down 3042 if (--cmdcnt > 0) { 3043 do_cmd(c); 3044 } 3045 dot_next(); // go to next B-o-l 3046 dot = move_to_col(dot, ccol + offset); // try stay in same col 3047 break; 3048 case 12: // ctrl-L force redraw whole screen 3049 case 18: // ctrl-R force redraw 3050 place_cursor(0, 0, FALSE); // put cursor in correct place 3051 clear_to_eos(); // tel terminal to erase display 3052 mysleep(10); 3053 screen_erase(); // erase the internal screen buffer 3054 last_status_cksum = 0; // force status update 3055 refresh(TRUE); // this will redraw the entire display 3056 break; 3057 case 13: // Carriage Return ^M 3058 case '+': // +- goto next line 3059 if (--cmdcnt > 0) { 3060 do_cmd(c); 3061 } 3062 dot_next(); 3063 dot_skip_over_ws(); 3064 break; 3065 case 21: // ctrl-U scroll up half screen 3066 dot_scroll((rows - 2) / 2, -1); 3067 break; 3068 case 25: // ctrl-Y scroll up one line 3069 dot_scroll(1, -1); 3070 break; 3071 case 27: // esc 3072 if (cmd_mode == 0) 3073 indicate_error(c); 3074 cmd_mode = 0; // stop insrting 3075 end_cmd_q(); 3076 last_status_cksum = 0; // force status update 3077 break; 3078 case ' ': // move right 3079 case 'l': // move right 3080 case KEYCODE_RIGHT: // Cursor Key Right 3081 if (--cmdcnt > 0) { 3082 do_cmd(c); 3083 } 3084 dot_right(); 3085 break; 3086#if ENABLE_FEATURE_VI_YANKMARK 3087 case '"': // "- name a register to use for Delete/Yank 3088 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower() 3089 if ((unsigned)c1 <= 25) { // a-z? 3090 YDreg = c1; 3091 } else { 3092 indicate_error(c); 3093 } 3094 break; 3095 case '\'': // '- goto a specific mark 3096 c1 = (get_one_char() | 0x20) - 'a'; 3097 if ((unsigned)c1 <= 25) { // a-z? 3098 // get the b-o-l 3099 q = mark[c1]; 3100 if (text <= q && q < end) { 3101 dot = q; 3102 dot_begin(); // go to B-o-l 3103 dot_skip_over_ws(); 3104 } 3105 } else if (c1 == '\'') { // goto previous context 3106 dot = swap_context(dot); // swap current and previous context 3107 dot_begin(); // go to B-o-l 3108 dot_skip_over_ws(); 3109 } else { 3110 indicate_error(c); 3111 } 3112 break; 3113 case 'm': // m- Mark a line 3114 // this is really stupid. If there are any inserts or deletes 3115 // between text[0] and dot then this mark will not point to the 3116 // correct location! It could be off by many lines! 3117 // Well..., at least its quick and dirty. 3118 c1 = (get_one_char() | 0x20) - 'a'; 3119 if ((unsigned)c1 <= 25) { // a-z? 3120 // remember the line 3121 mark[c1] = dot; 3122 } else { 3123 indicate_error(c); 3124 } 3125 break; 3126 case 'P': // P- Put register before 3127 case 'p': // p- put register after 3128 p = reg[YDreg]; 3129 if (p == NULL) { 3130 status_line_bold("Nothing in register %c", what_reg()); 3131 break; 3132 } 3133 // are we putting whole lines or strings 3134 if (strchr(p, '\n') != NULL) { 3135 if (c == 'P') { 3136 dot_begin(); // putting lines- Put above 3137 } 3138 if (c == 'p') { 3139 // are we putting after very last line? 3140 if (end_line(dot) == (end - 1)) { 3141 dot = end; // force dot to end of text[] 3142 } else { 3143 dot_next(); // next line, then put before 3144 } 3145 } 3146 } else { 3147 if (c == 'p') 3148 dot_right(); // move to right, can move to NL 3149 } 3150 string_insert(dot, p); // insert the string 3151 end_cmd_q(); // stop adding to q 3152 break; 3153 case 'U': // U- Undo; replace current line with original version 3154 if (reg[Ureg] != 0) { 3155 p = begin_line(dot); 3156 q = end_line(dot); 3157 p = text_hole_delete(p, q); // delete cur line 3158 p += string_insert(p, reg[Ureg]); // insert orig line 3159 dot = p; 3160 dot_skip_over_ws(); 3161 } 3162 break; 3163#endif /* FEATURE_VI_YANKMARK */ 3164 case '$': // $- goto end of line 3165 case KEYCODE_END: // Cursor Key End 3166 if (--cmdcnt > 0) { 3167 dot_next(); 3168 do_cmd(c); 3169 } 3170 dot = end_line(dot); 3171 break; 3172 case '%': // %- find matching char of pair () [] {} 3173 for (q = dot; q < end && *q != '\n'; q++) { 3174 if (strchr("()[]{}", *q) != NULL) { 3175 // we found half of a pair 3176 p = find_pair(q, *q); 3177 if (p == NULL) { 3178 indicate_error(c); 3179 } else { 3180 dot = p; 3181 } 3182 break; 3183 } 3184 } 3185 if (*q == '\n') 3186 indicate_error(c); 3187 break; 3188 case 'f': // f- forward to a user specified char 3189 last_forward_char = get_one_char(); // get the search char 3190 // 3191 // dont separate these two commands. 'f' depends on ';' 3192 // 3193 //**** fall through to ... ';' 3194 case ';': // ;- look at rest of line for last forward char 3195 if (--cmdcnt > 0) { 3196 do_cmd(';'); 3197 } 3198 if (last_forward_char == 0) 3199 break; 3200 q = dot + 1; 3201 while (q < end - 1 && *q != '\n' && *q != last_forward_char) { 3202 q++; 3203 } 3204 if (*q == last_forward_char) 3205 dot = q; 3206 break; 3207 case ',': // repeat latest 'f' in opposite direction 3208 if (--cmdcnt > 0) { 3209 do_cmd(','); 3210 } 3211 if (last_forward_char == 0) 3212 break; 3213 q = dot - 1; 3214 while (q >= text && *q != '\n' && *q != last_forward_char) { 3215 q--; 3216 } 3217 if (q >= text && *q == last_forward_char) 3218 dot = q; 3219 break; 3220 3221 case '-': // -- goto prev line 3222 if (--cmdcnt > 0) { 3223 do_cmd(c); 3224 } 3225 dot_prev(); 3226 dot_skip_over_ws(); 3227 break; 3228#if ENABLE_FEATURE_VI_DOT_CMD 3229 case '.': // .- repeat the last modifying command 3230 // Stuff the last_modifying_cmd back into stdin 3231 // and let it be re-executed. 3232 if (lmc_len > 0) { 3233 last_modifying_cmd[lmc_len] = 0; 3234 ioq = ioq_start = xstrdup(last_modifying_cmd); 3235 } 3236 break; 3237#endif 3238#if ENABLE_FEATURE_VI_SEARCH 3239 case '?': // /- search for a pattern 3240 case '/': // /- search for a pattern 3241 buf[0] = c; 3242 buf[1] = '\0'; 3243 q = get_input_line(buf); // get input line- use "status line" 3244 if (q[0] && !q[1]) { 3245 if (last_search_pattern) 3246 last_search_pattern[0] = c; 3247 goto dc3; // if no pat re-use old pat 3248 } 3249 if (q[0]) { // strlen(q) > 1: new pat- save it and find 3250 // there is a new pat 3251 free(last_search_pattern); 3252 last_search_pattern = xstrdup(q); 3253 goto dc3; // now find the pattern 3254 } 3255 // user changed mind and erased the "/"- do nothing 3256 break; 3257 case 'N': // N- backward search for last pattern 3258 if (--cmdcnt > 0) { 3259 do_cmd(c); 3260 } 3261 if (last_search_pattern == 0) { 3262 msg = "No previous regular expression"; 3263 goto dc2; 3264 } 3265 dir = BACK; // assume BACKWARD search 3266 p = dot - 1; 3267 if (last_search_pattern[0] == '?') { 3268 dir = FORWARD; 3269 p = dot + 1; 3270 } 3271 goto dc4; // now search for pattern 3272 break; 3273 case 'n': // n- repeat search for last pattern 3274 // search rest of text[] starting at next char 3275 // if search fails return orignal "p" not the "p+1" address 3276 if (--cmdcnt > 0) { 3277 do_cmd(c); 3278 } 3279 dc3: 3280 dir = FORWARD; // assume FORWARD search 3281 p = dot + 1; 3282 if (last_search_pattern[0] == '?') { 3283 dir = BACK; 3284 p = dot - 1; 3285 } 3286 dc4: 3287 q = char_search(p, last_search_pattern + 1, dir, FULL); 3288 if (q != NULL) { 3289 dot = q; // good search, update "dot" 3290 msg = ""; 3291 goto dc2; 3292 } 3293 // no pattern found between "dot" and "end"- continue at top 3294 p = text; 3295 if (dir == BACK) { 3296 p = end - 1; 3297 } 3298 q = char_search(p, last_search_pattern + 1, dir, FULL); 3299 if (q != NULL) { // found something 3300 dot = q; // found new pattern- goto it 3301 msg = "search hit BOTTOM, continuing at TOP"; 3302 if (dir == BACK) { 3303 msg = "search hit TOP, continuing at BOTTOM"; 3304 } 3305 } else { 3306 msg = "Pattern not found"; 3307 } 3308 dc2: 3309 if (*msg) 3310 status_line_bold("%s", msg); 3311 break; 3312 case '{': // {- move backward paragraph 3313 q = char_search(dot, "\n\n", BACK, FULL); 3314 if (q != NULL) { // found blank line 3315 dot = next_line(q); // move to next blank line 3316 } 3317 break; 3318 case '}': // }- move forward paragraph 3319 q = char_search(dot, "\n\n", FORWARD, FULL); 3320 if (q != NULL) { // found blank line 3321 dot = next_line(q); // move to next blank line 3322 } 3323 break; 3324#endif /* FEATURE_VI_SEARCH */ 3325 case '0': // 0- goto begining of line 3326 case '1': // 1- 3327 case '2': // 2- 3328 case '3': // 3- 3329 case '4': // 4- 3330 case '5': // 5- 3331 case '6': // 6- 3332 case '7': // 7- 3333 case '8': // 8- 3334 case '9': // 9- 3335 if (c == '0' && cmdcnt < 1) { 3336 dot_begin(); // this was a standalone zero 3337 } else { 3338 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number 3339 } 3340 break; 3341 case ':': // :- the colon mode commands 3342 p = get_input_line(":"); // get input line- use "status line" 3343#if ENABLE_FEATURE_VI_COLON 3344 colon(p); // execute the command 3345#else 3346 if (*p == ':') 3347 p++; // move past the ':' 3348 cnt = strlen(p); 3349 if (cnt <= 0) 3350 break; 3351 if (strncmp(p, "quit", cnt) == 0 3352 || strncmp(p, "q!", cnt) == 0 // delete lines 3353 ) { 3354 if (file_modified && p[1] != '!') { 3355 status_line_bold("No write since last change (:quit! overrides)"); 3356 } else { 3357 editing = 0; 3358 } 3359 } else if (strncmp(p, "write", cnt) == 0 3360 || strncmp(p, "wq", cnt) == 0 3361 || strncmp(p, "wn", cnt) == 0 3362 || (p[0] == 'x' && !p[1]) 3363 ) { 3364 cnt = file_write(current_filename, text, end - 1); 3365 if (cnt < 0) { 3366 if (cnt == -1) 3367 status_line_bold("Write error: %s", strerror(errno)); 3368 } else { 3369 file_modified = 0; 3370 last_file_modified = -1; 3371 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt); 3372 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' 3373 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N' 3374 ) { 3375 editing = 0; 3376 } 3377 } 3378 } else if (strncmp(p, "file", cnt) == 0) { 3379 last_status_cksum = 0; // force status update 3380 } else if (sscanf(p, "%d", &j) > 0) { 3381 dot = find_line(j); // go to line # j 3382 dot_skip_over_ws(); 3383 } else { // unrecognized cmd 3384 not_implemented(p); 3385 } 3386#endif /* !FEATURE_VI_COLON */ 3387 break; 3388 case '<': // <- Left shift something 3389 case '>': // >- Right shift something 3390 cnt = count_lines(text, dot); // remember what line we are on 3391 c1 = get_one_char(); // get the type of thing to delete 3392 find_range(&p, &q, c1); 3393 yank_delete(p, q, 1, YANKONLY); // save copy before change 3394 p = begin_line(p); 3395 q = end_line(q); 3396 i = count_lines(p, q); // # of lines we are shifting 3397 for ( ; i > 0; i--, p = next_line(p)) { 3398 if (c == '<') { 3399 // shift left- remove tab or 8 spaces 3400 if (*p == '\t') { 3401 // shrink buffer 1 char 3402 text_hole_delete(p, p); 3403 } else if (*p == ' ') { 3404 // we should be calculating columns, not just SPACE 3405 for (j = 0; *p == ' ' && j < tabstop; j++) { 3406 text_hole_delete(p, p); 3407 } 3408 } 3409 } else if (c == '>') { 3410 // shift right -- add tab or 8 spaces 3411 char_insert(p, '\t'); 3412 } 3413 } 3414 dot = find_line(cnt); // what line were we on 3415 dot_skip_over_ws(); 3416 end_cmd_q(); // stop adding to q 3417 break; 3418 case 'A': // A- append at e-o-l 3419 dot_end(); // go to e-o-l 3420 //**** fall through to ... 'a' 3421 case 'a': // a- append after current char 3422 if (*dot != '\n') 3423 dot++; 3424 goto dc_i; 3425 break; 3426 case 'B': // B- back a blank-delimited Word 3427 case 'E': // E- end of a blank-delimited word 3428 case 'W': // W- forward a blank-delimited word 3429 if (--cmdcnt > 0) { 3430 do_cmd(c); 3431 } 3432 dir = FORWARD; 3433 if (c == 'B') 3434 dir = BACK; 3435 if (c == 'W' || isspace(dot[dir])) { 3436 dot = skip_thing(dot, 1, dir, S_TO_WS); 3437 dot = skip_thing(dot, 2, dir, S_OVER_WS); 3438 } 3439 if (c != 'W') 3440 dot = skip_thing(dot, 1, dir, S_BEFORE_WS); 3441 break; 3442 case 'C': // C- Change to e-o-l 3443 case 'D': // D- delete to e-o-l 3444 save_dot = dot; 3445 dot = dollar_line(dot); // move to before NL 3446 // copy text into a register and delete 3447 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l 3448 if (c == 'C') 3449 goto dc_i; // start inserting 3450#if ENABLE_FEATURE_VI_DOT_CMD 3451 if (c == 'D') 3452 end_cmd_q(); // stop adding to q 3453#endif 3454 break; 3455 case 'g': // 'gg' goto a line number (vim) (default: very first line) 3456 c1 = get_one_char(); 3457 if (c1 != 'g') { 3458 buf[0] = 'g'; 3459 buf[1] = c1; // TODO: if Unicode? 3460 buf[2] = '\0'; 3461 not_implemented(buf); 3462 break; 3463 } 3464 if (cmdcnt == 0) 3465 cmdcnt = 1; 3466 /* fall through */ 3467 case 'G': // G- goto to a line number (default= E-O-F) 3468 dot = end - 1; // assume E-O-F 3469 if (cmdcnt > 0) { 3470 dot = find_line(cmdcnt); // what line is #cmdcnt 3471 } 3472 dot_skip_over_ws(); 3473 break; 3474 case 'H': // H- goto top line on screen 3475 dot = screenbegin; 3476 if (cmdcnt > (rows - 1)) { 3477 cmdcnt = (rows - 1); 3478 } 3479 if (--cmdcnt > 0) { 3480 do_cmd('+'); 3481 } 3482 dot_skip_over_ws(); 3483 break; 3484 case 'I': // I- insert before first non-blank 3485 dot_begin(); // 0 3486 dot_skip_over_ws(); 3487 //**** fall through to ... 'i' 3488 case 'i': // i- insert before current char 3489 case KEYCODE_INSERT: // Cursor Key Insert 3490 dc_i: 3491 cmd_mode = 1; // start insrting 3492 break; 3493 case 'J': // J- join current and next lines together 3494 if (--cmdcnt > 1) { 3495 do_cmd(c); 3496 } 3497 dot_end(); // move to NL 3498 if (dot < end - 1) { // make sure not last char in text[] 3499 *dot++ = ' '; // replace NL with space 3500 file_modified++; 3501 while (isblank(*dot)) { // delete leading WS 3502 dot_delete(); 3503 } 3504 } 3505 end_cmd_q(); // stop adding to q 3506 break; 3507 case 'L': // L- goto bottom line on screen 3508 dot = end_screen(); 3509 if (cmdcnt > (rows - 1)) { 3510 cmdcnt = (rows - 1); 3511 } 3512 if (--cmdcnt > 0) { 3513 do_cmd('-'); 3514 } 3515 dot_begin(); 3516 dot_skip_over_ws(); 3517 break; 3518 case 'M': // M- goto middle line on screen 3519 dot = screenbegin; 3520 for (cnt = 0; cnt < (rows-1) / 2; cnt++) 3521 dot = next_line(dot); 3522 break; 3523 case 'O': // O- open a empty line above 3524 // 0i\n ESC -i 3525 p = begin_line(dot); 3526 if (p[-1] == '\n') { 3527 dot_prev(); 3528 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." 3529 dot_end(); 3530 dot = char_insert(dot, '\n'); 3531 } else { 3532 dot_begin(); // 0 3533 dot = char_insert(dot, '\n'); // i\n ESC 3534 dot_prev(); // - 3535 } 3536 goto dc_i; 3537 break; 3538 case 'R': // R- continuous Replace char 3539 dc5: 3540 cmd_mode = 2; 3541 break; 3542 case KEYCODE_DELETE: 3543 c = 'x'; 3544 // fall through 3545 case 'X': // X- delete char before dot 3546 case 'x': // x- delete the current char 3547 case 's': // s- substitute the current char 3548 if (--cmdcnt > 0) { 3549 do_cmd(c); 3550 } 3551 dir = 0; 3552 if (c == 'X') 3553 dir = -1; 3554 if (dot[dir] != '\n') { 3555 if (c == 'X') 3556 dot--; // delete prev char 3557 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char 3558 } 3559 if (c == 's') 3560 goto dc_i; // start insrting 3561 end_cmd_q(); // stop adding to q 3562 break; 3563 case 'Z': // Z- if modified, {write}; exit 3564 // ZZ means to save file (if necessary), then exit 3565 c1 = get_one_char(); 3566 if (c1 != 'Z') { 3567 indicate_error(c); 3568 break; 3569 } 3570 if (file_modified) { 3571 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) { 3572 status_line_bold("\"%s\" File is read only", current_filename); 3573 break; 3574 } 3575 cnt = file_write(current_filename, text, end - 1); 3576 if (cnt < 0) { 3577 if (cnt == -1) 3578 status_line_bold("Write error: %s", strerror(errno)); 3579 } else if (cnt == (end - 1 - text + 1)) { 3580 editing = 0; 3581 } 3582 } else { 3583 editing = 0; 3584 } 3585 break; 3586 case '^': // ^- move to first non-blank on line 3587 dot_begin(); 3588 dot_skip_over_ws(); 3589 break; 3590 case 'b': // b- back a word 3591 case 'e': // e- end of word 3592 if (--cmdcnt > 0) { 3593 do_cmd(c); 3594 } 3595 dir = FORWARD; 3596 if (c == 'b') 3597 dir = BACK; 3598 if ((dot + dir) < text || (dot + dir) > end - 1) 3599 break; 3600 dot += dir; 3601 if (isspace(*dot)) { 3602 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS); 3603 } 3604 if (isalnum(*dot) || *dot == '_') { 3605 dot = skip_thing(dot, 1, dir, S_END_ALNUM); 3606 } else if (ispunct(*dot)) { 3607 dot = skip_thing(dot, 1, dir, S_END_PUNCT); 3608 } 3609 break; 3610 case 'c': // c- change something 3611 case 'd': // d- delete something 3612#if ENABLE_FEATURE_VI_YANKMARK 3613 case 'y': // y- yank something 3614 case 'Y': // Y- Yank a line 3615#endif 3616 { 3617 int yf, ml, whole = 0; 3618 yf = YANKDEL; // assume either "c" or "d" 3619#if ENABLE_FEATURE_VI_YANKMARK 3620 if (c == 'y' || c == 'Y') 3621 yf = YANKONLY; 3622#endif 3623 c1 = 'y'; 3624 if (c != 'Y') 3625 c1 = get_one_char(); // get the type of thing to delete 3626 // determine range, and whether it spans lines 3627 ml = find_range(&p, &q, c1); 3628 if (c1 == 27) { // ESC- user changed mind and wants out 3629 c = c1 = 27; // Escape- do nothing 3630 } else if (strchr("wW", c1)) { 3631 if (c == 'c') { 3632 // don't include trailing WS as part of word 3633 while (isblank(*q)) { 3634 if (q <= text || q[-1] == '\n') 3635 break; 3636 q--; 3637 } 3638 } 3639 dot = yank_delete(p, q, ml, yf); // delete word 3640 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { 3641 // partial line copy text into a register and delete 3642 dot = yank_delete(p, q, ml, yf); // delete word 3643 } else if (strchr("cdykjHL+-{}\r\n", c1)) { 3644 // whole line copy text into a register and delete 3645 dot = yank_delete(p, q, ml, yf); // delete lines 3646 whole = 1; 3647 } else { 3648 // could not recognize object 3649 c = c1 = 27; // error- 3650 ml = 0; 3651 indicate_error(c); 3652 } 3653 if (ml && whole) { 3654 if (c == 'c') { 3655 dot = char_insert(dot, '\n'); 3656 // on the last line of file don't move to prev line 3657 if (whole && dot != (end-1)) { 3658 dot_prev(); 3659 } 3660 } else if (c == 'd') { 3661 dot_begin(); 3662 dot_skip_over_ws(); 3663 } 3664 } 3665 if (c1 != 27) { 3666 // if CHANGING, not deleting, start inserting after the delete 3667 if (c == 'c') { 3668 strcpy(buf, "Change"); 3669 goto dc_i; // start inserting 3670 } 3671 if (c == 'd') { 3672 strcpy(buf, "Delete"); 3673 } 3674#if ENABLE_FEATURE_VI_YANKMARK 3675 if (c == 'y' || c == 'Y') { 3676 strcpy(buf, "Yank"); 3677 } 3678 p = reg[YDreg]; 3679 q = p + strlen(p); 3680 for (cnt = 0; p <= q; p++) { 3681 if (*p == '\n') 3682 cnt++; 3683 } 3684 status_line("%s %d lines (%d chars) using [%c]", 3685 buf, cnt, strlen(reg[YDreg]), what_reg()); 3686#endif 3687 end_cmd_q(); // stop adding to q 3688 } 3689 break; 3690 } 3691 case 'k': // k- goto prev line, same col 3692 case KEYCODE_UP: // cursor key Up 3693 if (--cmdcnt > 0) { 3694 do_cmd(c); 3695 } 3696 dot_prev(); 3697 dot = move_to_col(dot, ccol + offset); // try stay in same col 3698 break; 3699 case 'r': // r- replace the current char with user input 3700 c1 = get_one_char(); // get the replacement char 3701 if (*dot != '\n') { 3702 *dot = c1; 3703 file_modified++; 3704 } 3705 end_cmd_q(); // stop adding to q 3706 break; 3707 case 't': // t- move to char prior to next x 3708 last_forward_char = get_one_char(); 3709 do_cmd(';'); 3710 if (*dot == last_forward_char) 3711 dot_left(); 3712 last_forward_char = 0; 3713 break; 3714 case 'w': // w- forward a word 3715 if (--cmdcnt > 0) { 3716 do_cmd(c); 3717 } 3718 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM 3719 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM); 3720 } else if (ispunct(*dot)) { // we are on PUNCT 3721 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT); 3722 } 3723 if (dot < end - 1) 3724 dot++; // move over word 3725 if (isspace(*dot)) { 3726 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS); 3727 } 3728 break; 3729 case 'z': // z- 3730 c1 = get_one_char(); // get the replacement char 3731 cnt = 0; 3732 if (c1 == '.') 3733 cnt = (rows - 2) / 2; // put dot at center 3734 if (c1 == '-') 3735 cnt = rows - 2; // put dot at bottom 3736 screenbegin = begin_line(dot); // start dot at top 3737 dot_scroll(cnt, -1); 3738 break; 3739 case '|': // |- move to column "cmdcnt" 3740 dot = move_to_col(dot, cmdcnt - 1); // try to move to column 3741 break; 3742 case '~': // ~- flip the case of letters a-z -> A-Z 3743 if (--cmdcnt > 0) { 3744 do_cmd(c); 3745 } 3746 if (islower(*dot)) { 3747 *dot = toupper(*dot); 3748 file_modified++; 3749 } else if (isupper(*dot)) { 3750 *dot = tolower(*dot); 3751 file_modified++; 3752 } 3753 dot_right(); 3754 end_cmd_q(); // stop adding to q 3755 break; 3756 //----- The Cursor and Function Keys ----------------------------- 3757 case KEYCODE_HOME: // Cursor Key Home 3758 dot_begin(); 3759 break; 3760 // The Fn keys could point to do_macro which could translate them 3761#if 0 3762 case KEYCODE_FUN1: // Function Key F1 3763 case KEYCODE_FUN2: // Function Key F2 3764 case KEYCODE_FUN3: // Function Key F3 3765 case KEYCODE_FUN4: // Function Key F4 3766 case KEYCODE_FUN5: // Function Key F5 3767 case KEYCODE_FUN6: // Function Key F6 3768 case KEYCODE_FUN7: // Function Key F7 3769 case KEYCODE_FUN8: // Function Key F8 3770 case KEYCODE_FUN9: // Function Key F9 3771 case KEYCODE_FUN10: // Function Key F10 3772 case KEYCODE_FUN11: // Function Key F11 3773 case KEYCODE_FUN12: // Function Key F12 3774 break; 3775#endif 3776 } 3777 3778 dc1: 3779 // if text[] just became empty, add back an empty line 3780 if (end == text) { 3781 char_insert(text, '\n'); // start empty buf with dummy line 3782 dot = text; 3783 } 3784 // it is OK for dot to exactly equal to end, otherwise check dot validity 3785 if (dot != end) { 3786 dot = bound_dot(dot); // make sure "dot" is valid 3787 } 3788#if ENABLE_FEATURE_VI_YANKMARK 3789 check_context(c); // update the current context 3790#endif 3791 3792 if (!isdigit(c)) 3793 cmdcnt = 0; // cmd was not a number, reset cmdcnt 3794 cnt = dot - begin_line(dot); 3795 // Try to stay off of the Newline 3796 if (*dot == '\n' && cnt > 0 && cmd_mode == 0) 3797 dot--; 3798} 3799 3800/* NB! the CRASHME code is unmaintained, and doesn't currently build */ 3801#if ENABLE_FEATURE_VI_CRASHME 3802static int totalcmds = 0; 3803static int Mp = 85; // Movement command Probability 3804static int Np = 90; // Non-movement command Probability 3805static int Dp = 96; // Delete command Probability 3806static int Ip = 97; // Insert command Probability 3807static int Yp = 98; // Yank command Probability 3808static int Pp = 99; // Put command Probability 3809static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0; 3810static const char chars[20] = "\t012345 abcdABCD-=.$"; 3811static const char *const words[20] = { 3812 "this", "is", "a", "test", 3813 "broadcast", "the", "emergency", "of", 3814 "system", "quick", "brown", "fox", 3815 "jumped", "over", "lazy", "dogs", 3816 "back", "January", "Febuary", "March" 3817}; 3818static const char *const lines[20] = { 3819 "You should have received a copy of the GNU General Public License\n", 3820 "char c, cm, *cmd, *cmd1;\n", 3821 "generate a command by percentages\n", 3822 "Numbers may be typed as a prefix to some commands.\n", 3823 "Quit, discarding changes!\n", 3824 "Forced write, if permission originally not valid.\n", 3825 "In general, any ex or ed command (such as substitute or delete).\n", 3826 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", 3827 "Please get w/ me and I will go over it with you.\n", 3828 "The following is a list of scheduled, committed changes.\n", 3829 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", 3830 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", 3831 "Any question about transactions please contact Sterling Huxley.\n", 3832 "I will try to get back to you by Friday, December 31.\n", 3833 "This Change will be implemented on Friday.\n", 3834 "Let me know if you have problems accessing this;\n", 3835 "Sterling Huxley recently added you to the access list.\n", 3836 "Would you like to go to lunch?\n", 3837 "The last command will be automatically run.\n", 3838 "This is too much english for a computer geek.\n", 3839}; 3840static char *multilines[20] = { 3841 "You should have received a copy of the GNU General Public License\n", 3842 "char c, cm, *cmd, *cmd1;\n", 3843 "generate a command by percentages\n", 3844 "Numbers may be typed as a prefix to some commands.\n", 3845 "Quit, discarding changes!\n", 3846 "Forced write, if permission originally not valid.\n", 3847 "In general, any ex or ed command (such as substitute or delete).\n", 3848 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", 3849 "Please get w/ me and I will go over it with you.\n", 3850 "The following is a list of scheduled, committed changes.\n", 3851 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", 3852 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", 3853 "Any question about transactions please contact Sterling Huxley.\n", 3854 "I will try to get back to you by Friday, December 31.\n", 3855 "This Change will be implemented on Friday.\n", 3856 "Let me know if you have problems accessing this;\n", 3857 "Sterling Huxley recently added you to the access list.\n", 3858 "Would you like to go to lunch?\n", 3859 "The last command will be automatically run.\n", 3860 "This is too much english for a computer geek.\n", 3861}; 3862 3863// create a random command to execute 3864static void crash_dummy() 3865{ 3866 static int sleeptime; // how long to pause between commands 3867 char c, cm, *cmd, *cmd1; 3868 int i, cnt, thing, rbi, startrbi, percent; 3869 3870 // "dot" movement commands 3871 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL"; 3872 3873 // is there already a command running? 3874 if (readbuffer[0] > 0) 3875 goto cd1; 3876 cd0: 3877 readbuffer[0] = 'X'; 3878 startrbi = rbi = 1; 3879 sleeptime = 0; // how long to pause between commands 3880 memset(readbuffer, '\0', sizeof(readbuffer)); 3881 // generate a command by percentages 3882 percent = (int) lrand48() % 100; // get a number from 0-99 3883 if (percent < Mp) { // Movement commands 3884 // available commands 3885 cmd = cmd1; 3886 M++; 3887 } else if (percent < Np) { // non-movement commands 3888 cmd = "mz<>\'\""; // available commands 3889 N++; 3890 } else if (percent < Dp) { // Delete commands 3891 cmd = "dx"; // available commands 3892 D++; 3893 } else if (percent < Ip) { // Inset commands 3894 cmd = "iIaAsrJ"; // available commands 3895 I++; 3896 } else if (percent < Yp) { // Yank commands 3897 cmd = "yY"; // available commands 3898 Y++; 3899 } else if (percent < Pp) { // Put commands 3900 cmd = "pP"; // available commands 3901 P++; 3902 } else { 3903 // We do not know how to handle this command, try again 3904 U++; 3905 goto cd0; 3906 } 3907 // randomly pick one of the available cmds from "cmd[]" 3908 i = (int) lrand48() % strlen(cmd); 3909 cm = cmd[i]; 3910 if (strchr(":\024", cm)) 3911 goto cd0; // dont allow colon or ctrl-T commands 3912 readbuffer[rbi++] = cm; // put cmd into input buffer 3913 3914 // now we have the command- 3915 // there are 1, 2, and multi char commands 3916 // find out which and generate the rest of command as necessary 3917 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands 3918 cmd1 = " \n\r0$^-+wWeEbBhjklHL"; 3919 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[] 3920 cmd1 = "abcdefghijklmnopqrstuvwxyz"; 3921 } 3922 thing = (int) lrand48() % strlen(cmd1); // pick a movement command 3923 c = cmd1[thing]; 3924 readbuffer[rbi++] = c; // add movement to input buffer 3925 } 3926 if (strchr("iIaAsc", cm)) { // multi-char commands 3927 if (cm == 'c') { 3928 // change some thing 3929 thing = (int) lrand48() % strlen(cmd1); // pick a movement command 3930 c = cmd1[thing]; 3931 readbuffer[rbi++] = c; // add movement to input buffer 3932 } 3933 thing = (int) lrand48() % 4; // what thing to insert 3934 cnt = (int) lrand48() % 10; // how many to insert 3935 for (i = 0; i < cnt; i++) { 3936 if (thing == 0) { // insert chars 3937 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))]; 3938 } else if (thing == 1) { // insert words 3939 strcat(readbuffer, words[(int) lrand48() % 20]); 3940 strcat(readbuffer, " "); 3941 sleeptime = 0; // how fast to type 3942 } else if (thing == 2) { // insert lines 3943 strcat(readbuffer, lines[(int) lrand48() % 20]); 3944 sleeptime = 0; // how fast to type 3945 } else { // insert multi-lines 3946 strcat(readbuffer, multilines[(int) lrand48() % 20]); 3947 sleeptime = 0; // how fast to type 3948 } 3949 } 3950 strcat(readbuffer, "\033"); 3951 } 3952 readbuffer[0] = strlen(readbuffer + 1); 3953 cd1: 3954 totalcmds++; 3955 if (sleeptime > 0) 3956 mysleep(sleeptime); // sleep 1/100 sec 3957} 3958 3959// test to see if there are any errors 3960static void crash_test() 3961{ 3962 static time_t oldtim; 3963 3964 time_t tim; 3965 char d[2], msg[80]; 3966 3967 msg[0] = '\0'; 3968 if (end < text) { 3969 strcat(msg, "end<text "); 3970 } 3971 if (end > textend) { 3972 strcat(msg, "end>textend "); 3973 } 3974 if (dot < text) { 3975 strcat(msg, "dot<text "); 3976 } 3977 if (dot > end) { 3978 strcat(msg, "dot>end "); 3979 } 3980 if (screenbegin < text) { 3981 strcat(msg, "screenbegin<text "); 3982 } 3983 if (screenbegin > end - 1) { 3984 strcat(msg, "screenbegin>end-1 "); 3985 } 3986 3987 if (msg[0]) { 3988 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s", 3989 totalcmds, last_input_char, msg, SOs, SOn); 3990 fflush_all(); 3991 while (safe_read(STDIN_FILENO, d, 1) > 0) { 3992 if (d[0] == '\n' || d[0] == '\r') 3993 break; 3994 } 3995 } 3996 tim = time(NULL); 3997 if (tim >= (oldtim + 3)) { 3998 sprintf(status_buffer, 3999 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d", 4000 totalcmds, M, N, I, D, Y, P, U, end - text + 1); 4001 oldtim = tim; 4002 } 4003} 4004#endif 4005