Hi

On Wed, Jan 15, 2020 at 12:23 AM enh via Toybox
<toybox@lists.landley.net> wrote:
>
> ^D is the opposite of ^U in vi (the ^D/^U pair is the half-screen
> version of ^F/^B). ^C is unbound in vi. It's pretty surprising for these
> to cause toybox vi to exit, and it's annoying as long as toybox vi
> unconditionally exits rather than checks whether there are unsaved
> modifications!
>
> (I'm tempted to implement ^D/^U and ^F/^B, but I don't want to make
> Jarno's rebase of his in-progress changes any harder.)

Dont worry, if you feel like you need some feature implement it. I can
always rebase. I send small patch of changes I did few weeks ago,
rebased on top of this.

I had idea of changing dlist of heap allocated lines into memory
blocks that can be mmap:ed file and ordered list of offsets that
describe data in its current state.

  // mem_block contains RO data that is either original file as mmap
  // or heap allocated inserted data. mem_blocks are not deleted or modified
  // but instead slice_list is modified on cut operations, and insert
  // allocates new memblock into list and adds slices referencing it.
  //
  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;

So when you open file you have one mem_block and one slice, and when
you cut line at middle
you still have one block but 2 slices with hole between them. and
insert adds new memblock, and adds/modifies 1-3 slices depending on
position.

This should make opening big files possible, implementing undo history
will be simpler, few operations will be much simpler, few harder...
but unfortunately big portion of movements needs to be rewritten.

I had 11hour flight back to europe on Monday with chromebook sitting
on my lap, and I got about half of the implementation ready. But I
dont want to send patch before it is usable working state, since this
thing might already have users :)

Do you think this is better design than previous one? if you have time
or interest to take a look I attached my vi.c from my working tree

br
Jarno




> ---
>  toys/pending/vi.c | 10 +++-------
>  1 file changed, 3 insertions(+), 7 deletions(-)
> _______________________________________________
> Toybox mailing list
> Toybox@lists.landley.net
> http://lists.landley.net/listinfo.cgi/toybox-landley.net
/* vi.c - You can't spell "evil" without "vi".
 *
 * Copyright 2015 Rob Landley <r...@landley.net>
 * Copyright 2019 Jarno Mäkipää <jmaki...@gmail.com>
 *
 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html

USE_VI(NEWTOY(vi, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))

config VI
  bool "vi"
  default n
  help
    usage: vi FILE
    Visual text editor. Predates the existence of standardized cursor keys,
    so the controls are weird and historical.
*/

#define FOR_vi
#include "toys.h"

GLOBALS(
    int cur_col;
    int cur_row;
    int scr_row;
    int drawn_row;
    int drawn_col;
    unsigned screen_height;
    unsigned screen_width;
    int vi_mode;
    int count0;
    int count1;
    int vi_mov_flag;
    int modified;
    char vi_reg;
    char *last_search;
    int tabstop;
    int list;
    struct str_line {
      int alloc;
      int len;
      char *data;
    } *il;
    size_t screen; //offset in slices must be higher than cursor
    size_t cursor; //offset in slices
    //yank buffer
    struct yank_buf {
      char reg;
      int alloc;
      char* data;
    } yank;

  // 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;

)


// 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


static void draw_page();

//utf8 support
static int utf8_lnw(int* width, char* str, int bytes);
static int utf8_dec(char key, char *utf8_scratch, int *sta_p);
static int utf8_width(char *str, int bytes);
static char* utf8_last(char* str, int size);


static int cur_left(int count0, int count1, char* unused);
static int cur_right(int count0, int count1, char* unused);
static int cur_up(int count0, int count1, char* unused);
static int cur_down(int count0, int count1, char* unused);
static void check_cursor_bounds();
static void adjust_screen_buffer();
static int search_str(char *s);

//from TT.cursor to
static int vi_yank(char reg, size_t from, int flags);
static int vi_delete(char reg, size_t from, int flags);


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
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
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;
}

size_t text_strchr(size_t offset, char c)
{
  struct slice_list *s = TT.slices;
  size_t epos, spos = 0;
  int i = 0;

  if (!s) return -1;

  //find start
  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
  }

  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 -1;

}

size_t text_strrchr(size_t offset, char c)
{
  struct slice_list *s = TT.slices;
  size_t epos, spos = 0;
  int i = 0;

  if (!s) return -1;

  //find start
  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
  }

  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;
  } while(s != TT.slices->prev); //tail

  return -1;

}

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;
}

