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