toybox/toys/pending/vi.c
<<
>>
Prefs
   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    xputsn("\e[1m");
1298    ret = crunch_escape(out,cols,wc);
1299    xputsn("\e[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  xputsn("\e[H"); // jump to top left
1372  if (redraw&2) xputsn("\e[2J\e[H");   //clear screen
1373  else if (scroll>0) printf("\e[%dL", scroll);  //scroll up
1374  else if (scroll<0) printf("\e[%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  printf("\e[%u;0H\e[2K", y+1);
1392  //find cursor position
1393  aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
1394
1395  //if we need to render text that is not inserted to buffer yet
1396  if (TT.vi_mode == 2 && TT.il->len) {
1397    char* iend = TT.il->data; //input end
1398    x = 0;
1399    //find insert end position
1400    iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
1401    clip = (aw+iw) - TT.screen_width+margin;
1402
1403    //if clipped area is bigger than text before insert
1404    if (clip > aw) {
1405      clip -= aw;
1406      iend = TT.il->data;
1407
1408      iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
1409      x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
1410    } else {
1411      iend = TT.il->data;
1412      end = line;
1413
1414      //if clipped area is substring from cursor row start
1415      aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
1416      x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
1417      x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
1418    }
1419  }
1420  //when not inserting but still need to keep cursor inside screen
1421  //margin area
1422  else if ( aw+margin > TT.screen_width) {
1423    clip = aw-TT.screen_width+margin;
1424    end = line;
1425    aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
1426    x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
1427  }
1428  else {
1429    end = line;
1430    x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
1431  }
1432  cx_scr = x;
1433  cy_scr = y;
1434  x += crunch_str(&end, TT.screen_width-x,  stdout, "\t\n", vi_crunch);
1435
1436  //start drawing all other rows that needs update
1437  ///////////////////////////////////////////////////////////////////
1438  y = 0, SSOL = TT.screen, line = toybuf;
1439  bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
1440
1441  //if we moved around in long line might need to redraw everything
1442  if (clip != TT.drawn_col) redraw = 3;
1443
1444  for (; y < TT.screen_height; y++ ) {
1445    int draw_line = 0;
1446    if (SSOL == SOL) {
1447      line = toybuf;
1448      SSOL += bytes+1;
1449      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
1450      continue;
1451    } else if (redraw) draw_line++;
1452    else if (scroll<0 && TT.screen_height-y-1<-scroll)
1453      scroll++, draw_line++;
1454    else if (scroll>0) scroll--, draw_line++;
1455
1456    printf("\e[%u;0H", y+1);
1457    if (draw_line) {
1458      printf("\e[2K");
1459      if (line && strlen(line)) {
1460        aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
1461        crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
1462        if ( *line ) printf("@");
1463      } else printf("\e[2m~\e[m");
1464    }
1465    if (SSOL+bytes < TT.filesize)  {
1466      line = toybuf;
1467      SSOL += bytes+1;
1468      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
1469   } else line = 0;
1470  }
1471
1472  TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
1473
1474  // Finished updating visual area, show status line.
1475  printf("\e[%u;0H\e[2K", TT.screen_height+1);
1476  if (TT.vi_mode == 2) printf("\e[1m-- INSERT --\e[m");
1477  if (!TT.vi_mode) {
1478    cx_scr = printf("%s", TT.il->data);
1479    cy_scr = TT.screen_height;
1480    *toybuf = 0;
1481  } else {
1482    // TODO: the row,col display doesn't show the cursor column
1483    // TODO: real vi shows the percentage by lines, not bytes
1484    sprintf(toybuf, "%zu/%zuC  %zu%%  %d,%d", TT.cursor, TT.filesize,
1485      (100*TT.cursor)/(TT.filesize ? : 1), TT.cur_row+1, TT.cur_col+1);
1486    if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
1487  }
1488  printf("\e[%u;%uH%s\e[%u;%uH", TT.screen_height+1,
1489    (int) (1+TT.screen_width-strlen(toybuf)),
1490    toybuf, cy_scr+1, cx_scr+1);
1491  xflush(1);
1492}
1493
1494void vi_main(void)
1495{
1496  char stdout_buf[BUFSIZ];
1497  char keybuf[16] = {0};
1498  char vi_buf[16] = {0};
1499  char utf8_code[8] = {0};
1500  int utf8_dec_p = 0, vi_buf_pos = 0;
1501  FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
1502
1503  TT.il = xzalloc(sizeof(struct str_line));
1504  TT.il->data = xzalloc(80);
1505  TT.yank.data = xzalloc(128);
1506
1507  TT.il->alloc = 80, TT.yank.alloc = 128;
1508
1509  linelist_load(0);
1510
1511  TT.vi_mov_flag = 0x20000000;
1512  TT.vi_mode = 1, TT.tabstop = 8;
1513
1514  TT.screen_width = 80, TT.screen_height = 24;
1515  terminal_size(&TT.screen_width, &TT.screen_height);
1516  TT.screen_height -= 1;
1517
1518  // Avoid flicker.
1519  setbuf(stdout, stdout_buf);
1520
1521  xsignal(SIGWINCH, generic_signal);
1522  set_terminal(0, 1, 0, 0);
1523  //writes stdout into different xterm buffer so when we exit
1524  //we dont get scroll log full of junk
1525  xputsn("\e[?1049h");
1526
1527  for (;;) {
1528    int key = 0;
1529
1530    draw_page();
1531    if (script) {
1532      key = fgetc(script);
1533      if (key == EOF) {
1534        fclose(script);
1535        script = 0;
1536        key = scan_key(keybuf, -1);
1537      }
1538    } else key = scan_key(keybuf, -1);
1539
1540    if (key == -1) goto cleanup_vi;
1541    else if (key == -3) {
1542      toys.signal = 0;
1543      terminal_size(&TT.screen_width, &TT.screen_height);
1544      TT.screen_height -= 1; //TODO this is hack fix visual alignment
1545      continue;
1546    }
1547
1548    // TODO: support cursor keys in ex mode too.
1549    if (TT.vi_mode && key>=256) {
1550      key -= 256;
1551      if (key==KEY_UP) cur_up(1, 1, 0);
1552      else if (key==KEY_DOWN) cur_down(1, 1, 0);
1553      else if (key==KEY_LEFT) cur_left(1, 1, 0);
1554      else if (key==KEY_RIGHT) cur_right(1, 1, 0);
1555      else if (key==KEY_HOME) vi_zero(1, 1, 0);
1556      else if (key==KEY_END) vi_dollar(1, 1, 0);
1557      else if (key==KEY_PGDN) ctrl_f();
1558      else if (key==KEY_PGUP) ctrl_b();
1559      continue;
1560    }
1561
1562    if (TT.vi_mode == 1) { //NORMAL
1563      switch (key) {
1564        case '/':
1565        case '?':
1566        case ':':
1567          TT.vi_mode = 0;
1568          TT.il->data[0]=key;
1569          TT.il->len++;
1570          break;
1571        case 'A':
1572          vi_eol();
1573          TT.vi_mode = 2;
1574          break;
1575        case 'a':
1576          cur_right(1, 1, 0);
1577          // FALLTHROUGH
1578        case 'i':
1579          TT.vi_mode = 2;
1580          break;
1581        case 'B'-'@':
1582          ctrl_b();
1583          break;
1584        case 'E'-'@':
1585          ctrl_e();
1586          break;
1587        case 'F'-'@':
1588          ctrl_f();
1589          break;
1590        case 'Y'-'@':
1591          ctrl_y();
1592          break;
1593        case 27:
1594          vi_buf[0] = 0;
1595          vi_buf_pos = 0;
1596          break;
1597        default:
1598          if (key > 0x20 && key < 0x7B) {
1599            vi_buf[vi_buf_pos] = key;//TODO handle input better
1600            vi_buf_pos++;
1601            if (run_vi_cmd(vi_buf)) {
1602              memset(vi_buf, 0, 16);
1603              vi_buf_pos = 0;
1604            }
1605            else if (vi_buf_pos == 16) {
1606              vi_buf_pos = 0;
1607              memset(vi_buf, 0, 16);
1608            }
1609
1610          }
1611
1612          break;
1613      }
1614    } else if (TT.vi_mode == 0) { //EX MODE
1615      switch (key) {
1616        case 0x7F:
1617        case 0x08:
1618          if (TT.il->len > 1) {
1619            TT.il->data[--TT.il->len] = 0;
1620            break;
1621          }
1622          // FALLTHROUGH
1623        case 27:
1624          TT.vi_mode = 1;
1625          TT.il->len = 0;
1626          memset(TT.il->data, 0, TT.il->alloc);
1627          break;
1628        case 0x0A:
1629        case 0x0D:
1630          if (run_ex_cmd(TT.il->data) == -1)
1631            goto cleanup_vi;
1632          TT.vi_mode = 1;
1633          TT.il->len = 0;
1634          memset(TT.il->data, 0, TT.il->alloc);
1635          break;
1636        default: //add chars to ex command until ENTER
1637          if (key >= 0x20 && key < 0x7F) { //might be utf?
1638            if (TT.il->len == TT.il->alloc) {
1639              TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
1640              TT.il->alloc *= 2;
1641            }
1642            TT.il->data[TT.il->len] = key;
1643            TT.il->len++;
1644          }
1645          break;
1646      }
1647    } else if (TT.vi_mode == 2) {//INSERT MODE
1648      switch (key) {
1649        case 27:
1650          i_insert(TT.il->data, TT.il->len);
1651          cur_left(1, 1, 0);
1652          TT.vi_mode = 1;
1653          TT.il->len = 0;
1654          memset(TT.il->data, 0, TT.il->alloc);
1655          break;
1656        case 0x7F:
1657        case 0x08:
1658          if (TT.il->len) {
1659            char *last = utf8_last(TT.il->data, TT.il->len);
1660            int shrink = strlen(last);
1661            memset(last, 0, shrink);
1662            TT.il->len -= shrink;
1663          }
1664          break;
1665        case 0x0A:
1666        case 0x0D:
1667          //insert newline
1668          //
1669          TT.il->data[TT.il->len++] = '\n';
1670          i_insert(TT.il->data, TT.il->len);
1671          TT.il->len = 0;
1672          memset(TT.il->data, 0, TT.il->alloc);
1673          break;
1674        default:
1675          if ((key >= 0x20 || key == 0x09) &&
1676              utf8_dec(key, utf8_code, &utf8_dec_p)) {
1677
1678            if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) {
1679              TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
1680              TT.il->alloc *= 2;
1681            }
1682            strcpy(TT.il->data+TT.il->len, utf8_code);
1683            TT.il->len += utf8_dec_p;
1684            utf8_dec_p = 0;
1685            *utf8_code = 0;
1686
1687          }
1688          break;
1689      }
1690    }
1691  }
1692cleanup_vi:
1693  linelist_unload();
1694  free(TT.il->data), free(TT.il), free(TT.yank.data);
1695  tty_reset();
1696  xputsn("\e[?1049l");
1697}
1698