char text_byte(size_t offset)
{
  struct slice_list *s = TT.slices;
  size_t spos = 0;
  //find start
  for (;;) {
    if (spos<=offset && spos+s->node->len>=offset) break;

    spos += s->node->len;
    s = s->next;

    if (s == TT.slices) return 0; //error out of bounds
  }
  return s->node->data[offset-spos];
}

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;

  *dest = 0;

  if (!s) return 0;
  if ((end = text_strchr(offset, '\n')) < 0) 
    if ((end = text_filesize())  > offset+max_len) return 0;

  //find start
  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
  }

  i = offset-spos;
  j = end - offset;
  if (dest) do {
    for (; i < s->node->len && j; i++, j--)
      *(dest++) = s->node->data[i];
    s = s->next;
    i = 0;
  } while(s != TT.slices && j);

  *dest = 0;
  return end - offset;
}


void linelist_unload()
{
//  void* list = 0;
//  for (;TT.text->down; TT.text = TT.text->down);
//  list = (void*)TT.text;
//  TT.text = TT.screen = TT.c_r = 0;
//  llist_traverse(list, linelist_free);
}

void write_file(char *filename)
{
//  struct linelist *lst = TT.text;
//  FILE *fp = 0;
//  if (!filename) filename = (char*)*toys.optargs;
//  if (!(fp = fopen(filename, "w")) ) return;
//
//  for (;lst; lst = lst->down)
//    fprintf(fp, "%s\n", lst->line->data);
//
//  fclose(fp);
}

int linelist_load(char *filename)
{
  if (!filename) filename = (char*)*toys.optargs;

  if (filename) {
    int fd;
    struct stat sb;
    size_t len;
    char *data;
    if ( (fd = open(filename, O_RDONLY)) <0) return 0;
    if (fstat(fd, &sb) < 0) return 0;

    len = sb.st_size;
    data = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) return 0;
    insert_str(data, 0, len, len, MMAP);
  }

  return 1;
}

//TODO
int vi_yy(char reg, int count0, int count1)
{
  size_t history = TT.cursor;
  size_t pos = 0;
  TT.vi_mov_flag |= 0x4;

  //go left to first char on line
  pos = TT.cursor;

  if (count0>1) cur_down(count0-1, 1, 0);

  vi_yank(reg, pos, 0);

  TT.cursor = history;
  return 1;
}

int vi_dd(char reg, int count0, int count1)
{
  size_t pos = 0;
  TT.vi_mov_flag |= 0x4;

  //go left to first char on line
  TT.cursor = text_strrchr(TT.cursor, '\n')+1;
  pos = TT.cursor;

  if (count0>1) cur_down(count0-1, 1, 0);

  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;
}

//TODO
int vi_movw(int count0, int count1, char* unused)
{
  int count = count0*count1;
  //const char *empties = " \t\n\r";
  //const char *specials = ",.=-+*/(){}<>[]";
  while (count--) {
    //if at empty jump to non empty
    //if at special jump to non special
    //else jump to empty or spesial
  }
  check_cursor_bounds();
  return 1;
}

static int vi_movb(int count0, int count1, char* unused)
{
  int count = count0*count1;
  //const char *empties = " \t\n\r";
  //const char *specials = ",.=-+*/(){}<>[]";
  while (count--) {
    //if at empty jump to non empty
    //if at special jump to non special
    //else jump to empty or spesial
  }
  TT.vi_mov_flag |= 0x80000000;
  check_cursor_bounds();
  return 1;
}

//TODO
static int vi_move(int count0, int count1, char *unused)
{
  int count = count0*count1;

  while (count--) {
//TODO  if (count>1 || slice_char(TT.cursor) <= ' ')
//TODO    vi_movw(count, 1, 0); //find next word;
//TODO  while (slice_char(TT.cursor+1)>' ') TT.cursor++;
  }
  TT.vi_mov_flag |= 2;
  check_cursor_bounds();
  return 1;
}


static void i_insert(char* str, int len)
{
  char *s = xstrdup(str);

  insert_str(s, TT.cursor, strlen(s), strlen(s), HEAP);
  TT.vi_mov_flag |= 0x30000000;
}

