1/* vi.c - You can't spell "evil" without "vi". 2 * 3 * Copyright 2015 Rob Landley <rob@landley.net> 4 * Copyright 2019 Jarno Mäkipää <jmakip87@gmail.com> 5 * 6 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html 7 8USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN)) 9 10config VI 11 bool "vi" 12 default n 13 help 14 usage: vi [-s script] FILE 15 -s script: run script file 16 Visual text editor. Predates the existence of standardized cursor keys, 17 so the controls are weird and historical. 18*/ 19 20#define FOR_vi 21#include "toys.h" 22 23GLOBALS( 24 char *s; 25 int vi_mode, tabstop, list; 26 int cur_col, cur_row, scr_row; 27 int drawn_row, drawn_col; 28 int count0, count1, vi_mov_flag; 29 unsigned screen_height, screen_width; 30 char vi_reg, *last_search; 31 struct str_line { 32 int alloc; 33 int len; 34 char *data; 35 } *il; 36 size_t screen, cursor; //offsets 37 //yank buffer 38 struct yank_buf { 39 char reg; 40 int alloc; 41 char* data; 42 } yank; 43 44 int modified; 45 size_t filesize; 46// mem_block contains RO data that is either original file as mmap 47// or heap allocated inserted data 48// 49// 50// 51 struct block_list { 52 struct block_list *next, *prev; 53 struct mem_block { 54 size_t size; 55 size_t len; 56 enum alloc_flag { 57 MMAP, //can be munmap() before exit() 58 HEAP, //can be free() before exit() 59 STACK, //global or stack perhaps toybuf 60 } alloc; 61 const char *data; 62 } *node; 63 } *text; 64 65// slices do not contain actual allocated data but slices of data in mem_block 66// when file is first opened it has only one slice. 67// after inserting data into middle new mem_block is allocated for insert data 68// and 3 slices are created, where first and last slice are pointing to original 69// mem_block with offsets, and middle slice is pointing to newly allocated block 70// When deleting, data is not freed but mem_blocks are sliced more such way that 71// deleted data left between 2 slices 72 struct slice_list { 73 struct slice_list *next, *prev; 74 struct slice { 75 size_t len; 76 const char *data; 77 } *node; 78 } *slices; 79) 80 81static const char *blank = " \n\r\t"; 82static const char *specials = ",.:;=-+*/(){}<>[]!@#$%^&|\\?\"\'"; 83 84//get utf8 length and width at same time 85static int utf8_lnw(int *width, char *s, int bytes) 86{ 87 unsigned wc; 88 int length = 1; 89 90 if (*s == '\t') *width = TT.tabstop; 91 else { 92 length = utf8towc(&wc, s, bytes); 93 if (length < 1) length = 0, *width = 0; 94 else *width = wcwidth(wc); 95 } 96 return length; 97} 98 99static int utf8_dec(char key, char *utf8_scratch, int *sta_p) 100{ 101 int len = 0; 102 char *c = utf8_scratch; 103 c[*sta_p] = key; 104 if (!(*sta_p)) *c = key; 105 if (*c < 0x7F) { *sta_p = 1; return 1; } 106 if ((*c & 0xE0) == 0xc0) len = 2; 107 else if ((*c & 0xF0) == 0xE0 ) len = 3; 108 else if ((*c & 0xF8) == 0xF0 ) len = 4; 109 else {*sta_p = 0; return 0; } 110 111 (*sta_p)++; 112 113 if (*sta_p == 1) return 0; 114 if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; } 115 116 if (*sta_p == len) { c[(*sta_p)] = 0; return 1; } 117 118 return 0; 119} 120 121static char* utf8_last(char* str, int size) 122{ 123 char* end = str+size; 124 int pos = size, len, width = 0; 125 for (;pos >= 0; end--, pos--) { 126 len = utf8_lnw(&width, end, size-pos); 127 if (len && width) return end; 128 } 129 return 0; 130} 131 132struct double_list *dlist_add_before(struct double_list **head, 133 struct double_list **list, char *data) 134{ 135 struct double_list *new = xmalloc(sizeof(struct double_list)); 136 new->data = data; 137 if (*list == *head) *head = new; 138 139 dlist_add_nomalloc(list, new); 140 return new; 141} 142 143struct double_list *dlist_add_after(struct double_list **head, 144 struct double_list **list, char *data) 145{ 146 struct double_list *new = xmalloc(sizeof(struct double_list)); 147 new->data = data; 148 149 if (*list) { 150 new->prev = *list; 151 new->next = (*list)->next; 152 (*list)->next->prev = new; 153 (*list)->next = new; 154 } else *head = *list = new->next = new->prev = new; 155 return new; 156} 157 158// str must be already allocated 159// ownership of allocated data is moved 160// data, pre allocated data 161// offset, offset in whole text 162// size, data allocation size of given data 163// len, length of the string 164// type, define allocation type for cleanup purposes at app exit 165static int insert_str(const char *data, size_t offset, size_t size, size_t len, 166 enum alloc_flag type) 167{ 168 struct mem_block *b = xmalloc(sizeof(struct mem_block)); 169 struct slice *next = xmalloc(sizeof(struct slice)); 170 struct slice_list *s = TT.slices; 171 b->size = size; 172 b->len = len; 173 b->alloc = type; 174 b->data = data; 175 next->len = len; 176 next->data = data; 177 178 //mem blocks can be just added unordered 179 TT.text = (struct block_list *)dlist_add((struct double_list **)&TT.text, 180 (char *)b); 181 182 if (!s) { 183 TT.slices = (struct slice_list *)dlist_add( 184 (struct double_list **)&TT.slices, 185 (char *)next); 186 } else { 187 size_t pos = 0; 188 //search insertation point for slice 189 do { 190 if (pos<=offset && pos+s->node->len>offset) break; 191 pos += s->node->len; 192 s = s->next; 193 if (s == TT.slices) return -1; //error out of bounds 194 } while (1); 195 //need to cut previous slice into 2 since insert is in middle 196 if (pos+s->node->len>offset && pos!=offset) { 197 struct slice *tail = xmalloc(sizeof(struct slice)); 198 tail->len = s->node->len-(offset-pos); 199 tail->data = s->node->data+(offset-pos); 200 s->node->len = offset-pos; 201 //pos = offset; 202 s = (struct slice_list *)dlist_add_after( 203 (struct double_list **)&TT.slices, 204 (struct double_list **)&s, 205 (char *)tail); 206 207 s = (struct slice_list *)dlist_add_before( 208 (struct double_list **)&TT.slices, 209 (struct double_list **)&s, 210 (char *)next); 211 } else if (pos==offset) { 212 // insert before 213 s = (struct slice_list *)dlist_add_before( 214 (struct double_list **)&TT.slices, 215 (struct double_list **)&s, 216 (char *)next); 217 } else { 218 // insert after 219 s = (struct slice_list *)dlist_add_after((struct double_list **)&TT.slices, 220 (struct double_list **)&s, 221 (char *)next); 222 } 223 } 224 return 0; 225} 226 227// this will not free any memory 228// will only create more slices depending on position 229static int cut_str(size_t offset, size_t len) 230{ 231 struct slice_list *e, *s = TT.slices; 232 size_t end = offset+len; 233 size_t epos, spos = 0; 234 if (!s) return -1; 235 236 //find start and end slices 237 for (;;) { 238 if (spos<=offset && spos+s->node->len>offset) break; 239 spos += s->node->len; 240 s = s->next; 241 242 if (s == TT.slices) return -1; //error out of bounds 243 } 244 245 for (e = s, epos = spos; ; ) { 246 if (epos<=end && epos+e->node->len>end) break; 247 epos += e->node->len; 248 e = e->next; 249 250 if (e == TT.slices) return -1; //error out of bounds 251 } 252 253 for (;;) { 254 if (spos == offset && ( end >= spos+s->node->len)) { 255 //cut full 256 spos += s->node->len; 257 offset += s->node->len; 258 s = dlist_pop(&s); 259 if (s == TT.slices) TT.slices = s->next; 260 261 } else if (spos < offset && ( end >= spos+s->node->len)) { 262 //cut end 263 size_t clip = s->node->len - (offset - spos); 264 offset = spos+s->node->len; 265 spos += s->node->len; 266 s->node->len -= clip; 267 } else if (spos == offset && s == e) { 268 //cut begin 269 size_t clip = end - offset; 270 s->node->len -= clip; 271 s->node->data += clip; 272 break; 273 } else { 274 //cut middle 275 struct slice *tail = xmalloc(sizeof(struct slice)); 276 size_t clip = end-offset; 277 tail->len = s->node->len-(offset-spos)-clip; 278 tail->data = s->node->data+(offset-spos)+clip; 279 s->node->len = offset-spos; //wrong? 280 s = (struct slice_list *)dlist_add_after( 281 (struct double_list **)&TT.slices, 282 (struct double_list **)&s, 283 (char *)tail); 284 break; 285 } 286 if (s == e) break; 287 288 s = s->next; 289 } 290 291 return 0; 292} 293 294//find offset position in slices 295static struct slice_list *slice_offset(size_t *start, size_t offset) 296{ 297 struct slice_list *s = TT.slices; 298 size_t spos = 0; 299 300 //find start 301 for ( ;s ; ) { 302 if (spos<=offset && spos+s->node->len>offset) break; 303 304 spos += s->node->len; 305 s = s->next; 306 307 if (s == TT.slices) s = 0; //error out of bounds 308 } 309 if (s) *start = spos; 310 return s; 311} 312 313static size_t text_strchr(size_t offset, char c) 314{ 315 struct slice_list *s = TT.slices; 316 size_t epos, spos = 0; 317 int i = 0; 318 319 //find start 320 if (!(s = slice_offset(&spos, offset))) return SIZE_MAX; 321 322 i = offset-spos; 323 epos = spos+i; 324 do { 325 for (; i < s->node->len; i++, epos++) 326 if (s->node->data[i] == c) return epos; 327 s = s->next; 328 i = 0; 329 } while (s != TT.slices); 330 331 return SIZE_MAX; 332} 333 334static size_t text_strrchr(size_t offset, char c) 335{ 336 struct slice_list *s = TT.slices; 337 size_t epos, spos = 0; 338 int i = 0; 339 340 //find start 341 if (!(s = slice_offset(&spos, offset))) return SIZE_MAX; 342 343 i = offset-spos; 344 epos = spos+i; 345 do { 346 for (; i >= 0; i--, epos--) 347 if (s->node->data[i] == c) return epos; 348 s = s->prev; 349 i = s->node->len-1; 350 } while (s != TT.slices->prev); //tail 351 352 return SIZE_MAX; 353} 354 355static size_t text_filesize() 356{ 357 struct slice_list *s = TT.slices; 358 size_t pos = 0; 359 if (s) do { 360 361 pos += s->node->len; 362 s = s->next; 363 364 } while (s != TT.slices); 365 366 return pos; 367} 368 369static int text_count(size_t start, size_t end, char c) 370{ 371 struct slice_list *s = TT.slices; 372 size_t i, count = 0, spos = 0; 373 if (!(s = slice_offset(&spos, start))) return 0; 374 i = start-spos; 375 if (s) do { 376 for (; i < s->node->len && spos+i<end; i++) 377 if (s->node->data[i] == c) count++; 378 if (spos+i>=end) return count; 379 380 spos += s->node->len; 381 i = 0; 382 s = s->next; 383 384 } while (s != TT.slices); 385 386 return count; 387} 388 389static char text_byte(size_t offset) 390{ 391 struct slice_list *s = TT.slices; 392 size_t spos = 0; 393 //find start 394 if (!(s = slice_offset(&spos, offset))) return 0; 395 return s->node->data[offset-spos]; 396} 397 398//utf-8 codepoint -1 if not valid, 0 if out_of_bounds, len if valid 399//copies data to dest if dest is not 0 400static int text_codepoint(char *dest, size_t offset) 401{ 402 char scratch[8] = {0}; 403 int state = 0, finished = 0; 404 405 for (;!(finished = utf8_dec(text_byte(offset), scratch, &state)); offset++) 406 if (!state) return -1; 407 408 if (!finished && !state) return -1; 409 if (dest) memcpy(dest, scratch, 8); 410 411 return strlen(scratch); 412} 413 414static size_t text_sol(size_t offset) 415{ 416 size_t pos; 417 if (!TT.filesize || !offset) return 0; 418 else if (TT.filesize <= offset) return TT.filesize-1; 419 else if ((pos = text_strrchr(offset-1, '\n')) == SIZE_MAX) return 0; 420 else if (pos < offset) return pos+1; 421 return offset; 422} 423 424static size_t text_eol(size_t offset) 425{ 426 if (!TT.filesize) offset = 1; 427 else if (TT.filesize <= offset) return TT.filesize-1; 428 else if ((offset = text_strchr(offset, '\n')) == SIZE_MAX) 429 return TT.filesize-1; 430 return offset; 431} 432 433static size_t text_nsol(size_t offset) 434{ 435 offset = text_eol(offset); 436 if (text_byte(offset) == '\n') offset++; 437 if (offset >= TT.filesize) offset--; 438 return offset; 439} 440 441static size_t text_psol(size_t offset) 442{ 443 offset = text_sol(offset); 444 if (offset) offset--; 445 if (offset && text_byte(offset-1) != '\n') offset = text_sol(offset-1); 446 return offset; 447} 448 449static size_t text_getline(char *dest, size_t offset, size_t max_len) 450{ 451 struct slice_list *s = TT.slices; 452 size_t end, spos = 0; 453 int i, j = 0; 454 455 if (dest) *dest = 0; 456 457 if (!s) return 0; 458 if ((end = text_strchr(offset, '\n')) == SIZE_MAX) 459 if ((end = TT.filesize) > offset+max_len) return 0; 460 461 //find start 462 if (!(s = slice_offset(&spos, offset))) return 0; 463 464 i = offset-spos; 465 j = end-offset+1; 466 if (dest) do { 467 for (; i < s->node->len && j; i++, j--, dest++) 468 *dest = s->node->data[i]; 469 s = s->next; 470 i = 0; 471 } while (s != TT.slices && j); 472 473 if (dest) *dest = 0; 474 475 return end-offset; 476} 477 478//copying is needed when file has lot of inserts that are 479//just few char long, but not always. Advanced search should 480//check big slices directly and just copy edge cases. 481//Also this is only line based search multiline 482//and regexec should be done instead. 483static size_t text_strstr(size_t offset, char *str) 484{ 485 size_t bytes, pos = offset; 486 char *s = 0; 487 do { 488 bytes = text_getline(toybuf, pos, ARRAY_LEN(toybuf)); 489 if (!bytes) pos++; //empty line 490 else if ((s = strstr(toybuf, str))) return pos+(s-toybuf); 491 else pos += bytes; 492 } while (pos < TT.filesize); 493 494 return SIZE_MAX; 495} 496 497static void block_list_free(void *node) 498{ 499 struct block_list *d = node; 500 501 if (d->node->alloc == HEAP) free((void *)d->node->data); 502 else if (d->node->alloc == MMAP) munmap((void *)d->node->data, d->node->size); 503 504 free(d->node); 505 free(d); 506} 507 508static void linelist_unload() 509{ 510 llist_traverse((void *)TT.slices, llist_free_double); 511 llist_traverse((void *)TT.text, block_list_free); 512 TT.slices = 0, TT.text = 0; 513} 514 515static int linelist_load(char *filename) 516{ 517 if (!filename) filename = (char*)*toys.optargs; 518 519 if (filename) { 520 int fd = open(filename, O_RDONLY); 521 long long size; 522 523 if (fd == -1 || !(size = fdlength(fd))) { 524 insert_str("", 0, 0, 0, STACK); 525 TT.filesize = 0; 526 527 return 0; 528 } 529 insert_str(xmmap(0, size, PROT_READ, MAP_SHARED, fd, 0), 0, size,size,MMAP); 530 xclose(fd); 531 TT.filesize = text_filesize(); 532 } 533 534 return 1; 535} 536 537static void write_file(char *filename) 538{ 539 struct slice_list *s = TT.slices; 540 struct stat st; 541 int fd = 0; 542 if (!s) return; 543 544 if (!filename) filename = (char*)*toys.optargs; 545 546 sprintf(toybuf, "%s.swp", filename); 547 548 if ( (fd = xopen(toybuf, O_WRONLY | O_CREAT | O_TRUNC)) <0) return; 549 550 do { 551 xwrite(fd, (void *)s->node->data, s->node->len ); 552 s = s->next; 553 } while (s != TT.slices); 554 555 linelist_unload(); 556 557 xclose(fd); 558 if (!stat(filename, &st)) chmod(toybuf, st.st_mode); 559 else chmod(toybuf, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 560 xrename(toybuf, filename); 561 linelist_load(filename); 562} 563 564//jump into valid offset index 565//and valid utf8 codepoint 566static void check_cursor_bounds() 567{ 568 char buf[8] = {0}; 569 int len, width = 0; 570 if (!TT.filesize) TT.cursor = 0; 571 572 for (;;) { 573 if (TT.cursor < 1) { 574 TT.cursor = 0; 575 return; 576 } else if (TT.cursor >= TT.filesize-1) { 577 TT.cursor = TT.filesize-1; 578 return; 579 } 580 if ((len = text_codepoint(buf, TT.cursor)) < 1) { 581 TT.cursor--; //we are not in valid data try jump over 582 continue; 583 } 584 if (utf8_lnw(&width, buf, len) && width) break; 585 else TT.cursor--; //combine char jump over 586 } 587} 588 589// TT.vi_mov_flag is used for special cases when certain move 590// acts differently depending is there DELETE/YANK or NOP 591// Also commands such as G does not default to count0=1 592// 0x1 = Command needs argument (f,F,r...) 593// 0x2 = Move 1 right on yank/delete/insert (e, $...) 594// 0x4 = yank/delete last line fully 595// 0x10000000 = redraw after cursor needed 596// 0x20000000 = full redraw needed 597// 0x40000000 = count0 not given 598// 0x80000000 = move was reverse 599 600//TODO rewrite the logic, difficulties counting lines 601//and with big files scroll should not rely in knowing 602//absoluteline numbers 603static void adjust_screen_buffer() 604{ 605 size_t c, s; 606 TT.cur_row = 0, TT.scr_row = 0; 607 if (!TT.cursor) { 608 TT.screen = 0; 609 TT.vi_mov_flag = 0x20000000; 610 return; 611 } else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) { 612 //give up, file is big, do full redraw 613 614 TT.screen = text_strrchr(TT.cursor-1, '\n')+1; 615 TT.vi_mov_flag = 0x20000000; 616 return; 617 } 618 619 s = text_count(0, TT.screen, '\n'); 620 c = text_count(0, TT.cursor, '\n'); 621 if (s >= c) { 622 TT.screen = text_strrchr(TT.cursor-1, '\n')+1; 623 s = c; 624 TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll 625 } else { 626 int distance = c-s+1; 627 if (distance > (int)TT.screen_height) { 628 int n, adj = distance-TT.screen_height; 629 TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll 630 for (;adj; adj--, s++) 631 if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen) 632 TT.screen = n+1; 633 } 634 } 635 636 TT.scr_row = s; 637 TT.cur_row = c; 638 639} 640 641//TODO search yank buffer by register 642//TODO yanks could be separate slices so no need to copy data 643//now only supports default register 644static int vi_yank(char reg, size_t from, int flags) 645{ 646 size_t start = from, end = TT.cursor; 647 char *str; 648 649 memset(TT.yank.data, 0, TT.yank.alloc); 650 if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from; 651 else TT.cursor = start; //yank moves cursor to left pos always? 652 653 if (TT.yank.alloc < end-from) { 654 size_t new_bounds = (1+end-from)/1024; 655 new_bounds += ((1+end-from)%1024) ? 1 : 0; 656 new_bounds *= 1024; 657 TT.yank.data = xrealloc(TT.yank.data, new_bounds); 658 TT.yank.alloc = new_bounds; 659 } 660 661 //this is naive copy 662 for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start); 663 664 *str = 0; 665 666 return 1; 667} 668 669static int vi_delete(char reg, size_t from, int flags) 670{ 671 size_t start = from, end = TT.cursor; 672 673 vi_yank(reg, from, flags); 674 675 if (TT.vi_mov_flag&0x80000000) 676 start = TT.cursor, end = from; 677 678 //pre adjust cursor move one right until at next valid rune 679 if (TT.vi_mov_flag&2) { 680 //TODO 681 } 682 //do slice cut 683 cut_str(start, end-start); 684 685 //cursor is at start at after delete 686 TT.cursor = start; 687 TT.filesize = text_filesize(); 688 //find line start by strrchr(/n) ++ 689 //set cur_col with crunch_n_str maybe? 690 TT.vi_mov_flag |= 0x30000000; 691 692 return 1; 693} 694 695static int vi_change(char reg, size_t to, int flags) 696{ 697 vi_delete(reg, to, flags); 698 TT.vi_mode = 2; 699 return 1; 700} 701 702static int cur_left(int count0, int count1, char *unused) 703{ 704 int count = count0*count1; 705 TT.vi_mov_flag |= 0x80000000; 706 for (;count && TT.cursor; count--) { 707 TT.cursor--; 708 if (text_byte(TT.cursor) == '\n') TT.cursor++; 709 check_cursor_bounds(); 710 } 711 return 1; 712} 713 714static int cur_right(int count0, int count1, char *unused) 715{ 716 int count = count0*count1, len, width = 0; 717 char buf[8] = {0}; 718 719 for (;count; count--) { 720 len = text_codepoint(buf, TT.cursor); 721 722 if (*buf == '\n') break; 723 else if (len > 0) TT.cursor += len; 724 else TT.cursor++; 725 726 for (;TT.cursor < TT.filesize;) { 727 if ((len = text_codepoint(buf, TT.cursor)) < 1) { 728 TT.cursor++; //we are not in valid data try jump over 729 continue; 730 } 731 732 if (utf8_lnw(&width, buf, len) && width) break; 733 else TT.cursor += len; 734 } 735 } 736 check_cursor_bounds(); 737 return 1; 738} 739 740//TODO column shift 741static int cur_up(int count0, int count1, char *unused) 742{ 743 int count = count0*count1; 744 for (;count--;) TT.cursor = text_psol(TT.cursor); 745 746 TT.vi_mov_flag |= 0x80000000; 747 check_cursor_bounds(); 748 return 1; 749} 750 751//TODO column shift 752static int cur_down(int count0, int count1, char *unused) 753{ 754 int count = count0*count1; 755 for (;count--;) TT.cursor = text_nsol(TT.cursor); 756 check_cursor_bounds(); 757 return 1; 758} 759 760static int vi_H(int count0, int count1, char *unused) 761{ 762 TT.cursor = text_sol(TT.screen); 763 return 1; 764} 765 766static int vi_L(int count0, int count1, char *unused) 767{ 768 TT.cursor = text_sol(TT.screen); 769 cur_down(TT.screen_height-1, 1, 0); 770 return 1; 771} 772 773static int vi_M(int count0, int count1, char *unused) 774{ 775 TT.cursor = text_sol(TT.screen); 776 cur_down(TT.screen_height/2, 1, 0); 777 return 1; 778} 779 780static int search_str(char *s) 781{ 782 size_t pos = text_strstr(TT.cursor+1, s); 783 784 if (TT.last_search != s) { 785 free(TT.last_search); 786 TT.last_search = xstrdup(s); 787 } 788 789 if (pos != SIZE_MAX) TT.cursor = pos; 790 check_cursor_bounds(); 791 return 0; 792} 793 794static int vi_yy(char reg, int count0, int count1) 795{ 796 size_t history = TT.cursor; 797 size_t pos = text_sol(TT.cursor); //go left to first char on line 798 TT.vi_mov_flag |= 0x4; 799 800 for (;count0; count0--) TT.cursor = text_nsol(TT.cursor); 801 802 vi_yank(reg, pos, 0); 803 804 TT.cursor = history; 805 return 1; 806} 807 808static int vi_dd(char reg, int count0, int count1) 809{ 810 size_t pos = text_sol(TT.cursor); //go left to first char on line 811 TT.vi_mov_flag |= 0x30000000; 812 813 for (;count0; count0--) TT.cursor = text_nsol(TT.cursor); 814 815 if (pos == TT.cursor && TT.filesize) pos--; 816 vi_delete(reg, pos, 0); 817 check_cursor_bounds(); 818 return 1; 819} 820 821static int vi_x(char reg, int count0, int count1) 822{ 823 size_t from = TT.cursor; 824 825 if (text_byte(TT.cursor) == '\n') { 826 cur_left(count0-1, 1, 0); 827 } 828 else { 829 cur_right(count0-1, 1, 0); 830 if (text_byte(TT.cursor) == '\n') TT.vi_mov_flag |= 2; 831 else cur_right(1, 1, 0); 832 } 833 834 vi_delete(reg, from, 0); 835 check_cursor_bounds(); 836 return 1; 837} 838 839static int vi_movw(int count0, int count1, char *unused) 840{ 841 int count = count0*count1; 842 while (count--) { 843 char c = text_byte(TT.cursor); 844 do { 845 if (TT.cursor > TT.filesize-1) break; 846 //if at empty jump to non empty 847 if (c == '\n') { 848 if (++TT.cursor > TT.filesize-1) break; 849 if ((c = text_byte(TT.cursor)) == '\n') break; 850 continue; 851 } else if (strchr(blank, c)) do { 852 if (++TT.cursor > TT.filesize-1) break; 853 c = text_byte(TT.cursor); 854 } while (strchr(blank, c)); 855 //if at special jump to non special 856 else if (strchr(specials, c)) do { 857 if (++TT.cursor > TT.filesize-1) break; 858 c = text_byte(TT.cursor); 859 } while (strchr(specials, c)); 860 //else jump to empty or spesial 861 else do { 862 if (++TT.cursor > TT.filesize-1) break; 863 c = text_byte(TT.cursor); 864 } while (c && !strchr(blank, c) && !strchr(specials, c)); 865 866 } while (strchr(blank, c) && c != '\n'); //never stop at empty 867 } 868 check_cursor_bounds(); 869 return 1; 870} 871 872static int vi_movb(int count0, int count1, char *unused) 873{ 874 int count = count0*count1; 875 int type = 0; 876 char c; 877 while (count--) { 878 c = text_byte(TT.cursor); 879 do { 880 if (!TT.cursor) break; 881 //if at empty jump to non empty 882 if (strchr(blank, c)) do { 883 if (!--TT.cursor) break; 884 c = text_byte(TT.cursor); 885 } while (strchr(blank, c)); 886 //if at special jump to non special 887 else if (strchr(specials, c)) do { 888 if (!--TT.cursor) break; 889 type = 0; 890 c = text_byte(TT.cursor); 891 } while (strchr(specials, c)); 892 //else jump to empty or spesial 893 else do { 894 if (!--TT.cursor) break; 895 type = 1; 896 c = text_byte(TT.cursor); 897 } while (!strchr(blank, c) && !strchr(specials, c)); 898 899 } while (strchr(blank, c)); //never stop at empty 900 } 901 //find first 902 for (;TT.cursor; TT.cursor--) { 903 c = text_byte(TT.cursor-1); 904 if (type && !strchr(blank, c) && !strchr(specials, c)) break; 905 else if (!type && !strchr(specials, c)) break; 906 } 907 908 TT.vi_mov_flag |= 0x80000000; 909 check_cursor_bounds(); 910 return 1; 911} 912 913static int vi_move(int count0, int count1, char *unused) 914{ 915 int count = count0*count1; 916 int type = 0; 917 char c; 918 919 if (count>1) vi_movw(count-1, 1, unused); 920 921 c = text_byte(TT.cursor); 922 if (strchr(specials, c)) type = 1; 923 TT.cursor++; 924 for (;TT.cursor < TT.filesize-1; TT.cursor++) { 925 c = text_byte(TT.cursor+1); 926 if (!type && (strchr(blank, c) || strchr(specials, c))) break; 927 else if (type && !strchr(specials, c)) break; 928 } 929 930 TT.vi_mov_flag |= 2; 931 check_cursor_bounds(); 932 return 1; 933} 934 935 936static void i_insert(char *str, int len) 937{ 938 if (!str || !len) return; 939 940 insert_str(xstrdup(str), TT.cursor, len, len, HEAP); 941 TT.cursor += len; 942 TT.filesize = text_filesize(); 943 TT.vi_mov_flag |= 0x30000000; 944} 945 946static int vi_zero(int count0, int count1, char *unused) 947{ 948 TT.cursor = text_sol(TT.cursor); 949 TT.cur_col = 0; 950 TT.vi_mov_flag |= 0x80000000; 951 return 1; 952} 953 954static int vi_dollar(int count0, int count1, char *unused) 955{ 956 size_t new = text_strchr(TT.cursor, '\n'); 957 958 if (new != TT.cursor) { 959 TT.cursor = new - 1; 960 TT.vi_mov_flag |= 2; 961 check_cursor_bounds(); 962 } 963 return 1; 964} 965 966static void vi_eol() 967{ 968 TT.cursor = text_strchr(TT.cursor, '\n'); 969 check_cursor_bounds(); 970} 971 972static void ctrl_b() 973{ 974 int i; 975 976 for (i=0; i<TT.screen_height-2; ++i) { 977 TT.screen = text_psol(TT.screen); 978 // TODO: retain x offset. 979 TT.cursor = text_psol(TT.screen); 980 } 981} 982 983static void ctrl_f() 984{ 985 int i; 986 987 for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen); 988 // TODO: real vi keeps the x position. 989 if (TT.screen > TT.cursor) TT.cursor = TT.screen; 990} 991 992static void ctrl_e() 993{ 994 TT.screen = text_nsol(TT.screen); 995 // TODO: real vi keeps the x position. 996 if (TT.screen > TT.cursor) TT.cursor = TT.screen; 997} 998 999static void ctrl_y() 1000{ 1001 TT.screen = text_psol(TT.screen); 1002 // TODO: only if we're on the bottom line 1003 TT.cursor = text_psol(TT.cursor); 1004 // TODO: real vi keeps the x position. 1005} 1006 1007//TODO check register where to push from 1008static int vi_push(char reg, int count0, int count1) 1009{ 1010 //if row changes during push original cursor position is kept 1011 //vi inconsistancy 1012 //if yank ends with \n push is linemode else push in place+1 1013 size_t history = TT.cursor; 1014 char *start = TT.yank.data; 1015 char *eol = strchr(start, '\n'); 1016 1017 if (start[strlen(start)-1] == '\n') { 1018 if ((TT.cursor = text_strchr(TT.cursor, '\n')) == SIZE_MAX) 1019 TT.cursor = TT.filesize; 1020 else TT.cursor = text_nsol(TT.cursor); 1021 } else cur_right(1, 1, 0); 1022 1023 i_insert(start, strlen(start)); 1024 if (eol) { 1025 TT.vi_mov_flag |= 0x10000000; 1026 TT.cursor = history; 1027 } 1028 1029 return 1; 1030} 1031 1032static int vi_find_c(int count0, int count1, char *symbol) 1033{ 1034//// int count = count0*count1; 1035 size_t pos = text_strchr(TT.cursor, *symbol); 1036 if (pos != SIZE_MAX) TT.cursor = pos; 1037 return 1; 1038} 1039 1040static int vi_find_cb(int count0, int count1, char *symbol) 1041{ 1042 //do backward search 1043 size_t pos = text_strrchr(TT.cursor, *symbol); 1044 if (pos != SIZE_MAX) TT.cursor = pos; 1045 return 1; 1046} 1047 1048//if count is not spesified should go to last line 1049static int vi_go(int count0, int count1, char *symbol) 1050{ 1051 size_t prev_cursor = TT.cursor; 1052 int count = count0*count1-1; 1053 TT.cursor = 0; 1054 1055 if (TT.vi_mov_flag&0x40000000 && (TT.cursor = TT.filesize) > 0) 1056 TT.cursor = text_sol(TT.cursor-1); 1057 else if (count) { 1058 size_t next = 0; 1059 for ( ;count && (next = text_strchr(next+1, '\n')) != SIZE_MAX; count--) 1060 TT.cursor = next; 1061 TT.cursor++; 1062 } 1063 1064 check_cursor_bounds(); //adjusts cursor column 1065 if (prev_cursor > TT.cursor) TT.vi_mov_flag |= 0x80000000; 1066 1067 return 1; 1068} 1069 1070static int vi_o(char reg, int count0, int count1) 1071{ 1072 TT.cursor = text_eol(TT.cursor); 1073 insert_str(xstrdup("\n"), TT.cursor++, 1, 1, HEAP); 1074 TT.vi_mov_flag |= 0x30000000; 1075 TT.vi_mode = 2; 1076 return 1; 1077} 1078 1079static int vi_O(char reg, int count0, int count1) 1080{ 1081 TT.cursor = text_psol(TT.cursor); 1082 return vi_o(reg, count0, count1); 1083} 1084 1085static int vi_D(char reg, int count0, int count1) 1086{ 1087 size_t pos = TT.cursor; 1088 if (!count0) return 1; 1089 vi_eol(); 1090 vi_delete(reg, pos, 0); 1091 if (--count0) vi_dd(reg, count0, 1); 1092 1093 check_cursor_bounds(); 1094 return 1; 1095} 1096 1097static int vi_I(char reg, int count0, int count1) 1098{ 1099 TT.cursor = text_sol(TT.cursor); 1100 TT.vi_mode = 2; 1101 return 1; 1102} 1103 1104static int vi_join(char reg, int count0, int count1) 1105{ 1106 size_t next; 1107 while (count0--) { 1108 //just strchr(/n) and cut_str(pos, 1); 1109 if ((next = text_strchr(TT.cursor, '\n')) == SIZE_MAX) break; 1110 TT.cursor = next+1; 1111 vi_delete(reg, TT.cursor-1, 0); 1112 } 1113 return 1; 1114} 1115 1116static int vi_find_next(char reg, int count0, int count1) 1117{ 1118 if (TT.last_search) search_str(TT.last_search); 1119 return 1; 1120} 1121 1122//NOTES 1123//vi-mode cmd syntax is 1124//("[REG])[COUNT0]CMD[COUNT1](MOV) 1125//where: 1126//------------------------------------------------------------- 1127//"[REG] is optional buffer where deleted/yanked text goes REG can be 1128// atleast 0-9, a-z or default " 1129//[COUNT] is optional multiplier for cmd execution if there is 2 COUNT 1130// operations they are multiplied together 1131//CMD is operation to be executed 1132//(MOV) is movement operation, some CMD does not require MOV and some 1133// have special cases such as dd, yy, also movements can work without 1134// CMD 1135//ex commands can be even more complicated than this.... 1136// 1137struct vi_cmd_param { 1138 const char* cmd; 1139 unsigned flags; 1140 int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS 1141}; 1142struct vi_mov_param { 1143 const char* mov; 1144 unsigned flags; 1145 int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params 1146}; 1147//special cases without MOV and such 1148struct vi_special_param { 1149 const char *cmd; 1150 int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1 1151}; 1152struct vi_special_param vi_special[] = 1153{ 1154 {"D", &vi_D}, 1155 {"I", &vi_I}, 1156 {"J", &vi_join}, 1157 {"O", &vi_O}, 1158 {"n", &vi_find_next}, 1159 {"o", &vi_o}, 1160 {"p", &vi_push}, 1161 {"x", &vi_x}, 1162 {"dd", &vi_dd}, 1163 {"yy", &vi_yy}, 1164}; 1165//there is around ~47 vi moves 1166//some of them need extra params 1167//such as f and ' 1168struct vi_mov_param vi_movs[] = 1169{ 1170 {"0", 0, &vi_zero}, 1171 {"b", 0, &vi_movb}, 1172 {"e", 0, &vi_move}, 1173 {"G", 0, &vi_go}, 1174 {"H", 0, &vi_H}, 1175 {"h", 0, &cur_left}, 1176 {"j", 0, &cur_down}, 1177 {"k", 0, &cur_up}, 1178 {"L", 0, &vi_L}, 1179 {"l", 0, &cur_right}, 1180 {"M", 0, &vi_M}, 1181 {"w", 0, &vi_movw}, 1182 {"$", 0, &vi_dollar}, 1183 {"f", 1, &vi_find_c}, 1184 {"F", 1, &vi_find_cb}, 1185}; 1186//change and delete unfortunately behave different depending on move command, 1187//such as ce cw are same, but dw and de are not... 1188//also dw stops at w position and cw seem to stop at e pos+1... 1189//so after movement we need to possibly set up some flags before executing 1190//command, and command needs to adjust... 1191struct vi_cmd_param vi_cmds[] = 1192{ 1193 {"c", 1, &vi_change}, 1194 {"d", 1, &vi_delete}, 1195 {"y", 1, &vi_yank}, 1196}; 1197 1198static int run_vi_cmd(char *cmd) 1199{ 1200 int i = 0, val = 0; 1201 char *cmd_e; 1202 int (*vi_cmd)(char, size_t, int) = 0; 1203 int (*vi_mov)(int, int, char*) = 0; 1204 1205 TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0; 1206 TT.vi_reg = '"'; 1207 1208 if (*cmd == '"') { 1209 cmd++; 1210 TT.vi_reg = *cmd; //TODO check validity 1211 cmd++; 1212 } 1213 errno = 0; 1214 val = strtol(cmd, &cmd_e, 10); 1215 if (errno || val == 0) val = 1, TT.vi_mov_flag |= 0x40000000; 1216 else cmd = cmd_e; 1217 TT.count0 = val; 1218 1219 for (i = 0; i < ARRAY_LEN(vi_special); i++) { 1220 if (strstr(cmd, vi_special[i].cmd)) { 1221 return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1); 1222 } 1223 } 1224 1225 for (i = 0; i < ARRAY_LEN(vi_cmds); i++) { 1226 if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) { 1227 vi_cmd = vi_cmds[i].vi_cmd; 1228 cmd += strlen(vi_cmds[i].cmd); 1229 break; 1230 } 1231 } 1232 errno = 0; 1233 val = strtol(cmd, &cmd_e, 10); 1234 if (errno || val == 0) val = 1; 1235 else cmd = cmd_e; 1236 TT.count1 = val; 1237 1238 for (i = 0; i < ARRAY_LEN(vi_movs); i++) { 1239 if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) { 1240 vi_mov = vi_movs[i].vi_mov; 1241 TT.vi_mov_flag |= vi_movs[i].flags; 1242 cmd++; 1243 if (TT.vi_mov_flag&1 && !(*cmd)) return 0; 1244 break; 1245 } 1246 } 1247 if (vi_mov) { 1248 int prev_cursor = TT.cursor; 1249 if (vi_mov(TT.count0, TT.count1, cmd)) { 1250 if (vi_cmd) return (vi_cmd(TT.vi_reg, prev_cursor, TT.vi_mov_flag)); 1251 else return 1; 1252 } else return 0; //return some error 1253 } 1254 return 0; 1255} 1256 1257 1258static int run_ex_cmd(char *cmd) 1259{ 1260 if (cmd[0] == '/') { 1261 search_str(&cmd[1]); 1262 } else if (cmd[0] == '?') { 1263 // TODO: backwards search. 1264 } else if (cmd[0] == ':') { 1265 if (!strcmp(&cmd[1], "q") || !strcmp(&cmd[1], "q!")) { 1266 // TODO: if no !, check whether file modified. 1267 //exit_application; 1268 return -1; 1269 } 1270 else if (strstr(&cmd[1], "wq")) { 1271 write_file(0); 1272 return -1; 1273 } 1274 else if (strstr(&cmd[1], "w")) { 1275 write_file(0); 1276 return 1; 1277 } 1278 else if (strstr(&cmd[1], "set list")) { 1279 TT.list = 1; 1280 TT.vi_mov_flag |= 0x30000000; 1281 return 1; 1282 } 1283 else if (strstr(&cmd[1], "set nolist")) { 1284 TT.list = 0; 1285 TT.vi_mov_flag |= 0x30000000; 1286 return 1; 1287 } 1288 } 1289 return 0; 1290 1291} 1292 1293static int vi_crunch(FILE *out, int cols, int wc) 1294{ 1295 int ret = 0; 1296 if (wc < 32 && TT.list) { 1297 tty_esc("1m"); 1298 ret = crunch_escape(out,cols,wc); 1299 tty_esc("m"); 1300 } else if (wc == 0x09) { 1301 if (out) { 1302 int i = TT.tabstop; 1303 for (;i--;) fputs(" ", out); 1304 } 1305 ret = TT.tabstop; 1306 } else if (wc == '\n') return 0; 1307 return ret; 1308} 1309 1310//crunch_str with n bytes restriction for printing substrings or 1311//non null terminated strings 1312static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore, 1313 int (*escout)(FILE *out, int cols, int wc)) 1314{ 1315 int columns = 0, col, bytes; 1316 char *start, *end; 1317 unsigned wc; 1318 1319 for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) { 1320 if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) { 1321 if (!escmore || wc>255 || !strchr(escmore, wc)) { 1322 if (width-columns<col) break; 1323 if (out) fwrite(end, bytes, 1, out); 1324 1325 continue; 1326 } 1327 } 1328 1329 if (bytes<1) { 1330 bytes = 1; 1331 wc = *end; 1332 } 1333 col = width-columns; 1334 if (col<1) break; 1335 if (escout) { 1336 if ((col = escout(out, col, wc))<0) break; 1337 } else if (out) fwrite(end, 1, bytes, out); 1338 } 1339 *str = end; 1340 1341 return columns; 1342} 1343 1344static void draw_page() 1345{ 1346 unsigned y = 0; 1347 int x = 0; 1348 1349 char *line = 0, *end = 0; 1350 int bytes = 0; 1351 1352 //screen coordinates for cursor 1353 int cy_scr = 0, cx_scr = 0; 1354 1355 //variables used only for cursor handling 1356 int aw = 0, iw = 0, clip = 0, margin = 8; 1357 1358 int scroll = 0, redraw = 0; 1359 1360 int SSOL, SOL; 1361 1362 1363 adjust_screen_buffer(); 1364 //redraw = 3; //force full redraw 1365 redraw = (TT.vi_mov_flag & 0x30000000)>>28; 1366 1367 scroll = TT.drawn_row-TT.scr_row; 1368 if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3; 1369 else if (abs(scroll)>TT.screen_height/2) redraw = 3; 1370 1371 tty_jump(0, 0); 1372 if (redraw&2) tty_esc("2J"), tty_esc("H"); //clear screen 1373 else if (scroll>0) printf("\033[%dL", scroll); //scroll up 1374 else if (scroll<0) printf("\033[%dM", -scroll); //scroll down 1375 1376 SOL = text_sol(TT.cursor); 1377 bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf)); 1378 line = toybuf; 1379 1380 for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL); 1381 1382 cy_scr = y; 1383 1384 //draw cursor row 1385 ///////////////////////////////////////////////////////////// 1386 //for long lines line starts to scroll when cursor hits margin 1387 bytes = TT.cursor-SOL; // TT.cur_col; 1388 end = line; 1389 1390 1391 tty_jump(0, y); 1392 tty_esc("2K"); 1393 //find cursor position 1394 aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch); 1395 1396 //if we need to render text that is not inserted to buffer yet 1397 if (TT.vi_mode == 2 && TT.il->len) { 1398 char* iend = TT.il->data; //input end 1399 x = 0; 1400 //find insert end position 1401 iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch); 1402 clip = (aw+iw) - TT.screen_width+margin; 1403 1404 //if clipped area is bigger than text before insert 1405 if (clip > aw) { 1406 clip -= aw; 1407 iend = TT.il->data; 1408 1409 iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch); 1410 x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch); 1411 } else { 1412 iend = TT.il->data; 1413 end = line; 1414 1415 //if clipped area is substring from cursor row start 1416 aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch); 1417 x = crunch_str(&end, aw, stdout, "\t\n", vi_crunch); 1418 x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch); 1419 } 1420 } 1421 //when not inserting but still need to keep cursor inside screen 1422 //margin area 1423 else if ( aw+margin > TT.screen_width) { 1424 clip = aw-TT.screen_width+margin; 1425 end = line; 1426 aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch); 1427 x = crunch_str(&end, aw, stdout, "\t\n", vi_crunch); 1428 } 1429 else { 1430 end = line; 1431 x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch); 1432 } 1433 cx_scr = x; 1434 cy_scr = y; 1435 x += crunch_str(&end, TT.screen_width-x, stdout, "\t\n", vi_crunch); 1436 1437 //start drawing all other rows that needs update 1438 /////////////////////////////////////////////////////////////////// 1439 y = 0, SSOL = TT.screen, line = toybuf; 1440 bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf)); 1441 1442 //if we moved around in long line might need to redraw everything 1443 if (clip != TT.drawn_col) redraw = 3; 1444 1445 for (; y < TT.screen_height; y++ ) { 1446 int draw_line = 0; 1447 if (SSOL == SOL) { 1448 line = toybuf; 1449 SSOL += bytes+1; 1450 bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf)); 1451 continue; 1452 } else if (redraw) draw_line++; 1453 else if (scroll<0 && TT.screen_height-y-1<-scroll) 1454 scroll++, draw_line++; 1455 else if (scroll>0) scroll--, draw_line++; 1456 1457 tty_jump(0, y); 1458 if (draw_line) { 1459 tty_esc("2K"); 1460 if (line && strlen(line)) { 1461 aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch); 1462 crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch); 1463 if ( *line ) printf("@"); 1464 } else printf("\033[2m~\033[m"); 1465 } 1466 if (SSOL+bytes < TT.filesize) { 1467 line = toybuf; 1468 SSOL += bytes+1; 1469 bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf)); 1470 } else line = 0; 1471 } 1472 1473 TT.drawn_row = TT.scr_row, TT.drawn_col = clip; 1474 1475 // Finished updating visual area, show status line. 1476 tty_jump(0, TT.screen_height); 1477 tty_esc("2K"); 1478 if (TT.vi_mode == 2) printf("\033[1m-- INSERT --\033[m"); 1479 if (!TT.vi_mode) { 1480 cx_scr = printf("%s", TT.il->data); 1481 cy_scr = TT.screen_height; 1482 *toybuf = 0; 1483 } else { 1484 // TODO: the row,col display doesn't show the cursor column 1485 // TODO: real vi shows the percentage by lines, not bytes 1486 sprintf(toybuf, "%zu/%zuC %zu%% %d,%d", TT.cursor, TT.filesize, 1487 (100*TT.cursor)/(TT.filesize ? : 1), TT.cur_row+1, TT.cur_col+1); 1488 if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1); 1489 } 1490 tty_jump(TT.screen_width-strlen(toybuf), TT.screen_height); 1491 printf("%s", toybuf); 1492 1493 tty_jump(cx_scr, cy_scr); 1494 xflush(1); 1495} 1496 1497void vi_main(void) 1498{ 1499 char stdout_buf[8192]; 1500 char keybuf[16] = {0}; 1501 char vi_buf[16] = {0}; 1502 char utf8_code[8] = {0}; 1503 int utf8_dec_p = 0, vi_buf_pos = 0; 1504 FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0; 1505 1506 TT.il = xzalloc(sizeof(struct str_line)); 1507 TT.il->data = xzalloc(80); 1508 TT.yank.data = xzalloc(128); 1509 1510 TT.il->alloc = 80, TT.yank.alloc = 128; 1511 1512 linelist_load(0); 1513 1514 TT.vi_mov_flag = 0x20000000; 1515 TT.vi_mode = 1, TT.tabstop = 8; 1516 1517 TT.screen_width = 80, TT.screen_height = 24; 1518 terminal_size(&TT.screen_width, &TT.screen_height); 1519 TT.screen_height -= 1; 1520 1521 // Avoid flicker. 1522 setbuffer(stdout, stdout_buf, sizeof(stdout_buf)); 1523 1524 xsignal(SIGWINCH, generic_signal); 1525 set_terminal(0, 1, 0, 0); 1526 //writes stdout into different xterm buffer so when we exit 1527 //we dont get scroll log full of junk 1528 tty_esc("?1049h"); 1529 1530 for (;;) { 1531 int key = 0; 1532 1533 draw_page(); 1534 if (script) { 1535 key = fgetc(script); 1536 if (key == EOF) { 1537 fclose(script); 1538 script = 0; 1539 key = scan_key(keybuf, -1); 1540 } 1541 } else key = scan_key(keybuf, -1); 1542 1543 if (key == -1) goto cleanup_vi; 1544 else if (key == -3) { 1545 toys.signal = 0; 1546 terminal_size(&TT.screen_width, &TT.screen_height); 1547 TT.screen_height -= 1; //TODO this is hack fix visual alignment 1548 continue; 1549 } 1550 1551 // TODO: support cursor keys in ex mode too. 1552 if (TT.vi_mode && key>=256) { 1553 key -= 256; 1554 if (key==KEY_UP) cur_up(1, 1, 0); 1555 else if (key==KEY_DOWN) cur_down(1, 1, 0); 1556 else if (key==KEY_LEFT) cur_left(1, 1, 0); 1557 else if (key==KEY_RIGHT) cur_right(1, 1, 0); 1558 else if (key==KEY_HOME) vi_zero(1, 1, 0); 1559 else if (key==KEY_END) vi_dollar(1, 1, 0); 1560 else if (key==KEY_PGDN) ctrl_f(); 1561 else if (key==KEY_PGUP) ctrl_b(); 1562 continue; 1563 } 1564 1565 if (TT.vi_mode == 1) { //NORMAL 1566 switch (key) { 1567 case '/': 1568 case '?': 1569 case ':': 1570 TT.vi_mode = 0; 1571 TT.il->data[0]=key; 1572 TT.il->len++; 1573 break; 1574 case 'A': 1575 vi_eol(); 1576 TT.vi_mode = 2; 1577 break; 1578 case 'a': 1579 cur_right(1, 1, 0); 1580 // FALLTHROUGH 1581 case 'i': 1582 TT.vi_mode = 2; 1583 break; 1584 case 'B'-'@': 1585 ctrl_b(); 1586 break; 1587 case 'E'-'@': 1588 ctrl_e(); 1589 break; 1590 case 'F'-'@': 1591 ctrl_f(); 1592 break; 1593 case 'Y'-'@': 1594 ctrl_y(); 1595 break; 1596 case 27: 1597 vi_buf[0] = 0; 1598 vi_buf_pos = 0; 1599 break; 1600 default: 1601 if (key > 0x20 && key < 0x7B) { 1602 vi_buf[vi_buf_pos] = key;//TODO handle input better 1603 vi_buf_pos++; 1604 if (run_vi_cmd(vi_buf)) { 1605 memset(vi_buf, 0, 16); 1606 vi_buf_pos = 0; 1607 } 1608 else if (vi_buf_pos == 16) { 1609 vi_buf_pos = 0; 1610 memset(vi_buf, 0, 16); 1611 } 1612 1613 } 1614 1615 break; 1616 } 1617 } else if (TT.vi_mode == 0) { //EX MODE 1618 switch (key) { 1619 case 0x7F: 1620 case 0x08: 1621 if (TT.il->len > 1) { 1622 TT.il->data[--TT.il->len] = 0; 1623 break; 1624 } 1625 // FALLTHROUGH 1626 case 27: 1627 TT.vi_mode = 1; 1628 TT.il->len = 0; 1629 memset(TT.il->data, 0, TT.il->alloc); 1630 break; 1631 case 0x0A: 1632 case 0x0D: 1633 if (run_ex_cmd(TT.il->data) == -1) 1634 goto cleanup_vi; 1635 TT.vi_mode = 1; 1636 TT.il->len = 0; 1637 memset(TT.il->data, 0, TT.il->alloc); 1638 break; 1639 default: //add chars to ex command until ENTER 1640 if (key >= 0x20 && key < 0x7F) { //might be utf? 1641 if (TT.il->len == TT.il->alloc) { 1642 TT.il->data = realloc(TT.il->data, TT.il->alloc*2); 1643 TT.il->alloc *= 2; 1644 } 1645 TT.il->data[TT.il->len] = key; 1646 TT.il->len++; 1647 } 1648 break; 1649 } 1650 } else if (TT.vi_mode == 2) {//INSERT MODE 1651 switch (key) { 1652 case 27: 1653 i_insert(TT.il->data, TT.il->len); 1654 cur_left(1, 1, 0); 1655 TT.vi_mode = 1; 1656 TT.il->len = 0; 1657 memset(TT.il->data, 0, TT.il->alloc); 1658 break; 1659 case 0x7F: 1660 case 0x08: 1661 if (TT.il->len) { 1662 char *last = utf8_last(TT.il->data, TT.il->len); 1663 int shrink = strlen(last); 1664 memset(last, 0, shrink); 1665 TT.il->len -= shrink; 1666 } 1667 break; 1668 case 0x0A: 1669 case 0x0D: 1670 //insert newline 1671 // 1672 TT.il->data[TT.il->len++] = '\n'; 1673 i_insert(TT.il->data, TT.il->len); 1674 TT.il->len = 0; 1675 memset(TT.il->data, 0, TT.il->alloc); 1676 break; 1677 default: 1678 if ((key >= 0x20 || key == 0x09) && 1679 utf8_dec(key, utf8_code, &utf8_dec_p)) { 1680 1681 if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) { 1682 TT.il->data = realloc(TT.il->data, TT.il->alloc*2); 1683 TT.il->alloc *= 2; 1684 } 1685 strcpy(TT.il->data+TT.il->len, utf8_code); 1686 TT.il->len += utf8_dec_p; 1687 utf8_dec_p = 0; 1688 *utf8_code = 0; 1689 1690 } 1691 break; 1692 } 1693 } 1694 } 1695cleanup_vi: 1696 linelist_unload(); 1697 free(TT.il->data), free(TT.il), free(TT.yank.data); 1698 tty_reset(); 1699 tty_esc("?1049l"); 1700} 1701