Справочник по консольным командам Toybox для Android 12


  Ver.0.8.4     Ver.0.8.9     Pending  

Путь: Toys/Pending, команды версии: Ver.4     Ver.9


vi

Комментарии в файле vi.c :

usage: vi [-s script] FILE

  • -s script: запустить файл сценария Визуальный текстовый редактор. Предшествует появлению стандартизированных клавиш управления курсором, поэтому элементы управления странные и исторические.

  • usage: vi [-s script] FILE

  • -s script: run script file Visual text editor. Predates the existence of standardized cursor keys, so the controls are weird and historical.

  • Исходный текст в файле vi.c

    #define FOR_vi
    #include "toys.h"
    
    GLOBALS(
      char *s;
      int vi_mode, tabstop, list;
      int cur_col, cur_row, scr_row;
      int drawn_row, drawn_col;
      int count0, count1, vi_mov_flag;
      unsigned screen_height, screen_width;
      char vi_reg, *last_search;
      struct str_line {
        int alloc;
        int len;
        char *data;
      } *il;
      size_t screen, cursor; //offsets
      //yank buffer
      struct yank_buf {
        char reg;
        int alloc;
        char* data;
      } yank;
    
      int modified;
      size_t filesize;
    // mem_block contains RO data that is either original file as mmap
    // or heap allocated inserted data
    //
    //
    //
      struct block_list {
        struct block_list *next, *prev;
        struct mem_block {
          size_t size;
          size_t len;
          enum alloc_flag {
            MMAP,  //can be munmap() before exit()
            HEAP,  //can be free() before exit()
            STACK, //global or stack perhaps toybuf
          } alloc;
          const char *data;
        } *node;
      } *text;
    
    // slices do not contain actual allocated data but slices of data in mem_block
    // when file is first opened it has only one slice.
    // after inserting data into middle new mem_block is allocated for insert data
    // and 3 slices are created, where first and last slice are pointing to original
    // mem_block with offsets, and middle slice is pointing to newly allocated block
    // When deleting, data is not freed but mem_blocks are sliced more such way that
    // deleted data left between 2 slices
      struct slice_list {
        struct slice_list *next, *prev;
        struct slice {
          size_t len;
          const char *data;
        } *node;
      } *slices;
    )
    
    static const char *blank = " \n\r\t";
    static const char *specials = ",.:;=-+*/(){}<>[]!@#$%^&|\\?\"\'";
    
    //get utf8 length and width at same time
    static int utf8_lnw(int *width, char *s, int bytes)
    {
      unsigned wc;
      int length = 1;
    
      if (*s == '\t') *width = TT.tabstop;
      else {
        length = utf8towc(&wc, s, bytes);
        if (length < 1) length = 0, *width = 0;
        else *width = wcwidth(wc);
      }
      return length;
    }
    
    static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
    {
      int len = 0;
      char *c = utf8_scratch;
      c[*sta_p] = key;
      if (!(*sta_p))  *c = key;
      if (*c < 0x7F) { *sta_p = 1; return 1; }
      if ((*c & 0xE0) == 0xc0) len = 2;
      else if ((*c & 0xF0) == 0xE0 ) len = 3;
      else if ((*c & 0xF8) == 0xF0 ) len = 4;
      else {*sta_p = 0; return 0; }
    
      (*sta_p)++;
    
      if (*sta_p == 1) return 0;
      if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
    
      if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
    
      return 0;
    }
    
    static char* utf8_last(char* str, int size)
    {
      char* end = str+size;
      int pos = size, len, width = 0;
      for (;pos >= 0; end--, pos--) {
        len = utf8_lnw(&width, end, size-pos);
        if (len && width) return end;
      }
      return 0;
    }
    
    struct double_list *dlist_add_before(struct double_list **head,
      struct double_list **list, char *data)
    {
      struct double_list *new = xmalloc(sizeof(struct double_list));
      new->data = data;
      if (*list == *head) *head = new;
    
      dlist_add_nomalloc(list, new);
      return new;
    }
    
    struct double_list *dlist_add_after(struct double_list **head,
      struct double_list **list, char *data)
    {
      struct double_list *new = xmalloc(sizeof(struct double_list));
      new->data = data;
    
      if (*list) {
        new->prev = *list;
        new->next = (*list)->next;
        (*list)->next->prev = new;
        (*list)->next = new;
      } else *head = *list = new->next = new->prev = new;
      return new;
    }
    
    // str must be already allocated
    // ownership of allocated data is moved
    // data, pre allocated data
    // offset, offset in whole text
    // size, data allocation size of given data
    // len, length of the string
    // type, define allocation type for cleanup purposes at app exit
    static int insert_str(const char *data, size_t offset, size_t size, size_t len,
      enum alloc_flag type)
    {
      struct mem_block *b = xmalloc(sizeof(struct mem_block));
      struct slice *next = xmalloc(sizeof(struct slice));
      struct slice_list *s = TT.slices;
      b->size = size;
      b->len = len;
      b->alloc = type;
      b->data = data;
      next->len = len;
      next->data = data;
    
      //mem blocks can be just added unordered
      TT.text = (struct block_list *)dlist_add((struct double_list **)&TT.text,
        (char *)b);
    
      if (!s) {
        TT.slices = (struct slice_list *)dlist_add(
          (struct double_list **)&TT.slices,
          (char *)next);
      } else {
        size_t pos = 0;
        //search insertation point for slice
        do {
          if (pos<=offset && pos+s->node->len>offset) break;
          pos += s->node->len;
          s = s->next;
          if (s == TT.slices) return -1; //error out of bounds
        } while (1);
        //need to cut previous slice into 2 since insert is in middle
        if (pos+s->node->len>offset && pos!=offset) {
          struct slice *tail = xmalloc(sizeof(struct slice));
          tail->len = s->node->len-(offset-pos);
          tail->data = s->node->data+(offset-pos);
          s->node->len = offset-pos;
          //pos = offset;
          s = (struct slice_list *)dlist_add_after(
            (struct double_list **)&TT.slices,
            (struct double_list **)&s,
            (char *)tail);
    
          s = (struct slice_list *)dlist_add_before(
            (struct double_list **)&TT.slices,
            (struct double_list **)&s,
            (char *)next);
        } else if (pos==offset) {
          // insert before
          s = (struct slice_list *)dlist_add_before(
            (struct double_list **)&TT.slices,
            (struct double_list **)&s,
            (char *)next);
        } else {
          // insert after
          s = (struct slice_list *)dlist_add_after((struct double_list **)&TT.slices,
          (struct double_list **)&s,
          (char *)next);
        }
      }
      return 0;
    }
    
    // this will not free any memory
    // will only create more slices depending on position
    static int cut_str(size_t offset, size_t len)
    {
      struct slice_list *e, *s = TT.slices;
      size_t end = offset+len;
      size_t epos, spos = 0;
      if (!s) return -1;
    
      //find start and end slices
      for (;;) {
        if (spos<=offset && spos+s->node->len>offset) break;
        spos += s->node->len;
        s = s->next;
    
        if (s == TT.slices) return -1; //error out of bounds
      }
    
      for (e = s, epos = spos; ; ) {
        if (epos<=end && epos+e->node->len>end) break;
        epos += e->node->len;
        e = e->next;
    
        if (e == TT.slices) return -1; //error out of bounds
      }
    
      for (;;) {
        if (spos == offset && ( end >= spos+s->node->len)) {
          //cut full
          spos += s->node->len;
          offset += s->node->len;
          s = dlist_pop(&s);
          if (s == TT.slices) TT.slices = s->next;
    
        } else if (spos < offset && ( end >= spos+s->node->len)) {
          //cut end
          size_t clip = s->node->len - (offset - spos);
          offset = spos+s->node->len;
          spos += s->node->len;
          s->node->len -= clip;
        } else if (spos == offset && s == e) {
          //cut begin
          size_t clip = end - offset;
          s->node->len -= clip;
          s->node->data += clip;
          break;
        } else {
          //cut middle
          struct slice *tail = xmalloc(sizeof(struct slice));
          size_t clip = end-offset;
          tail->len = s->node->len-(offset-spos)-clip;
          tail->data = s->node->data+(offset-spos)+clip;
          s->node->len = offset-spos; //wrong?
          s = (struct slice_list *)dlist_add_after(
            (struct double_list **)&TT.slices,
            (struct double_list **)&s,
            (char *)tail);
          break;
        }
        if (s == e) break;
    
        s = s->next;
      }
    
      return 0;
    }
    
    //find offset position in slices
    static struct slice_list *slice_offset(size_t *start, size_t offset)
    {
      struct slice_list *s = TT.slices;
      size_t spos = 0;
    
      //find start
      for ( ;s ; ) {
        if (spos<=offset && spos+s->node->len>offset) break;
    
        spos += s->node->len;
        s = s->next;
    
        if (s == TT.slices) s = 0; //error out of bounds
      }
      if (s) *start = spos;
      return s;
    }
    
    static size_t text_strchr(size_t offset, char c)
    {
      struct slice_list *s = TT.slices;
      size_t epos, spos = 0;
      int i = 0;
    
      //find start
      if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
    
      i = offset-spos;
      epos = spos+i;
      do {
        for (; i < s->node->len; i++, epos++)
          if (s->node->data[i] == c) return epos;
        s = s->next;
        i = 0;
      } while (s != TT.slices);
    
      return SIZE_MAX;
    }
    
    static size_t text_strrchr(size_t offset, char c)
    {
      struct slice_list *s = TT.slices;
      size_t epos, spos = 0;
      int i = 0;
    
      //find start
      if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
    
      i = offset-spos;
      epos = spos+i;
      do {
        for (; i >= 0; i--, epos--)
          if (s->node->data[i] == c) return epos;
        s = s->prev;
        i = s->node->len-1;
      } while (s != TT.slices->prev); //tail
    
      return SIZE_MAX;
    }
    
    static size_t text_filesize()
    {
      struct slice_list *s = TT.slices;
      size_t pos = 0;
      if (s) do {
    
        pos += s->node->len;
        s = s->next;
    
      } while (s != TT.slices);
    
      return pos;
    }
    
    static int text_count(size_t start, size_t end, char c)
    {
      struct slice_list *s = TT.slices;
      size_t i, count = 0, spos = 0;
      if (!(s = slice_offset(&spos, start))) return 0;
      i = start-spos;
      if (s) do {
        for (; i < s->node->len && spos+i<end; i++)
          if (s->node->data[i] == c) count++;
        if (spos+i>=end) return count;
    
        spos += s->node->len;
        i = 0;
        s = s->next;
    
      } while (s != TT.slices);
    
      return count;
    }
    
    static char text_byte(size_t offset)
    {
      struct slice_list *s = TT.slices;
      size_t spos = 0;
      //find start
      if (!(s = slice_offset(&spos, offset))) return 0;
      return s->node->data[offset-spos];
    }
    
    //utf-8 codepoint -1 if not valid, 0 if out_of_bounds, len if valid
    //copies data to dest if dest is not 0
    static int text_codepoint(char *dest, size_t offset)
    {
      char scratch[8] = {0};
      int state = 0, finished = 0;
    
      for (;!(finished = utf8_dec(text_byte(offset), scratch, &state)); offset++)
        if (!state) return -1;
    
      if (!finished && !state) return -1;
      if (dest) memcpy(dest, scratch, 8);
    
      return strlen(scratch);
    }
    
    static size_t text_sol(size_t offset)
    {
      size_t pos;
      if (!TT.filesize || !offset) return 0;
      else if (TT.filesize <= offset) return TT.filesize-1;
      else if ((pos = text_strrchr(offset-1, '\n')) == SIZE_MAX) return 0;
      else if (pos < offset) return pos+1;
      return offset;
    }
    
    static size_t text_eol(size_t offset)
    {
      if (!TT.filesize) offset = 1;
      else if (TT.filesize <= offset) return TT.filesize-1;
      else if ((offset = text_strchr(offset, '\n')) == SIZE_MAX)
        return TT.filesize-1;
      return offset;
    }
    
    static size_t text_nsol(size_t offset)
    {
      offset = text_eol(offset);
      if (text_byte(offset) == '\n') offset++;
      if (offset >= TT.filesize) offset--;
      return offset;
    }
    
    static size_t text_psol(size_t offset)
    {
      offset = text_sol(offset);
      if (offset) offset--;
      if (offset && text_byte(offset-1) != '\n') offset = text_sol(offset-1);
      return offset;
    }
    
    static size_t text_getline(char *dest, size_t offset, size_t max_len)
    {
      struct slice_list *s = TT.slices;
      size_t end, spos = 0;
      int i, j = 0;
    
      if (dest) *dest = 0;
    
      if (!s) return 0;
      if ((end = text_strchr(offset, '\n')) == SIZE_MAX)
        if ((end = TT.filesize)  > offset+max_len) return 0;
    
      //find start
      if (!(s = slice_offset(&spos, offset))) return 0;
    
      i = offset-spos;
      j = end-offset+1;
      if (dest) do {
        for (; i < s->node->len && j; i++, j--, dest++)
          *dest = s->node->data[i];
        s = s->next;
        i = 0;
      } while (s != TT.slices && j);
    
      if (dest) *dest = 0;
    
      return end-offset;
    }
    
    //copying is needed when file has lot of inserts that are
    //just few char long, but not always. Advanced search should
    //check big slices directly and just copy edge cases.
    //Also this is only line based search multiline
    //and regexec should be done instead.
    static size_t text_strstr(size_t offset, char *str)
    {
      size_t bytes, pos = offset;
      char *s = 0;
      do {
        bytes = text_getline(toybuf, pos, ARRAY_LEN(toybuf));
        if (!bytes) pos++; //empty line
        else if ((s = strstr(toybuf, str))) return pos+(s-toybuf);
        else pos += bytes;
      } while (pos < TT.filesize);
    
      return SIZE_MAX;
    }
    
    static void block_list_free(void *node)
    {
      struct block_list *d = node;
    
      if (d->node->alloc == HEAP) free((void *)d->node->data);
      else if (d->node->alloc == MMAP) munmap((void *)d->node->data, d->node->size);
    
      free(d->node);
      free(d);
    }
    
    static void linelist_unload()
    {
      llist_traverse((void *)TT.slices, llist_free_double);
      llist_traverse((void *)TT.text, block_list_free);
      TT.slices = 0, TT.text = 0;
    }
    
    static int linelist_load(char *filename)
    {
      if (!filename) filename = (char*)*toys.optargs;
    
      if (filename) {
        int fd = open(filename, O_RDONLY);
        long long size;
    
        if (fd == -1 || !(size = fdlength(fd))) {
          insert_str("", 0, 0, 0, STACK);
          TT.filesize = 0;
    
          return 0;
        }
        insert_str(xmmap(0, size, PROT_READ, MAP_SHARED, fd, 0), 0, size,size,MMAP);
        xclose(fd);
        TT.filesize = text_filesize();
      }
    
      return 1;
    }
    
    static void write_file(char *filename)
    {
      struct slice_list *s = TT.slices;
      struct stat st;
      int fd = 0;
      if (!s) return;
    
      if (!filename) filename = (char*)*toys.optargs;
    
      sprintf(toybuf, "%s.swp", filename);
    
      if ( (fd = xopen(toybuf, O_WRONLY | O_CREAT | O_TRUNC)) <0) return;
    
      do {
        xwrite(fd, (void *)s->node->data, s->node->len );
        s = s->next;
      } while (s != TT.slices);
    
      linelist_unload();
    
      xclose(fd);
      if (!stat(filename, &st)) chmod(toybuf, st.st_mode);
      else chmod(toybuf, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
      xrename(toybuf, filename);
      linelist_load(filename);
    }
    
    //jump into valid offset index
    //and valid utf8 codepoint
    static void check_cursor_bounds()
    {
      char buf[8] = {0};
      int len, width = 0;
      if (!TT.filesize) TT.cursor = 0;
    
      for (;;) {
        if (TT.cursor < 1) {
          TT.cursor = 0;
          return;
        } else if (TT.cursor >= TT.filesize-1) {
          TT.cursor = TT.filesize-1;
          return;
        }
        if ((len = text_codepoint(buf, TT.cursor)) < 1) {
          TT.cursor--; //we are not in valid data try jump over
          continue;
        }
        if (utf8_lnw(&width, buf, len) && width) break;
        else TT.cursor--; //combine char jump over
      }
    }
    
    // TT.vi_mov_flag is used for special cases when certain move
    // acts differently depending is there DELETE/YANK or NOP
    // Also commands such as G does not default to count0=1
    // 0x1 = Command needs argument (f,F,r...)
    // 0x2 = Move 1 right on yank/delete/insert (e, $...)
    // 0x4 = yank/delete last line fully
    // 0x10000000 = redraw after cursor needed
    // 0x20000000 = full redraw needed
    // 0x40000000 = count0 not given
    // 0x80000000 = move was reverse
    
    //TODO rewrite the logic, difficulties counting lines
    //and with big files scroll should not rely in knowing
    //absoluteline numbers
    static void adjust_screen_buffer()
    {
      size_t c, s;
      TT.cur_row = 0, TT.scr_row = 0;
      if (!TT.cursor) {
        TT.screen = 0;
        TT.vi_mov_flag = 0x20000000;
        return;
      } else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) {
         //give up, file is big, do full redraw
    
        TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
        TT.vi_mov_flag = 0x20000000;
        return;
      }
    
      s = text_count(0, TT.screen, '\n');
      c = text_count(0, TT.cursor, '\n');
      if (s >= c) {
        TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
        s = c;
        TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
      } else {
        int distance = c-s+1;
        if (distance > (int)TT.screen_height) {
          int n, adj = distance-TT.screen_height;
          TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
          for (;adj; adj--, s++)
            if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen)
              TT.screen = n+1;
        }
      }
    
      TT.scr_row = s;
      TT.cur_row = c;
    
    }
    
    //TODO search yank buffer by register
    //TODO yanks could be separate slices so no need to copy data
    //now only supports default register
    static int vi_yank(char reg, size_t from, int flags)
    {
      size_t start = from, end = TT.cursor;
      char *str;
    
      memset(TT.yank.data, 0, TT.yank.alloc);
      if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
      else TT.cursor = start; //yank moves cursor to left pos always?
    
      if (TT.yank.alloc < end-from) {
        size_t new_bounds = (1+end-from)/1024;
        new_bounds += ((1+end-from)%1024) ? 1 : 0;
        new_bounds *= 1024;
        TT.yank.data = xrealloc(TT.yank.data, new_bounds);
        TT.yank.alloc = new_bounds;
      }
    
      //this is naive copy
      for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start);
    
      *str = 0;
    
      return 1;
    }
    
    static int vi_delete(char reg, size_t from, int flags)
    {
      size_t start = from, end = TT.cursor;
    
      vi_yank(reg, from, flags);
    
      if (TT.vi_mov_flag&0x80000000)
        start = TT.cursor, end = from;
    
      //pre adjust cursor move one right until at next valid rune
      if (TT.vi_mov_flag&2) {
        //TODO
      }
      //do slice cut
      cut_str(start, end-start);
    
      //cursor is at start at after delete
      TT.cursor = start;
      TT.filesize = text_filesize();
      //find line start by strrchr(/n) ++
      //set cur_col with crunch_n_str maybe?
      TT.vi_mov_flag |= 0x30000000;
    
      return 1;
    }
    
    static int vi_change(char reg, size_t to, int flags)
    {
      vi_delete(reg, to, flags);
      TT.vi_mode = 2;
      return 1;
    }
    
    static int cur_left(int count0, int count1, char *unused)
    {
      int count = count0*count1;
      TT.vi_mov_flag |= 0x80000000;
      for (;count && TT.cursor; count--) {
        TT.cursor--;
        if (text_byte(TT.cursor) == '\n') TT.cursor++;
        check_cursor_bounds();
      }
      return 1;
    }
    
    static int cur_right(int count0, int count1, char *unused)
    {
      int count = count0*count1, len, width = 0;
      char buf[8] = {0};
    
      for (;count; count--) {
        len = text_codepoint(buf, TT.cursor);
    
        if (*buf == '\n') break;
        else if (len > 0) TT.cursor += len;
        else TT.cursor++;
    
        for (;TT.cursor < TT.filesize;) {
          if ((len = text_codepoint(buf, TT.cursor)) < 1) {
            TT.cursor++; //we are not in valid data try jump over
            continue;
          }
    
          if (utf8_lnw(&width, buf, len) && width) break;
          else TT.cursor += len;
        }
      }
      check_cursor_bounds();
      return 1;
    }
    
    //TODO column shift
    static int cur_up(int count0, int count1, char *unused)
    {
      int count = count0*count1;
      for (;count--;) TT.cursor = text_psol(TT.cursor);
    
      TT.vi_mov_flag |= 0x80000000;
      check_cursor_bounds();
      return 1;
    }
    
    //TODO column shift
    static int cur_down(int count0, int count1, char *unused)
    {
      int count = count0*count1;
      for (;count--;) TT.cursor = text_nsol(TT.cursor);
      check_cursor_bounds();
      return 1;
    }
    
    static int vi_H(int count0, int count1, char *unused)
    {
      TT.cursor = text_sol(TT.screen);
      return 1;
    }
    
    static int vi_L(int count0, int count1, char *unused)
    {
      TT.cursor = text_sol(TT.screen);
      cur_down(TT.screen_height-1, 1, 0);
      return 1;
    }
    
    static int vi_M(int count0, int count1, char *unused)
    {
      TT.cursor = text_sol(TT.screen);
      cur_down(TT.screen_height/2, 1, 0);
      return 1;
    }
    
    static int search_str(char *s)
    {
      size_t pos = text_strstr(TT.cursor+1, s);
    
      if (TT.last_search != s) {
        free(TT.last_search);
        TT.last_search = xstrdup(s);
      }
    
      if (pos != SIZE_MAX) TT.cursor = pos;
      check_cursor_bounds();
      return 0;
    }
    
    static int vi_yy(char reg, int count0, int count1)
    {
      size_t history = TT.cursor;
      size_t pos = text_sol(TT.cursor); //go left to first char on line
      TT.vi_mov_flag |= 0x4;
    
      for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
    
      vi_yank(reg, pos, 0);
    
      TT.cursor = history;
      return 1;
    }
    
    static int vi_dd(char reg, int count0, int count1)
    {
      size_t pos = text_sol(TT.cursor); //go left to first char on line
      TT.vi_mov_flag |= 0x30000000;
    
      for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
    
      if (pos == TT.cursor && TT.filesize) pos--;
      vi_delete(reg, pos, 0);
      check_cursor_bounds();
      return 1;
    }
    
    static int vi_x(char reg, int count0, int count1)
    {
      size_t from = TT.cursor;
    
      if (text_byte(TT.cursor) == '\n') {
        cur_left(count0-1, 1, 0);
      }
      else {
        cur_right(count0-1, 1, 0);
        if (text_byte(TT.cursor) == '\n') TT.vi_mov_flag |= 2;
        else cur_right(1, 1, 0);
      }
    
      vi_delete(reg, from, 0);
      check_cursor_bounds();
      return 1;
    }
    
    static int vi_movw(int count0, int count1, char *unused)
    {
      int count = count0*count1;
      while (count--) {
        char c = text_byte(TT.cursor);
        do {
          if (TT.cursor > TT.filesize-1) break;
          //if at empty jump to non empty
          if (c == '\n') {
            if (++TT.cursor > TT.filesize-1) break;
            if ((c = text_byte(TT.cursor)) == '\n') break;
            continue;
          } else if (strchr(blank, c)) do {
            if (++TT.cursor > TT.filesize-1) break;
            c = text_byte(TT.cursor);
          } while (strchr(blank, c));
          //if at special jump to non special
          else if (strchr(specials, c)) do {
            if (++TT.cursor > TT.filesize-1) break;
            c = text_byte(TT.cursor);
          } while (strchr(specials, c));
          //else jump to empty or spesial
          else do {
            if (++TT.cursor > TT.filesize-1) break;
            c = text_byte(TT.cursor);
          } while (c && !strchr(blank, c) && !strchr(specials, c));
    
        } while (strchr(blank, c) && c != '\n'); //never stop at empty
      }
      check_cursor_bounds();
      return 1;
    }
    
    static int vi_movb(int count0, int count1, char *unused)
    {
      int count = count0*count1;
      int type = 0;
      char c;
      while (count--) {
        c = text_byte(TT.cursor);
        do {
          if (!TT.cursor) break;
          //if at empty jump to non empty
          if (strchr(blank, c)) do {
            if (!--TT.cursor) break;
            c = text_byte(TT.cursor);
          } while (strchr(blank, c));
          //if at special jump to non special
          else if (strchr(specials, c)) do {
            if (!--TT.cursor) break;
            type = 0;
            c = text_byte(TT.cursor);
          } while (strchr(specials, c));
          //else jump to empty or spesial
          else do {
            if (!--TT.cursor) break;
            type = 1;
            c = text_byte(TT.cursor);
          } while (!strchr(blank, c) && !strchr(specials, c));
    
        } while (strchr(blank, c)); //never stop at empty
      }
      //find first
      for (;TT.cursor; TT.cursor--) {
        c = text_byte(TT.cursor-1);
        if (type && !strchr(blank, c) && !strchr(specials, c)) break;
        else if (!type && !strchr(specials, c)) break;
      }
    
      TT.vi_mov_flag |= 0x80000000;
      check_cursor_bounds();
      return 1;
    }
    
    static int vi_move(int count0, int count1, char *unused)
    {
      int count = count0*count1;
      int type = 0;
      char c;
    
      if (count>1) vi_movw(count-1, 1, unused);
    
      c = text_byte(TT.cursor);
      if (strchr(specials, c)) type = 1;
      TT.cursor++;
      for (;TT.cursor < TT.filesize-1; TT.cursor++) {
        c = text_byte(TT.cursor+1);
        if (!type && (strchr(blank, c) || strchr(specials, c))) break;
        else if (type && !strchr(specials, c)) break;
      }
    
      TT.vi_mov_flag |= 2;
      check_cursor_bounds();
      return 1;
    }
    
    
    static void i_insert(char *str, int len)
    {
      if (!str || !len) return;
    
      insert_str(xstrdup(str), TT.cursor, len, len, HEAP);
      TT.cursor += len;
      TT.filesize = text_filesize();
      TT.vi_mov_flag |= 0x30000000;
    }
    
    static int vi_zero(int count0, int count1, char *unused)
    {
      TT.cursor = text_sol(TT.cursor);
      TT.cur_col = 0;
      TT.vi_mov_flag |= 0x80000000;
      return 1;
    }
    
    static int vi_dollar(int count0, int count1, char *unused)
    {
      size_t new = text_strchr(TT.cursor, '\n');
    
      if (new != TT.cursor) {
        TT.cursor = new - 1;
        TT.vi_mov_flag |= 2;
        check_cursor_bounds();
      }
      return 1;
    }
    
    static void vi_eol()
    {
      TT.cursor = text_strchr(TT.cursor, '\n');
      check_cursor_bounds();
    }
    
    static void ctrl_b()
    {
      int i;
    
      for (i=0; i<TT.screen_height-2; ++i) {
        TT.screen = text_psol(TT.screen);
        // TODO: retain x offset.
        TT.cursor = text_psol(TT.screen);
      }
    }
    
    static void ctrl_f()
    {
      int i;
    
      for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
      // TODO: real vi keeps the x position.
      if (TT.screen > TT.cursor) TT.cursor = TT.screen;
    }
    
    static void ctrl_e()
    {
      TT.screen = text_nsol(TT.screen);
      // TODO: real vi keeps the x position.
      if (TT.screen > TT.cursor) TT.cursor = TT.screen;
    }
    
    static void ctrl_y()
    {
      TT.screen = text_psol(TT.screen);
      // TODO: only if we're on the bottom line
      TT.cursor = text_psol(TT.cursor);
      // TODO: real vi keeps the x position.
    }
    
    //TODO check register where to push from
    static int vi_push(char reg, int count0, int count1)
    {
      //if row changes during push original cursor position is kept
      //vi inconsistancy
      //if yank ends with \n push is linemode else push in place+1
      size_t history = TT.cursor;
      char *start = TT.yank.data;
      char *eol = strchr(start, '\n');
    
      if (start[strlen(start)-1] == '\n') {
        if ((TT.cursor = text_strchr(TT.cursor, '\n')) == SIZE_MAX)
          TT.cursor = TT.filesize;
        else TT.cursor = text_nsol(TT.cursor);
      } else cur_right(1, 1, 0);
    
      i_insert(start, strlen(start));
      if (eol) {
        TT.vi_mov_flag |= 0x10000000;
        TT.cursor = history;
      }
    
      return 1;
    }
    
    static int vi_find_c(int count0, int count1, char *symbol)
    {
    ////  int count = count0*count1;
      size_t pos = text_strchr(TT.cursor, *symbol);
      if (pos != SIZE_MAX) TT.cursor = pos;
      return 1;
    }
    
    static int vi_find_cb(int count0, int count1, char *symbol)
    {
      //do backward search
      size_t pos = text_strrchr(TT.cursor, *symbol);
      if (pos != SIZE_MAX) TT.cursor = pos;
      return 1;
    }
    
    //if count is not spesified should go to last line
    static int vi_go(int count0, int count1, char *symbol)
    {
      size_t prev_cursor = TT.cursor;
      int count = count0*count1-1;
      TT.cursor = 0;
    
      if (TT.vi_mov_flag&0x40000000 && (TT.cursor = TT.filesize) > 0)
        TT.cursor = text_sol(TT.cursor-1);
      else if (count) {
        size_t next = 0;
        for ( ;count && (next = text_strchr(next+1, '\n')) != SIZE_MAX; count--)
          TT.cursor = next;
        TT.cursor++;
      }
    
      check_cursor_bounds();  //adjusts cursor column
      if (prev_cursor > TT.cursor) TT.vi_mov_flag |= 0x80000000;
    
      return 1;
    }
    
    static int vi_o(char reg, int count0, int count1)
    {
      TT.cursor = text_eol(TT.cursor);
      insert_str(xstrdup("\n"), TT.cursor++, 1, 1, HEAP);
      TT.vi_mov_flag |= 0x30000000;
      TT.vi_mode = 2;
      return 1;
    }
    
    static int vi_O(char reg, int count0, int count1)
    {
      TT.cursor = text_psol(TT.cursor);
      return vi_o(reg, count0, count1);
    }
    
    static int vi_D(char reg, int count0, int count1)
    {
      size_t pos = TT.cursor;
      if (!count0) return 1;
      vi_eol();
      vi_delete(reg, pos, 0);
      if (--count0) vi_dd(reg, count0, 1);
    
      check_cursor_bounds();
      return 1;
    }
    
    static int vi_I(char reg, int count0, int count1)
    {
      TT.cursor = text_sol(TT.cursor);
      TT.vi_mode = 2;
      return 1;
    }
    
    static int vi_join(char reg, int count0, int count1)
    {
      size_t next;
      while (count0--) {
        //just strchr(/n) and cut_str(pos, 1);
        if ((next = text_strchr(TT.cursor, '\n')) == SIZE_MAX) break;
        TT.cursor = next+1;
        vi_delete(reg, TT.cursor-1, 0);
      }
      return 1;
    }
    
    static int vi_find_next(char reg, int count0, int count1)
    {
      if (TT.last_search) search_str(TT.last_search);
      return 1;
    }
    
    //NOTES
    //vi-mode cmd syntax is
    //("[REG])[COUNT0]CMD[COUNT1](MOV)
    //where:
    //-------------------------------------------------------------
    //"[REG] is optional buffer where deleted/yanked text goes REG can be
    //  atleast 0-9, a-z or default "
    //[COUNT] is optional multiplier for cmd execution if there is 2 COUNT
    //  operations they are multiplied together
    //CMD is operation to be executed
    //(MOV) is movement operation, some CMD does not require MOV and some
    //  have special cases such as dd, yy, also movements can work without
    //  CMD
    //ex commands can be even more complicated than this....
    //
    struct vi_cmd_param {
      const char* cmd;
      unsigned flags;
      int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS
    };
    struct vi_mov_param {
      const char* mov;
      unsigned flags;
      int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params
    };
    //special cases without MOV and such
    struct vi_special_param {
      const char *cmd;
      int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1
    };
    struct vi_special_param vi_special[] =
    {
      {"D", &vi_D},
      {"I", &vi_I},
      {"J", &vi_join},
      {"O", &vi_O},
      {"n", &vi_find_next},
      {"o", &vi_o},
      {"p", &vi_push},
      {"x", &vi_x},
      {"dd", &vi_dd},
      {"yy", &vi_yy},
    };
    //there is around ~47 vi moves
    //some of them need extra params
    //such as f and '
    struct vi_mov_param vi_movs[] =
    {
      {"0", 0, &vi_zero},
      {"b", 0, &vi_movb},
      {"e", 0, &vi_move},
      {"G", 0, &vi_go},
      {"H", 0, &vi_H},
      {"h", 0, &cur_left},
      {"j", 0, &cur_down},
      {"k", 0, &cur_up},
      {"L", 0, &vi_L},
      {"l", 0, &cur_right},
      {"M", 0, &vi_M},
      {"w", 0, &vi_movw},
      {"$", 0, &vi_dollar},
      {"f", 1, &vi_find_c},
      {"F", 1, &vi_find_cb},
    };
    //change and delete unfortunately behave different depending on move command,
    //such as ce cw are same, but dw and de are not...
    //also dw stops at w position and cw seem to stop at e pos+1...
    //so after movement we need to possibly set up some flags before executing
    //command, and command needs to adjust...
    struct vi_cmd_param vi_cmds[] =
    {
      {"c", 1, &vi_change},
      {"d", 1, &vi_delete},
      {"y", 1, &vi_yank},
    };
    
    static int run_vi_cmd(char *cmd)
    {
      int i = 0, val = 0;
      char *cmd_e;
      int (*vi_cmd)(char, size_t, int) = 0;
      int (*vi_mov)(int, int, char*) = 0;
    
      TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0;
      TT.vi_reg = '"';
    
      if (*cmd == '"') {
        cmd++;
        TT.vi_reg = *cmd; //TODO check validity
        cmd++;
      }
      errno = 0;
      val = strtol(cmd, &cmd_e, 10);
      if (errno || val == 0) val = 1, TT.vi_mov_flag |= 0x40000000;
      else cmd = cmd_e;
      TT.count0 = val;
    
      for (i = 0; i < ARRAY_LEN(vi_special); i++) {
        if (strstr(cmd, vi_special[i].cmd)) {
          return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1);
        }
      }
    
      for (i = 0; i < ARRAY_LEN(vi_cmds); i++) {
        if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) {
          vi_cmd = vi_cmds[i].vi_cmd;
          cmd += strlen(vi_cmds[i].cmd);
          break;
        }
      }
      errno = 0;
      val = strtol(cmd, &cmd_e, 10);
      if (errno || val == 0) val = 1;
      else cmd = cmd_e;
      TT.count1 = val;
    
      for (i = 0; i < ARRAY_LEN(vi_movs); i++) {
        if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) {
          vi_mov = vi_movs[i].vi_mov;
          TT.vi_mov_flag |= vi_movs[i].flags;
          cmd++;
          if (TT.vi_mov_flag&1 && !(*cmd)) return 0;
          break;
        }
      }
      if (vi_mov) {
        int prev_cursor = TT.cursor;
        if (vi_mov(TT.count0, TT.count1, cmd)) {
          if (vi_cmd) return (vi_cmd(TT.vi_reg, prev_cursor, TT.vi_mov_flag));
          else return 1;
        } else return 0; //return some error
      }
      return 0;
    }
    
    
    static int run_ex_cmd(char *cmd)
    {
      if (cmd[0] == '/') {
        search_str(&cmd[1]);
      } else if (cmd[0] == '?') {
        // TODO: backwards search.
      } else if (cmd[0] == ':') {
        if (!strcmp(&cmd[1], "q") || !strcmp(&cmd[1], "q!")) {
          // TODO: if no !, check whether file modified.
          //exit_application;
          return -1;
        }
        else if (strstr(&cmd[1], "wq")) {
          write_file(0);
          return -1;
        }
        else if (strstr(&cmd[1], "w")) {
          write_file(0);
          return 1;
        }
        else if (strstr(&cmd[1], "set list")) {
          TT.list = 1;
          TT.vi_mov_flag |= 0x30000000;
          return 1;
        }
        else if (strstr(&cmd[1], "set nolist")) {
          TT.list = 0;
          TT.vi_mov_flag |= 0x30000000;
          return 1;
        }
      }
      return 0;
    
    }
    
    static int vi_crunch(FILE *out, int cols, int wc)
    {
      int ret = 0;
      if (wc < 32 && TT.list) {
        xputsn("\e[1m");
        ret = crunch_escape(out,cols,wc);
        xputsn("\e[m");
      } else if (wc == 0x09) {
        if (out) {
          int i = TT.tabstop;
          for (;i--;) fputs(" ", out);
        }
        ret = TT.tabstop;
      } else if (wc == '\n') return 0;
      return ret;
    }
    
    //crunch_str with n bytes restriction for printing substrings or
    //non null terminated strings
    static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
      int (*escout)(FILE *out, int cols, int wc))
    {
      int columns = 0, col, bytes;
      char *start, *end;
      unsigned wc;
    
      for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
        if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
          if (!escmore || wc>255 || !strchr(escmore, wc)) {
            if (width-columns<col) break;
            if (out) fwrite(end, bytes, 1, out);
    
            continue;
          }
        }
    
        if (bytes<1) {
          bytes = 1;
          wc = *end;
        }
        col = width-columns;
        if (col<1) break;
        if (escout) {
          if ((col = escout(out, col, wc))<0) break;
        } else if (out) fwrite(end, 1, bytes, out);
      }
      *str = end;
    
      return columns;
    }
    
    static void draw_page()
    {
      unsigned y = 0;
      int x = 0;
    
      char *line = 0, *end = 0;
      int bytes = 0;
    
      //screen coordinates for cursor
      int cy_scr = 0, cx_scr = 0;
    
      //variables used only for cursor handling
      int aw = 0, iw = 0, clip = 0, margin = 8;
    
      int scroll = 0, redraw = 0;
    
      int SSOL, SOL;
    
    
      adjust_screen_buffer();
      //redraw = 3; //force full redraw
      redraw = (TT.vi_mov_flag & 0x30000000)>>28;
    
      scroll = TT.drawn_row-TT.scr_row;
      if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
      else if (abs(scroll)>TT.screen_height/2) redraw = 3;
    
      xputsn("\e[H"); // jump to top left
      if (redraw&2) xputsn("\e[2J\e[H");   //clear screen
      else if (scroll>0) printf("\e[%dL", scroll);  //scroll up
      else if (scroll<0) printf("\e[%dM", -scroll); //scroll down
    
      SOL = text_sol(TT.cursor);
      bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
      line = toybuf;
    
      for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL);
    
      cy_scr = y;
    
      //draw cursor row
      /////////////////////////////////////////////////////////////
      //for long lines line starts to scroll when cursor hits margin
      bytes = TT.cursor-SOL; // TT.cur_col;
      end = line;
    
    
      printf("\e[%u;0H\e[2K", y+1);
      //find cursor position
      aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
    
      //if we need to render text that is not inserted to buffer yet
      if (TT.vi_mode == 2 && TT.il->len) {
        char* iend = TT.il->data; //input end
        x = 0;
        //find insert end position
        iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
        clip = (aw+iw) - TT.screen_width+margin;
    
        //if clipped area is bigger than text before insert
        if (clip > aw) {
          clip -= aw;
          iend = TT.il->data;
    
          iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
          x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
        } else {
          iend = TT.il->data;
          end = line;
    
          //if clipped area is substring from cursor row start
          aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
          x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
          x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
        }
      }
      //when not inserting but still need to keep cursor inside screen
      //margin area
      else if ( aw+margin > TT.screen_width) {
        clip = aw-TT.screen_width+margin;
        end = line;
        aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
        x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
      }
      else {
        end = line;
        x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
      }
      cx_scr = x;
      cy_scr = y;
      x += crunch_str(&end, TT.screen_width-x,  stdout, "\t\n", vi_crunch);
    
      //start drawing all other rows that needs update
      ///////////////////////////////////////////////////////////////////
      y = 0, SSOL = TT.screen, line = toybuf;
      bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
    
      //if we moved around in long line might need to redraw everything
      if (clip != TT.drawn_col) redraw = 3;
    
      for (; y < TT.screen_height; y++ ) {
        int draw_line = 0;
        if (SSOL == SOL) {
          line = toybuf;
          SSOL += bytes+1;
          bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
          continue;
        } else if (redraw) draw_line++;
        else if (scroll<0 && TT.screen_height-y-1<-scroll)
          scroll++, draw_line++;
        else if (scroll>0) scroll--, draw_line++;
    
        printf("\e[%u;0H", y+1);
        if (draw_line) {
          printf("\e[2K");
          if (line && strlen(line)) {
            aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
            crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
            if ( *line ) printf("@");
          } else printf("\e[2m~\e[m");
        }
        if (SSOL+bytes < TT.filesize)  {
          line = toybuf;
          SSOL += bytes+1;
          bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
       } else line = 0;
      }
    
      TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
    
      // Finished updating visual area, show status line.
      printf("\e[%u;0H\e[2K", TT.screen_height+1);
      if (TT.vi_mode == 2) printf("\e[1m-- INSERT --\e[m");
      if (!TT.vi_mode) {
        cx_scr = printf("%s", TT.il->data);
        cy_scr = TT.screen_height;
        *toybuf = 0;
      } else {
        // TODO: the row,col display doesn't show the cursor column
        // TODO: real vi shows the percentage by lines, not bytes
        sprintf(toybuf, "%zu/%zuC  %zu%%  %d,%d", TT.cursor, TT.filesize,
          (100*TT.cursor)/(TT.filesize ? : 1), TT.cur_row+1, TT.cur_col+1);
        if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
      }
      printf("\e[%u;%uH%s\e[%u;%uH", TT.screen_height+1,
        (int) (1+TT.screen_width-strlen(toybuf)),
        toybuf, cy_scr+1, cx_scr+1);
      xflush(1);
    }
    
    void vi_main(void)
    {
      char stdout_buf[BUFSIZ];
      char keybuf[16] = {0};
      char vi_buf[16] = {0};
      char utf8_code[8] = {0};
      int utf8_dec_p = 0, vi_buf_pos = 0;
      FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
    
      TT.il = xzalloc(sizeof(struct str_line));
      TT.il->data = xzalloc(80);
      TT.yank.data = xzalloc(128);
    
      TT.il->alloc = 80, TT.yank.alloc = 128;
    
      linelist_load(0);
    
      TT.vi_mov_flag = 0x20000000;
      TT.vi_mode = 1, TT.tabstop = 8;
    
      TT.screen_width = 80, TT.screen_height = 24;
      terminal_size(&TT.screen_width, &TT.screen_height);
      TT.screen_height -= 1;
    
      // Avoid flicker.
      setbuf(stdout, stdout_buf);
    
      xsignal(SIGWINCH, generic_signal);
      set_terminal(0, 1, 0, 0);
      //writes stdout into different xterm buffer so when we exit
      //we dont get scroll log full of junk
      xputsn("\e[?1049h");
    
      for (;;) {
        int key = 0;
    
        draw_page();
        if (script) {
          key = fgetc(script);
          if (key == EOF) {
            fclose(script);
            script = 0;
            key = scan_key(keybuf, -1);
          }
        } else key = scan_key(keybuf, -1);
    
        if (key == -1) goto cleanup_vi;
        else if (key == -3) {
          toys.signal = 0;
          terminal_size(&TT.screen_width, &TT.screen_height);
          TT.screen_height -= 1; //TODO this is hack fix visual alignment
          continue;
        }
    
        // TODO: support cursor keys in ex mode too.
        if (TT.vi_mode && key>=256) {
          key -= 256;
          if (key==KEY_UP) cur_up(1, 1, 0);
          else if (key==KEY_DOWN) cur_down(1, 1, 0);
          else if (key==KEY_LEFT) cur_left(1, 1, 0);
          else if (key==KEY_RIGHT) cur_right(1, 1, 0);
          else if (key==KEY_HOME) vi_zero(1, 1, 0);
          else if (key==KEY_END) vi_dollar(1, 1, 0);
          else if (key==KEY_PGDN) ctrl_f();
          else if (key==KEY_PGUP) ctrl_b();
          continue;
        }
    
        if (TT.vi_mode == 1) { //NORMAL
          switch (key) {
            case '/':
            case '?':
            case ':':
              TT.vi_mode = 0;
              TT.il->data[0]=key;
              TT.il->len++;
              break;
            case 'A':
              vi_eol();
              TT.vi_mode = 2;
              break;
            case 'a':
              cur_right(1, 1, 0);
              // FALLTHROUGH
            case 'i':
              TT.vi_mode = 2;
              break;
            case 'B'-'@':
              ctrl_b();
              break;
            case 'E'-'@':
              ctrl_e();
              break;
            case 'F'-'@':
              ctrl_f();
              break;
            case 'Y'-'@':
              ctrl_y();
              break;
            case 27:
              vi_buf[0] = 0;
              vi_buf_pos = 0;
              break;
            default:
              if (key > 0x20 && key < 0x7B) {
                vi_buf[vi_buf_pos] = key;//TODO handle input better
                vi_buf_pos++;
                if (run_vi_cmd(vi_buf)) {
                  memset(vi_buf, 0, 16);
                  vi_buf_pos = 0;
                }
                else if (vi_buf_pos == 16) {
                  vi_buf_pos = 0;
                  memset(vi_buf, 0, 16);
                }
    
              }
    
              break;
          }
        } else if (TT.vi_mode == 0) { //EX MODE
          switch (key) {
            case 0x7F:
            case 0x08:
              if (TT.il->len > 1) {
                TT.il->data[--TT.il->len] = 0;
                break;
              }
              // FALLTHROUGH
            case 27:
              TT.vi_mode = 1;
              TT.il->len = 0;
              memset(TT.il->data, 0, TT.il->alloc);
              break;
            case 0x0A:
            case 0x0D:
              if (run_ex_cmd(TT.il->data) == -1)
                goto cleanup_vi;
              TT.vi_mode = 1;
              TT.il->len = 0;
              memset(TT.il->data, 0, TT.il->alloc);
              break;
            default: //add chars to ex command until ENTER
              if (key >= 0x20 && key < 0x7F) { //might be utf?
                if (TT.il->len == TT.il->alloc) {
                  TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
                  TT.il->alloc *= 2;
                }
                TT.il->data[TT.il->len] = key;
                TT.il->len++;
              }
              break;
          }
        } else if (TT.vi_mode == 2) {//INSERT MODE
          switch (key) {
            case 27:
              i_insert(TT.il->data, TT.il->len);
              cur_left(1, 1, 0);
              TT.vi_mode = 1;
              TT.il->len = 0;
              memset(TT.il->data, 0, TT.il->alloc);
              break;
            case 0x7F:
            case 0x08:
              if (TT.il->len) {
                char *last = utf8_last(TT.il->data, TT.il->len);
                int shrink = strlen(last);
                memset(last, 0, shrink);
                TT.il->len -= shrink;
              }
              break;
            case 0x0A:
            case 0x0D:
              //insert newline
              //
              TT.il->data[TT.il->len++] = '\n';
              i_insert(TT.il->data, TT.il->len);
              TT.il->len = 0;
              memset(TT.il->data, 0, TT.il->alloc);
              break;
            default:
              if ((key >= 0x20 || key == 0x09) &&
                  utf8_dec(key, utf8_code, &utf8_dec_p)) {
    
                if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) {
                  TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
                  TT.il->alloc *= 2;
                }
                strcpy(TT.il->data+TT.il->len, utf8_code);
                TT.il->len += utf8_dec_p;
                utf8_dec_p = 0;
                *utf8_code = 0;
    
              }
              break;
          }
        }
      }
    cleanup_vi:
      linelist_unload();
      free(TT.il->data), free(TT.il), free(TT.yank.data);
      tty_reset();
      xputsn("\e[?1049l");
    }