//new line at split pos;
void i_split()
{
//  int alloc = 0, len = 0, idx = 0;
//  struct str_line *l = xmalloc(sizeof(struct str_line));
//  alloc = TT.c_r->line->alloc;
//
//  if (TT.cur_col) len = TT.c_r->line->len-TT.cur_col-1;
//  else len = TT.c_r->line->len;
//  if (len < 0) len = 0;
//
//  l->data = xzalloc(alloc);
//  l->alloc = alloc;
//  l->len = len;
//  idx = TT.c_r->line->len - len;
//
//  strncpy(l->data, &TT.c_r->line->data[idx], len);
//  memset(&l->data[len], 0, alloc-len);
//
//  TT.c_r->line->len -= len;
//  if (TT.c_r->line->len <= 0) TT.c_r->line->len = 0;
//
//  len = TT.c_r->line->len;
//
//  memset(&TT.c_r->line->data[len], 0, alloc-len);
//  TT.c_r = (struct linelist*)dlist_insert((struct double_list**)&TT.c_r, (char*)l);
//  TT.c_r->line = l;
//  TT.cur_col = 0;
}

static int vi_zero(int count0, int count1, char *unused)
{
  //backward find /n
  TT.cursor = text_strrchr(TT.cursor, '\n')+1;
  TT.cur_col = 0;
  TT.vi_mov_flag |= 0x80000000;
  return 1;
}

static int vi_eol(int count0, int count1, char *unused)
{
  //forward find /n
  TT.cursor = text_strchr(TT.cursor, '\n');
  TT.vi_mov_flag |= 2;
  check_cursor_bounds();
  return 1;
}

//TODO check register where to push from
static int vi_push(char reg, int count0, int count1)
{
  char *start = TT.yank.data;
  char *eol = strchr(start, '\n');

  i_insert(start, strlen(start));
  if (eol) {
    TT.vi_mov_flag |= 0x10000000;
  }

  //if row changes during push original cursor position is kept
  //vi inconsistancy
  //if (TT.c_r != cursor) TT.c_r = cursor, TT.cur_col = col;

  return 1;
}

//TODO
static int vi_find_c(int count0, int count1, char *symbol)
{
////  int count = count0*count1;
//  if (TT.c_r && TT.c_r->line->len) {
//    while (count--) {
//        char* pos = strstr(&TT.c_r->line->data[TT.cur_col], symbol);
//        if (pos) {
//          TT.cur_col = pos-TT.c_r->line->data;
//          return 1;
//        }
//    }
//  }
  return 0;
}

static int vi_find_cb(int count0, int count1, char *symbol)
{
  //do backward search
  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;

//  if (TT.vi_mov_flag&0x40000000) for (;TT.c_r && TT.c_r->down; TT.c_r = TT.c_r->down);
//  else for (;TT.c_r && TT.c_r->down && --count0; TT.c_r = TT.c_r->down);

  check_cursor_bounds();  //adjusts cursor column
  if (prev_cursor>TT.cursor) TT.vi_mov_flag |= 0x80000000;

  return 1;
}

//TODO rewrite using slices
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) {
    //int len, width;
    //char *s = end->line->data;
    //len = utf8_lnw(&width, s+col_e, strlen(s+col_e));
    //for (;;) {
      //col_e += len;
      //len = utf8_lnw(&width, s+col_e, strlen(s+col_e));
      //if (len<1 || width || !(*(s+col_e))) break;
    //}
  }
  //find if range contains atleast single /n
  //if so set TT.vi_mov_flag |= 0x10000000;

  //do slice cut
  cut_str(start, end-start);

  //cursor is at start at after delete
  TT.cursor = start;
  //find line start by strrchr(/n) ++
  //set cur_col with crunch_n_str maybe?

  return 1;
}

//TODO rewrite using slices
static int vi_D(char reg, int count0, int count1)
{
  int prev_col = TT.cur_col;
  size_t pos = TT.cursor;
  if (!count0) return 1;
  vi_eol(1, 1, 0);
  vi_delete(reg, pos, 0);
  count0--;
  if (count0) {
    //TODO move cursor down
    vi_dd(reg, count0, 1);
  }
  check_cursor_bounds();
  return 1;
}

//TODO rewrite using slices
static int vi_join(char reg, int count0, int count1)
{
  while (count0--) {
	//just strchr(/n) and cut_str(pos, 1);
  }
  return 1;
}

static int vi_find_next(char reg, int count0, int count1)
{
  if (TT.last_search) search_str(TT.last_search);
  return 1;
}

static int vi_change(char reg, size_t to, int flags)
{
  vi_delete(reg, to, flags);
  TT.vi_mode = 2;
  return 1;
}

//TODO search yank buffer by register
//TODO rewrite using slices
//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;

  memset(TT.yank.data, 0, TT.yank.alloc);
  if (TT.vi_mov_flag&0x80000000)
    start = TT.cursor, end = from;


  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;
  }
  //find start slice
  //find offset inside start slice
  //while start<end copy slice into yank.data
  // if end-start> copy full jump next slice
  // else copy remaining and break

  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[] =
{
  {"dd", &vi_dd},
  {"yy", &vi_yy},
  {"D", &vi_D},
  {"J", &vi_join},
  {"n", &vi_find_next},
  {"x", &vi_x},
  {"p", &vi_push}
};
//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, &cur_left},
  {"j", 0, &cur_down},
  {"k", 0, &cur_up},
  {"l", 0, &cur_right},
  {"w", 0, &vi_movw},
  {"$", 0, &vi_eol},
  {"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},
};

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;
}

//TODO rewrite with slices
static int search_str(char *s)
{
  //TODO may need to write
  //size_t slice_strstr(size_t at, s)
  //size_t slice_strrstr(size_t at, s) //reverse

  if (TT.last_search != s) {
    free(TT.last_search);
    TT.last_search = xstrdup(s);
  }

  TT.cursor = 0; //TODO
  TT.cur_col = 0; //TODO
  check_cursor_bounds();
  return 0;
}

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;

}

void vi_main(void)
{
  char keybuf[16] = {0};
  char vi_buf[16] = {0};
  char utf8_code[8] = {0};
  int utf8_dec_p = 0, vi_buf_pos = 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.screen = TT.cursor = 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;

  set_terminal(0, 1, 0, 0);
  //writes stdout into different xterm buffer so when we exit
  //we dont get scroll log full of junk
  tty_esc("?1049h");
  tty_esc("H");
  xflush(1);


  draw_page();
  while(1) {
    int key = scan_key(keybuf, -1);

    terminal_size(&TT.screen_width, &TT.screen_height);
    TT.screen_height -= 1; //TODO this is hack fix visual alignment

    // 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);
      draw_page();
      continue;
    }

    switch (key) {
      case -1:
      case 3:
      case 4:
        goto cleanup_vi;
    }
    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(1, 1, 0);
          // FALLTHROUGH
        case 'a':
          //TODO cur_right();
          // FALLTHROUGH
        case 'i':
          TT.vi_mode = 2;
          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 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);
          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 0x0D:
          //insert newline
          //
          i_insert(TT.il->data, TT.il->len);
          TT.il->len = 0;
          memset(TT.il->data, 0, TT.il->alloc);
          i_split();
          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;
      }
    }

    draw_page();

  }
cleanup_vi:
  linelist_unload();
  free(TT.il->data), free(TT.il), free(TT.yank.data);
  tty_reset();
  tty_esc("?1049l");
}

int vi_crunch(FILE* out, int cols, int wc)
{
  int ret = 0;
  if (wc < 32 && TT.list) {
    tty_esc("1m");
    ret = crunch_escape(out,cols,wc);
    tty_esc("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
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;

  for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
    wchar_t wc;

    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;

  struct slice_list *s = TT.slices;
  char *str;
  tty_esc("2J");
  tty_esc("H");   //clear screen

  adjust_screen_buffer();
  //scr_buf = TT.screen;
  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;

  tty_jump(0, 0);
  if (redraw&2) tty_esc("2J"), tty_esc("H");   //clear screen
  else if (scroll>0) printf("\033[%dL", scroll);  //scroll up
  else if (scroll<0) printf("\033[%dM", -scroll); //scroll down

  int SOL = text_strrchr(TT.cursor, '\n')+1;
  bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
  line = toybuf; 

  //crunch_nstr(&line, 1024, bytes, stdout, "\t\n", vi_crunch);
  //draw cursor row
  /////////////////////////////////////////////////////////////
  //for long lines line starts to scroll when cursor hits margin
  line = toybuf; //TODO scr_buf->line->data;
  //bytes = 0; // TT.cur_col;
  end = line;


  tty_jump(0, y);
  tty_esc("2K");
  //find cursor position
  aw = crunch_nstr(&end, 1024, 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, 1024, 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", vi_crunch);

  //start drawing all other rows that needs update
  ///////////////////////////////////////////////////////////////////
  y = 0;
  //TT.screen = TT.cursor; //DEBUG
  redraw = 1; //DEBUG
  int SSOL = text_strrchr(TT.screen, '\n')+1;
  memset(toybuf, 0, ARRAY_LEN(toybuf));
  bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
  line = 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++;

    tty_jump(0, y);
    if (draw_line) {

      tty_esc("2K");
      if (line) {
        if (draw_line && 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 if (draw_line) printf("~");
    }
    if (SSOL+bytes < text_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
PRINT_TAIL:
  tty_jump(0, TT.screen_height);
  tty_esc("2K");
  if (TT.vi_mode == 2) printf("\x1b[1m-- INSERT --\x1b[m");
  if (!TT.vi_mode) printf("\x1b[1m%s \x1b[m",TT.il->data);

  tty_jump(TT.screen_width-12, TT.screen_height);
  printf("%d,%d", TT.cur_row+1, TT.cur_col+1);
  if (TT.cur_col != cx_scr) printf("-%d", cx_scr+1);

  if (TT.vi_mode) tty_jump(cx_scr, cy_scr);

  xflush(1);

}
//TODO jump into valid offset index
//and valid utf8 codepoint
static void check_cursor_bounds()
{
}

//TODO
//update cursor and screen line numbers
static void adjust_screen_buffer()
{
  if (!TT.cursor) TT.screen = 0;
  else if (TT.screen > TT.cursor) {
    if (text_byte(TT.cursor) == '\n') TT.screen = TT.cursor;
    else TT.screen = text_strrchr(TT.cursor, '\n')+1;
  } else {
    size_t pos = TT.screen;
    int n = 0;
    for (;pos < TT.cursor; n++) {
      pos = text_strchr(pos+1, '\n');
      if (n > TT.screen_height*2) { 
        if (text_byte(TT.cursor) == '\n') TT.screen = TT.cursor;
        else TT.screen = text_strrchr(TT.cursor, '\n')+1;
        return;
      }
    }
    for (;n > TT.screen_height; n--) 
      TT.screen = text_strchr(TT.screen, '\n');

    TT.screen++;
  }

  //TODO
  TT.cur_row = 0, TT.scr_row = 0;

}

//get utf8 length and width at same time
static int utf8_lnw(int* width, char* s, int bytes)
{
  wchar_t wc;
  int length;

  *width = 0;
  if (*s == '\t') {
    *width = TT.tabstop;
    return 1;
  }
  length = utf8towc(&wc, s, bytes);
  if (length < 1) return 0;
  *width = wcwidth(wc);
  return length;
}

//try to estimate width of next "glyph" in terminal buffer
//combining chars 0x300-0x36F shall be zero width
static int utf8_width(char *s, int bytes)
{
  wchar_t wc;
  int length;

  if (*s == '\t') return TT.tabstop;
  length = utf8towc(&wc, s, bytes);
  if (length < 1) return -1;
  return wcwidth(wc);
}

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;
  int len = 0;
  int width = 0;
  while (pos >= 0) {
    len = utf8_lnw(&width, end, size-pos);
    if (len && width) return end;
    end--; pos--;
  }
  return 0;
}

static int cur_left(int count0, int count1, char* unused)
{
  int count = count0*count1;
  TT.vi_mov_flag |= 0x80000000;
  for (;count--;) {
    if (!TT.cursor) return 1;

    TT.cursor--;
    check_cursor_bounds();
  }
  return 1;
}

static int cur_right(int count0, int count1, char* unused)
{
  int count = count0*count1;
  for (;count--;) {
    //TODO jump next valid utf8 right
    TT.cursor++;
    //if (TT.cursor == filesize) return 1;
    //if (TT.cursor == '/n') break;
  }
  return 1;
}

//TODO
static int cur_up(int count0, int count1, char* unused)
{
  int count = count0*count1;
  for (;count--;) {
    if (TT.cursor) TT.cursor = text_strrchr(TT.cursor-1, '\n');
    if (TT.cursor) TT.cursor = text_strrchr(TT.cursor-1, '\n');
  }
  TT.cursor++;
  //TODO + column shift
  TT.vi_mov_flag |= 0x80000000;
  check_cursor_bounds();
  return 1;
}

//TODO
static int cur_down(int count0, int count1, char* unused)
{
  int count = count0*count1;
  for (;count--;)
    TT.cursor = text_strchr(TT.cursor, '\n')+1;

  //TODO + column shift

  check_cursor_bounds();
  return 1;
}

_______________________________________________
Toybox mailing list
Toybox@lists.landley.net
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to