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, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
   9
  10config VI
  11  bool "vi"
  12  default n
  13  help
  14    usage: vi FILE
  15    Visual text editor. Predates the existence of standardized cursor keys,
  16    so the controls are weird and historical.
  17*/
  18
  19#define FOR_vi
  20#include "toys.h"
  21
  22GLOBALS(
  23    int cur_col;
  24    int cur_row;
  25    unsigned screen_height;
  26    unsigned screen_width;
  27    int vi_mode;
  28    int count0;
  29    int count1;
  30    int vi_mov_flag;
  31    int modified;
  32    char vi_reg;
  33)
  34
  35/*
  36 *
  37 * TODO:
  38 * BUGS:  screen pos adjust does not cover "widelines"
  39 *
  40 *
  41 * REFACTOR:  use dllist functions where possible.
  42 *            draw_page dont draw full page at time if nothing changed...
  43 *            ex callbacks
  44 *
  45 * FEATURE:   ex: / ? %   //atleast easy cases
  46 *            ex: r
  47 *            ex: !external programs
  48 *            ex: w filename //only writes to same file now
  49 *            big file support?
  50 */
  51
  52
  53struct linestack_show {
  54  struct linestack_show *next;
  55  long top, left;
  56  int x, width, y, height;
  57};
  58
  59static void draw_page();
  60static int draw_str_until(int *drawn, char *str, int width, int bytes);
  61static void draw_char(char c, int x, int y, int highlight);
  62//utf8 support
  63static int utf8_lnw(int* width, char* str, int bytes);
  64static int utf8_dec(char key, char *utf8_scratch, int *sta_p);
  65static int utf8_len(char *str);
  66static int utf8_width(char *str, int bytes);
  67static int draw_rune(char *c, int x, int y, int highlight);
  68static char* utf8_last(char* str, int size);
  69
  70
  71static int cur_left(int count0, int count1, char* unused);
  72static int cur_right(int count0, int count1, char* unused);
  73static int cur_up(int count0, int count1, char* unused);
  74static int cur_down(int count0, int count1, char* unused);
  75static void check_cursor_bounds();
  76static void adjust_screen_buffer();
  77
  78
  79struct str_line {
  80  int alloc_len;
  81  int str_len;
  82  char *str_data;
  83};
  84
  85//lib dllist uses next and prev kinda opposite what im used to so I just
  86//renamed both ends to up and down
  87struct linelist {
  88  struct linelist *up;//next
  89  struct linelist *down;//prev
  90  struct str_line *line;
  91};
  92//inserted line not yet pushed to buffer
  93struct str_line *il;
  94struct linelist *text; //file loaded into buffer
  95struct linelist *scr_r;//current screen coord 0 row
  96struct linelist *c_r;//cursor position row
  97
  98
  99void dlist_insert_nomalloc(struct double_list **list, struct double_list *new)
 100{
 101  if (*list) {
 102    new->next = *list;
 103    new->prev = (*list)->prev;
 104    if ((*list)->prev) (*list)->prev->next = new;
 105    (*list)->prev = new;
 106  } else *list = new->next = new->prev = new;
 107}
 108
 109
 110// Add an entry to the end of a doubly linked list
 111struct double_list *dlist_insert(struct double_list **list, char *data)
 112{
 113  struct double_list *new = xmalloc(sizeof(struct double_list));
 114  new->data = data;
 115  dlist_insert_nomalloc(list, new);
 116
 117  return new;
 118}
 119//TODO implement
 120void linelist_unload()
 121{
 122
 123}
 124
 125void write_file(char *filename)
 126{
 127  struct linelist *lst = text;
 128  FILE *fp = 0;
 129  if (!filename)
 130    filename = (char*)*toys.optargs;
 131  fp = fopen(filename, "w");
 132  if (!fp) return;
 133  while (lst) {
 134    fprintf(fp, "%s\n", lst->line->str_data);
 135    lst = lst->down;
 136  }
 137  fclose(fp);
 138}
 139
 140int linelist_load(char *filename)
 141{
 142  struct linelist *lst = c_r;//cursor position or 0
 143  FILE *fp = 0;
 144  if (!filename)
 145    filename = (char*)*toys.optargs;
 146
 147  fp = fopen(filename, "r");
 148  if (!fp) {
 149    char *line = xzalloc(80);
 150    ssize_t alc = 80;
 151    lst = (struct linelist*)dlist_add((struct double_list**)&lst,
 152        xzalloc(sizeof(struct str_line)));
 153    lst->line->alloc_len = alc;
 154    lst->line->str_len = 0;
 155    lst->line->str_data = line;
 156    text = lst;
 157    dlist_terminate(text->up);
 158    return 1;
 159  }
 160
 161  for (;;) {
 162    char *line = xzalloc(80);
 163    ssize_t alc = 80;
 164    ssize_t len;
 165    if ((len = getline(&line, (void *)&alc, fp)) == -1) {
 166      if (errno == EINVAL || errno == ENOMEM) {
 167        printf("error %d\n", errno);
 168      }
 169      free(line);
 170      break;
 171    }
 172    lst = (struct linelist*)dlist_add((struct double_list**)&lst,
 173        xzalloc(sizeof(struct str_line)));
 174    lst->line->alloc_len = alc;
 175    lst->line->str_len = len;
 176    lst->line->str_data = line;
 177
 178    if (lst->line->str_data[len-1] == '\n') {
 179      lst->line->str_data[len-1] = 0;
 180      lst->line->str_len--;
 181    }
 182    if (text == 0) {
 183      text = lst;
 184    }
 185
 186  }
 187  if (text) {
 188    dlist_terminate(text->up);
 189  }
 190  fclose(fp);
 191  return 1;
 192
 193}
 194
 195int vi_yy(char reg, int count0, int count1)
 196{
 197  return 1;
 198}
 199
 200//TODO this is overly complicated refactor with lib dllist
 201int vi_dd(char reg, int count0, int count1)
 202{
 203  int count = count0*count1;
 204  struct linelist *lst = c_r;
 205  if (c_r == text && text == scr_r) {
 206    if (!text->down && !text->up && text->line) {
 207      text->line->str_len = 1;
 208      sprintf(text->line->str_data, " ");
 209      goto success_exit;
 210    }
 211    if (text->down) {
 212      text = text->down;
 213      text->up = 0;
 214      c_r = text;
 215      scr_r = text;
 216      free(lst->line->str_data);
 217      free(lst->line);
 218      free(lst);
 219    }
 220    goto recursion_exit;
 221  }
 222  //TODO use lib dllist stuff
 223  if (lst)
 224  {
 225    if (lst->down) {
 226      lst->down->up = lst->up;
 227    }
 228    if (lst->up) {
 229      lst->up->down = lst->down;
 230    }
 231    if (scr_r == c_r) {
 232      scr_r = c_r->down ? c_r->down : c_r->up;
 233    }
 234    if (c_r->down)
 235      c_r = c_r->down;
 236    else {
 237      c_r = c_r->up;
 238      count = 1;
 239    }
 240    free(lst->line->str_data);
 241    free(lst->line);
 242    free(lst);
 243  }
 244
 245recursion_exit:
 246  count--;
 247  //make this recursive
 248  if (count>0)
 249    return vi_dd(reg, count, 1);
 250success_exit:
 251  check_cursor_bounds();
 252  adjust_screen_buffer();
 253  return 1;
 254}
 255//TODO i think this thing has bug when removing >40 chars from 80 wide line
 256static int vi_x(char reg, int count0, int count1)
 257{
 258  int count = count0;
 259  char *s;
 260  char *last;
 261  int *l;
 262  int length = 0;
 263  int width = 0;
 264  int remaining = 0;
 265  char *end;
 266  char *start;
 267  if (!c_r)
 268    return 0;
 269  s = c_r->line->str_data;
 270  l = &c_r->line->str_len;
 271
 272  last = utf8_last(s,*l);
 273  if (last == s+TT.cur_col) {
 274    memset(last, 0, (*l)-TT.cur_col);
 275    *l = TT.cur_col;
 276    if (!TT.cur_col) return 1;
 277    last = utf8_last(s, TT.cur_col);
 278    TT.cur_col = last-s;
 279    return 1;
 280  }
 281
 282  start = s+TT.cur_col;
 283  end = start;
 284  remaining = (*l)-TT.cur_col;
 285  for (;remaining;) {
 286    int next = utf8_lnw(&width, end, remaining);
 287    if (next && width) {
 288      if (!count) break;
 289      count--;
 290    } if (!next) break;
 291    length += next;
 292    end += next;
 293    remaining -= next;
 294  }
 295  if (remaining) {
 296    memmove(start, end, remaining);
 297    memset(start+remaining,0,end-start);
 298  } else {
 299    memset(start,0,(*l)-TT.cur_col);
 300  }
 301  *l -= end-start;
 302  if (!TT.cur_col) return 1;
 303  if (TT.cur_col == (*l)) {
 304    last = utf8_last(s, TT.cur_col);
 305    TT.cur_col = last-s;
 306  }
 307  return 1;
 308}
 309
 310//move commands does not behave correct way yet.
 311int vi_movw(int count0, int count1, char* unused)
 312{
 313  int count = count0*count1;
 314  const char *empties = " \t\n\r";
 315  const char *specials = ",.=-+*/(){}<>[]";
 316//  char *current = 0;
 317  if (!c_r)
 318    return 0;
 319  if (TT.cur_col == c_r->line->str_len-1 || !c_r->line->str_len)
 320    goto next_line;
 321  if (strchr(empties, c_r->line->str_data[TT.cur_col]))
 322    goto find_non_empty;
 323  if (strchr(specials, c_r->line->str_data[TT.cur_col])) {
 324    for (;strchr(specials, c_r->line->str_data[TT.cur_col]); ) {
 325      TT.cur_col++;
 326      if (TT.cur_col == c_r->line->str_len-1)
 327        goto next_line;
 328    }
 329  } else for (;!strchr(specials, c_r->line->str_data[TT.cur_col]) &&
 330      !strchr(empties, c_r->line->str_data[TT.cur_col]);) {
 331      TT.cur_col++;
 332      if (TT.cur_col == c_r->line->str_len-1)
 333        goto next_line;
 334  }
 335
 336  for (;strchr(empties, c_r->line->str_data[TT.cur_col]); ) {
 337    TT.cur_col++;
 338find_non_empty:
 339    if (TT.cur_col == c_r->line->str_len-1) {
 340next_line:
 341      //we could call j and g0
 342      if (!c_r->down) return 0;
 343      c_r = c_r->down;
 344      TT.cur_col = 0;
 345      if (!c_r->line->str_len) break;
 346    }
 347  }
 348  count--;
 349  if (count>0)
 350    return vi_movw(count, 1, 0);
 351
 352  check_cursor_bounds();
 353  adjust_screen_buffer();
 354  return 1;
 355}
 356
 357static int vi_movb(int count0, int count1, char* unused)
 358{
 359  int count = count0*count1;
 360  if (!c_r)
 361    return 0;
 362  if (!TT.cur_col) {
 363      if (!c_r->up) return 0;
 364      c_r = c_r->up;
 365      TT.cur_col = (c_r->line->str_len) ? c_r->line->str_len-1 : 0;
 366      goto exit_function;
 367  }
 368  if (TT.cur_col)
 369      TT.cur_col--;
 370  while (c_r->line->str_data[TT.cur_col] <= ' ') {
 371    if (TT.cur_col) TT.cur_col--;
 372    else goto exit_function;
 373  }
 374  while (c_r->line->str_data[TT.cur_col] > ' ') {
 375    if (TT.cur_col)TT.cur_col--;
 376    else goto exit_function;
 377  }
 378  TT.cur_col++;
 379exit_function:
 380  count--;
 381  if (count>1)
 382    return vi_movb(count, 1, 0);
 383  check_cursor_bounds();
 384  adjust_screen_buffer();
 385  return 1;
 386}
 387
 388static int vi_move(int count0, int count1, char *unused)
 389{
 390  int count = count0*count1;
 391  if (!c_r)
 392    return 0;
 393  if (TT.cur_col < c_r->line->str_len)
 394    TT.cur_col++;
 395  if (c_r->line->str_data[TT.cur_col] <= ' ' || count > 1)
 396    vi_movw(count, 1, 0); //find next word;
 397  while (c_r->line->str_data[TT.cur_col] > ' ')
 398    TT.cur_col++;
 399  if (TT.cur_col) TT.cur_col--;
 400
 401  TT.vi_mov_flag |= 2;
 402  check_cursor_bounds();
 403  adjust_screen_buffer();
 404  return 1;
 405}
 406
 407void i_insert()
 408{
 409  char *t = xzalloc(c_r->line->alloc_len);
 410  char *s = c_r->line->str_data;
 411  int sel = c_r->line->str_len-TT.cur_col;
 412  strncpy(t, &s[TT.cur_col], sel);
 413  t[sel+1] = 0;
 414  if (c_r->line->alloc_len < c_r->line->str_len+il->str_len+5) {
 415    c_r->line->str_data = xrealloc(c_r->line->str_data,
 416      c_r->line->alloc_len*2+il->alloc_len*2);
 417
 418    c_r->line->alloc_len = c_r->line->alloc_len*2+2*il->alloc_len;
 419    memset(&c_r->line->str_data[c_r->line->str_len], 0,
 420        c_r->line->alloc_len-c_r->line->str_len);
 421
 422    s = c_r->line->str_data;
 423  }
 424  strcpy(&s[TT.cur_col], il->str_data);
 425  strcpy(&s[TT.cur_col+il->str_len], t);
 426  TT.cur_col += il->str_len;
 427  if (TT.cur_col) TT.cur_col--;
 428  c_r->line->str_len += il->str_len;
 429  free(t);
 430
 431}
 432
 433//new line at split pos;
 434void i_split()
 435{
 436  struct str_line *l = xmalloc(sizeof(struct str_line));
 437  int l_a = c_r->line->alloc_len;
 438  int l_len = c_r->line->str_len-TT.cur_col;
 439  l->str_data = xzalloc(l_a);
 440  l->alloc_len = l_a;
 441  l->str_len = l_len;
 442  strncpy(l->str_data, &c_r->line->str_data[TT.cur_col], l_len);
 443  l->str_data[l_len] = 0;
 444  c_r->line->str_len -= l_len;
 445  c_r->line->str_data[c_r->line->str_len] = 0;
 446  c_r = (struct linelist*)dlist_insert((struct double_list**)&c_r, (char*)l);
 447  c_r->line = l;
 448  TT.cur_col = 0;
 449  check_cursor_bounds();
 450  adjust_screen_buffer();
 451}
 452
 453static int vi_zero(int count0, int count1, char *unused)
 454{
 455  TT.cur_col = 0;
 456  return 1;
 457}
 458
 459static int vi_eol(int count0, int count1, char *unused)
 460{
 461  int count = count0*count1;
 462  for (;count > 1 && c_r->down; count--)
 463    c_r = c_r->down;
 464
 465  if (c_r && c_r->line->str_len)
 466    TT.cur_col = c_r->line->str_len-1;
 467  TT.vi_mov_flag |= 2;
 468  check_cursor_bounds();
 469  return 1;
 470}
 471
 472static int vi_find_c(int count0, int count1, char *symbol)
 473{
 474  int count = count0*count1;
 475  if (c_r && c_r->line->str_len) {
 476    while (count--) {
 477        char* pos = strstr(&c_r->line->str_data[TT.cur_col], symbol);
 478        if (pos) {
 479          TT.cur_col = pos-c_r->line->str_data;
 480          return 1;
 481        }
 482    }
 483  }
 484  return 0;
 485}
 486
 487static int vi_find_cb(int count0, int count1, char *symbol)
 488{
 489  //do backward search
 490  return 1;
 491}
 492
 493//if count is not spesified should go to last line
 494static int vi_go(int count0, int count1, char *symbol)
 495{
 496  c_r = text;
 497  while(--count0) {
 498    if (c_r && c_r->down) c_r = c_r->down;
 499  }
 500  TT.cur_col = 0;
 501  check_cursor_bounds();
 502  adjust_screen_buffer();
 503  return 1;
 504}
 505
 506//need to refactor when implementing yank buffers
 507static int vi_delete(char reg, struct linelist *row, int col, int flags)
 508{
 509  if (row == c_r) {
 510    if (col < TT.cur_col) {
 511      int distance = TT.cur_col - col;
 512      TT.cur_col = col;
 513      vi_x(reg, distance, 1);
 514    } else {
 515      int distance = col - TT.cur_col;
 516      if (distance > 0) vi_x(reg, distance, 1);
 517    }
 518    if (TT.vi_mov_flag&2) 
 519      vi_x(reg, 1, 1);
 520  }
 521  return 1;
 522}
 523
 524static int vi_D(char reg, int count0, int count1)
 525{
 526  int prev_col = TT.cur_col;
 527  struct linelist *pos = c_r;
 528  if (!count0) return 1;
 529  vi_eol(1, 1, 0);
 530  vi_delete(reg, pos, prev_col, 0);
 531  count0--;
 532  if (count0 && c_r->down) {
 533    c_r = c_r->down;
 534    vi_dd(reg, count0, 1);
 535  }
 536  return 1;
 537}
 538
 539static int vi_join(char reg, int count0, int count1)
 540{
 541  while (count0--) {
 542    if (c_r && c_r->down) {
 543      int size = c_r->line->str_len+c_r->down->line->str_len;
 544      if (size > c_r->line->alloc_len) {
 545        if (size > c_r->down->line->alloc_len) {
 546          c_r->line->str_data = xrealloc(c_r->line->str_data,
 547            c_r->line->alloc_len*2+il->alloc_len*2);
 548          memmove(&c_r->line->str_data[c_r->line->str_len],
 549              c_r->down->line->str_data,c_r->down->line->str_len);
 550          c_r->line->str_len = size;
 551          c_r = c_r->down;
 552          c_r->line->alloc_len = c_r->line->alloc_len*2+2*il->alloc_len;
 553          vi_dd(0,1,1);
 554        } else {
 555          memmove(&c_r->down->line->str_data[c_r->line->str_len],
 556              c_r->down->line->str_data,c_r->down->line->str_len);
 557          memmove(c_r->down->line->str_data,c_r->line->str_data,
 558              c_r->line->str_len);
 559          c_r->down->line->str_len = size;
 560          vi_dd(0,1,1);
 561        }
 562      } else {
 563          memmove(&c_r->line->str_data[c_r->line->str_len],
 564              c_r->down->line->str_data,c_r->down->line->str_len);
 565          c_r->line->str_len = size;
 566          c_r = c_r->down;
 567          vi_dd(0,1,1);
 568      }
 569      c_r = c_r->up;
 570
 571    }
 572  }
 573  return 1;
 574}
 575
 576static int vi_change(char reg, struct linelist *row, int col, int flags)
 577{
 578  vi_delete(reg, row, col, flags);
 579  TT.vi_mode = 2;
 580  return 1;
 581}
 582
 583static int vi_yank(char reg, struct linelist *row, int col, int flags)
 584{
 585  return 1;
 586}
 587
 588//NOTES
 589//vi-mode cmd syntax is
 590//("[REG])[COUNT0]CMD[COUNT1](MOV)
 591//where:
 592//-------------------------------------------------------------
 593//"[REG] is optional buffer where deleted/yanked text goes REG can be
 594//  atleast 0-9, a-z or default "
 595//[COUNT] is optional multiplier for cmd execution if there is 2 COUNT
 596//  operations they are multiplied together
 597//CMD is operation to be executed
 598//(MOV) is movement operation, some CMD does not require MOV and some
 599//  have special cases such as dd, yy, also movements can work without
 600//  CMD
 601//ex commands can be even more complicated than this....
 602//
 603struct vi_cmd_param {
 604  const char* cmd;
 605  unsigned flags;
 606  int (*vi_cmd)(char, struct linelist*, int, int);//REG,row,col,FLAGS
 607};
 608struct vi_mov_param {
 609  const char* mov;
 610  unsigned flags;
 611  int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params
 612};
 613//spesial cases without MOV and such
 614struct vi_spesial_param {
 615  const char *cmd;
 616  int (*vi_spesial)(char, int, int);//REG,COUNT0,COUNT1 
 617};
 618struct vi_spesial_param vi_spesial[5] =
 619{
 620  {"dd", &vi_dd},
 621  {"yy", &vi_yy},
 622  {"D", &vi_D},
 623  {"J", &vi_join},
 624  {"x", &vi_x},
 625};
 626//there is around ~47 vi moves
 627//some of them need extra params
 628//such as f and '
 629struct vi_mov_param vi_movs[12] =
 630{
 631  {"0", 0, &vi_zero},
 632  {"b", 0, &vi_movb},
 633  {"e", 0, &vi_move},
 634  {"G", 0, &vi_go},
 635  {"h", 0, &cur_left},
 636  {"j", 0, &cur_down},
 637  {"k", 0, &cur_up},
 638  {"l", 0, &cur_right},
 639  {"w", 0, &vi_movw},
 640  {"$", 0, &vi_eol},
 641  {"f", 1, &vi_find_c},
 642  {"F", 1, &vi_find_cb},
 643};
 644//change and delete unfortunately behave different depending on move command,
 645//such as ce cw are same, but dw and de are not...
 646//also dw stops at w position and cw seem to stop at e pos+1...
 647//so after movement we need to possibly set up some flags before executing
 648//command, and command needs to adjust...
 649struct vi_cmd_param vi_cmds[3] =
 650{
 651  {"c", 1, &vi_change},
 652  {"d", 1, &vi_delete},
 653  {"y", 1, &vi_yank},
 654};
 655
 656int run_vi_cmd(char *cmd)
 657{
 658  int i = 0;
 659  int val = 0;
 660  char *cmd_e;
 661  int (*vi_cmd)(char, struct linelist*, int, int) = 0;
 662  int (*vi_mov)(int, int, char*) = 0;
 663  TT.count0 = 0;
 664  TT.count1 = 0;
 665  TT.vi_reg = '"';
 666  TT.vi_mov_flag = 0;
 667  if (*cmd == '"') {
 668    cmd++;
 669    TT.vi_reg = *cmd; //TODO check validity
 670    cmd++;
 671  }
 672  val = strtol(cmd, &cmd_e, 10);
 673  if (errno || val == 0) val = 1;
 674  else cmd = cmd_e;
 675  TT.count0 = val;
 676
 677  for (i = 0; i < 5; i++) {
 678    if (strstr(cmd, vi_spesial[i].cmd)) {
 679      return vi_spesial[i].vi_spesial(TT.vi_reg, TT.count0, TT.count1);
 680    }
 681  }
 682
 683  for (i = 0; i < 3; i++) {
 684    if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) {
 685      vi_cmd = vi_cmds[i].vi_cmd;
 686      cmd += strlen(vi_cmds[i].cmd);
 687      break;
 688    }
 689  }
 690  val = strtol(cmd, &cmd_e, 10);
 691  if (errno || val == 0) val = 1;
 692  else cmd = cmd_e;
 693  TT.count1 = val;
 694
 695  for (i = 0; i < 12; i++) {
 696    if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) {
 697      vi_mov = vi_movs[i].vi_mov;
 698      TT.vi_mov_flag = vi_movs[i].flags;
 699      cmd++;
 700      if (TT.vi_mov_flag&1 && !(*cmd)) return 0;
 701      break;
 702    }
 703  }
 704  if (vi_mov) {
 705    int prev_col = TT.cur_col;
 706    struct linelist *pos = c_r;
 707    if (vi_mov(TT.count0, TT.count1, cmd)) {
 708      if (vi_cmd) return (vi_cmd(TT.vi_reg, pos, prev_col, TT.vi_mov_flag));
 709      else return 1;
 710    } else return 0; //return some error
 711  }
 712  return 0;
 713}
 714
 715int search_str(char *s)
 716{
 717  struct linelist *lst = c_r;
 718  char *c = strstr(&c_r->line->str_data[TT.cur_col], s);
 719  if (c) {
 720    TT.cur_col = c_r->line->str_data-c; //TODO ??
 721  TT.cur_col = c-c_r->line->str_data;
 722  }
 723  else for (; !c;) {
 724    lst = lst->down;
 725    if (!lst) return 1;
 726    c = strstr(&lst->line->str_data[TT.cur_col], s);
 727  }
 728  c_r = lst;
 729  TT.cur_col = c-c_r->line->str_data;
 730  return 0;
 731}
 732
 733int run_ex_cmd(char *cmd)
 734{
 735  if (cmd[0] == '/') {
 736    //search pattern
 737    if (!search_str(&cmd[1]) ) {
 738      check_cursor_bounds();
 739      adjust_screen_buffer();
 740    }
 741  } else if (cmd[0] == '?') {
 742
 743  } else if (cmd[0] == ':') {
 744    if (strstr(&cmd[1], "q!")) {
 745      //exit_application;
 746      return -1;
 747    }
 748    else if (strstr(&cmd[1], "wq")) {
 749      write_file(0);
 750      return -1;
 751    }
 752    else if (strstr(&cmd[1], "w")) {
 753      write_file(0);
 754      return 1;
 755    }
 756  }
 757  return 0;
 758
 759}
 760
 761void vi_main(void)
 762{
 763  char keybuf[16];
 764  char utf8_code[8];
 765  int utf8_dec_p = 0;
 766  int key = 0;
 767  char vi_buf[16];
 768  int vi_buf_pos = 0;
 769  il = xzalloc(sizeof(struct str_line));
 770  il->str_data = xzalloc(80);
 771  il->alloc_len = 80;
 772  keybuf[0] = 0;
 773  memset(vi_buf, 0, 16);
 774  memset(utf8_code, 0, 8);
 775  linelist_load(0);
 776  scr_r = text;
 777  c_r = text;
 778  TT.cur_row = 0;
 779  TT.cur_col = 0;
 780  TT.screen_width = 80;
 781  TT.screen_height = 24;
 782  TT.vi_mode = 1;
 783  terminal_size(&TT.screen_width, &TT.screen_height);
 784  TT.screen_height -= 2; //TODO this is hack fix visual alignment
 785  set_terminal(0, 1, 0, 0);
 786  //writes stdout into different xterm buffer so when we exit
 787  //we dont get scroll log full of junk
 788  tty_esc("?1049h");
 789  tty_esc("H");
 790  xflush(1);
 791  draw_page();
 792  while(1) {
 793    key = scan_key(keybuf, -1);
 794    printf("key %d\n", key);
 795    switch (key) {
 796      case -1:
 797      case 3:
 798      case 4:
 799        goto cleanup_vi;
 800    }
 801    if (TT.vi_mode == 1) { //NORMAL
 802      switch (key) {
 803        case '/':
 804        case '?':
 805        case ':':
 806          TT.vi_mode = 0;
 807          il->str_data[0]=key;
 808          il->str_len++;
 809          break;
 810        case 'a':
 811          if (c_r && c_r->line->str_len)
 812            TT.cur_col++;
 813        case 'i':
 814          TT.vi_mode = 2;
 815          break;
 816        case 27:
 817          vi_buf[0] = 0;
 818          vi_buf_pos = 0;
 819          break;
 820        default:
 821          if (key > 0x20 && key < 0x7B) {
 822            vi_buf[vi_buf_pos] = key;//TODO handle input better
 823            vi_buf_pos++;
 824            if (run_vi_cmd(vi_buf)) {
 825              memset(vi_buf, 0, 16);
 826              vi_buf_pos = 0;
 827            }
 828            else if (vi_buf_pos == 16) {
 829              vi_buf_pos = 0;
 830              memset(vi_buf, 0, 16);
 831            }
 832
 833          }
 834
 835          break;
 836      }
 837    } else if (TT.vi_mode == 0) { //EX MODE
 838      switch (key) {
 839        case 27:
 840          TT.vi_mode = 1;
 841          il->str_len = 0;
 842          memset(il->str_data, 0, il->alloc_len);
 843          break;
 844        case 0x7F:
 845        case 0x08:
 846          if (il->str_len) {
 847            il->str_data[il->str_len] = 0;
 848            if (il->str_len > 1) il->str_len--;
 849          }
 850          break;
 851        case 0x0D:
 852            if (run_ex_cmd(il->str_data) == -1)
 853              goto cleanup_vi;
 854          TT.vi_mode = 1;
 855          il->str_len = 0;
 856          memset(il->str_data, 0, il->alloc_len);
 857          break;
 858        default: //add chars to ex command until ENTER
 859          if (key >= 0x20 && key < 0x7F) { //might be utf?
 860            if (il->str_len == il->alloc_len) {
 861              il->str_data = realloc(il->str_data, il->alloc_len*2);
 862              il->alloc_len *= 2;
 863            }
 864            il->str_data[il->str_len] = key;
 865            il->str_len++;
 866          }
 867          break;
 868      }
 869    } else if (TT.vi_mode == 2) {//INSERT MODE
 870      switch (key) {
 871        case 27:
 872          i_insert();
 873          TT.vi_mode = 1;
 874          il->str_len = 0;
 875          memset(il->str_data, 0, il->alloc_len);
 876          break;
 877        case 0x7F:
 878        case 0x08:
 879          if (il->str_len)
 880            il->str_data[il->str_len--] = 0;
 881          break;
 882        case 0x09:
 883          //TODO implement real tabs
 884          il->str_data[il->str_len++] = ' ';
 885          il->str_data[il->str_len++] = ' ';
 886          break;
 887
 888        case 0x0D:
 889          //insert newline
 890          //
 891          i_insert();
 892          il->str_len = 0;
 893          memset(il->str_data, 0, il->alloc_len);
 894          i_split();
 895          break;
 896        default:
 897          if (key >= 0x20 && utf8_dec(key, utf8_code, &utf8_dec_p)) {
 898            if (il->str_len+utf8_dec_p+1 >= il->alloc_len) {
 899              il->str_data = realloc(il->str_data, il->alloc_len*2);
 900              il->alloc_len *= 2;
 901            }
 902            strcpy(il->str_data+il->str_len, utf8_code);
 903            il->str_len += utf8_dec_p;
 904            utf8_dec_p = 0;
 905            *utf8_code = 0;
 906
 907          }
 908          break;
 909      }
 910    }
 911
 912    draw_page();
 913
 914  }
 915cleanup_vi:
 916  linelist_unload();
 917  tty_reset();
 918  tty_esc("?1049l");
 919}
 920
 921static void draw_page()
 922{
 923  unsigned y = 0;
 924  int cy_scr = 0;
 925  int cx_scr = 0;
 926  int utf_l = 0;
 927
 928  char* line = 0;
 929  int bytes = 0;
 930  int drawn = 0;
 931  int x = 0;
 932  struct linelist *scr_buf= scr_r;
 933  //clear screen
 934  tty_esc("2J");
 935  tty_esc("H");
 936
 937  tty_jump(0, 0);
 938
 939  //draw lines until cursor row
 940  for (; y < TT.screen_height; ) {
 941    if (line && bytes) {
 942      draw_str_until(&drawn, line, TT.screen_width, bytes);
 943      bytes = drawn ? (bytes-drawn) : 0;
 944      line = bytes ? (line+drawn) : 0;
 945      y++;
 946      tty_jump(0, y);
 947    } else if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
 948      if (scr_buf == c_r)
 949        break;
 950      line = scr_buf->line->str_data;
 951      bytes = scr_buf->line->str_len;
 952      scr_buf = scr_buf->down;
 953    } else {
 954      if (scr_buf == c_r)
 955        break;
 956      y++;
 957      tty_jump(0, y);
 958      //printf(" \n");
 959      if (scr_buf) scr_buf = scr_buf->down;
 960    }
 961
 962  }
 963  //draw cursor row until cursor
 964  //this is to calculate cursor position on screen and possible insert
 965  line = scr_buf->line->str_data;
 966  bytes = TT.cur_col;
 967  for (; y < TT.screen_height; ) {
 968    if (bytes) {
 969      x = draw_str_until(&drawn, line, TT.screen_width, bytes);
 970      bytes = drawn ? (bytes-drawn) : 0;
 971      line = bytes ? (line+drawn) : 0;
 972    }
 973    if (!bytes) break;
 974    y++;
 975    tty_jump(0, y);
 976  }
 977  if (TT.vi_mode == 2 && il->str_len) {
 978    line = il->str_data;
 979    bytes = il->str_len;
 980    cx_scr = x;
 981    cy_scr = y;
 982    x = draw_str_until(&drawn, line, TT.screen_width-x, bytes);
 983    bytes = drawn ? (bytes-drawn) : 0;
 984    line = bytes ? (line+drawn) : 0;
 985    cx_scr += x;
 986    for (; y < TT.screen_height; ) {
 987      if (bytes) {
 988        x = draw_str_until(&drawn, line, TT.screen_width, bytes);
 989        bytes = drawn ? (bytes-drawn) : 0;
 990        line = bytes ? (line+drawn) : 0;
 991        cx_scr = x;
 992      }
 993      if (!bytes) break;
 994      y++;
 995      cy_scr = y;
 996      tty_jump(0, y);
 997    }
 998  } else {
 999    cy_scr = y;
1000    cx_scr = x;
1001  }
1002  line = scr_buf->line->str_data+TT.cur_col;
1003  bytes = scr_buf->line->str_len-TT.cur_col;
1004  scr_buf = scr_buf->down;
1005  x = draw_str_until(&drawn,line, TT.screen_width-x, bytes);
1006  bytes = drawn ? (bytes-drawn) : 0;
1007  line = bytes ? (line+drawn) : 0;
1008  y++;
1009  tty_jump(0, y);
1010
1011//draw until end
1012  for (; y < TT.screen_height; ) {
1013    if (line && bytes) {
1014      draw_str_until(&drawn, line, TT.screen_width, bytes);
1015      bytes = drawn ? (bytes-drawn) : 0;
1016      line = bytes ? (line+drawn) : 0;
1017      y++;
1018      tty_jump(0, y);
1019    } else if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
1020      line = scr_buf->line->str_data;
1021      bytes = scr_buf->line->str_len;
1022      scr_buf = scr_buf->down;
1023    } else {
1024      y++;
1025      tty_jump(0, y);
1026      if (scr_buf) scr_buf = scr_buf->down;
1027    }
1028
1029  }
1030
1031  tty_jump(0, TT.screen_height);
1032  switch (TT.vi_mode) {
1033    case 0:
1034    tty_esc("30;44m");
1035    printf("COMMAND|");
1036    break;
1037    case 1:
1038    tty_esc("30;42m");
1039    printf("NORMAL|");
1040    break;
1041    case 2:
1042    tty_esc("30;41m");
1043    printf("INSERT|");
1044    break;
1045
1046  }
1047  //DEBUG
1048  tty_esc("47m");
1049  tty_esc("30m");
1050  utf_l = utf8_len(&c_r->line->str_data[TT.cur_col]);
1051  if (utf_l) {
1052    char t[5] = {0, 0, 0, 0, 0};
1053    strncpy(t, &c_r->line->str_data[TT.cur_col], utf_l);
1054    printf("utf: %d %s", utf_l, t);
1055  }
1056  printf("| %d, %d\n", cx_scr, cy_scr); //screen coord
1057
1058  tty_jump(TT.screen_width-12, TT.screen_height);
1059  printf("| %d, %d\n", TT.cur_row, TT.cur_col);
1060  tty_esc("37m");
1061  tty_esc("40m");
1062  if (!TT.vi_mode) {
1063    tty_esc("1m");
1064    tty_jump(0, TT.screen_height+1);
1065    printf("%s", il->str_data);
1066    tty_esc("0m");
1067  } else tty_jump(cx_scr, cy_scr);
1068
1069  xflush(1);
1070
1071}
1072
1073static void draw_char(char c, int x, int y, int highlight)
1074{
1075  tty_jump(x, y);
1076  if (highlight) {
1077    tty_esc("30m"); //foreground black
1078    tty_esc("47m"); //background white
1079  }
1080  printf("%c", c);
1081}
1082
1083//utf rune draw
1084//printf and useless copy could be replaced by direct write() to stdout
1085static int draw_rune(char *c, int x, int y, int highlight)
1086{
1087  int l = utf8_len(c);
1088  char t[5] = {0, 0, 0, 0, 0};
1089  if (!l) return 0;
1090  tty_jump(x, y);
1091  tty_esc("0m");
1092  if (highlight) {
1093    tty_esc("30m"); //foreground black
1094    tty_esc("47m"); //background white
1095  }
1096  strncpy(t, c, 5);
1097  printf("%s", t);
1098  tty_esc("0m");
1099  return l;
1100}
1101
1102static void check_cursor_bounds()
1103{
1104  if (c_r->line->str_len == 0) TT.cur_col = 0;
1105  else if (c_r->line->str_len-1 < TT.cur_col) TT.cur_col = c_r->line->str_len-1;
1106  if (utf8_width(&c_r->line->str_data[TT.cur_col], c_r->line->str_len-TT.cur_col) <= 0)
1107    cur_left(1, 1, 0);
1108}
1109
1110static void adjust_screen_buffer()
1111{
1112  //search cursor and screen TODO move this perhaps
1113  struct linelist *t = text;
1114  int c = -1;
1115  int s = -1;
1116  int i = 0;
1117  for (;;) {
1118    i++;
1119    if (t == c_r)
1120      c = i;
1121    if (t == scr_r)
1122      s = i;
1123    t = t->down;
1124    if ( ((c != -1) && (s != -1)) || t == 0)
1125      break;
1126  }
1127  if (c <= s) {
1128    scr_r = c_r;
1129  }
1130  else if ( c > s ) {
1131    //should count multiline long strings!
1132    int distance = c - s + 1;
1133    //TODO instead iterate scr_r up and check strlen%screen_width
1134    //for each iteration
1135    if (distance >= (int)TT.screen_height) {
1136      int adj = distance - TT.screen_height;
1137      while (adj--) {
1138        scr_r = scr_r->down;
1139      }
1140    }
1141  }
1142  TT.cur_row = c;
1143
1144}
1145
1146//return 0 if not ASCII nor UTF-8
1147//this is not fully tested
1148//naive implementation with branches
1149//there is better branchless lookup table versions out there
1150//1 0xxxxxxx
1151//2 110xxxxx  10xxxxxx
1152//3 1110xxxx  10xxxxxx  10xxxxxx
1153//4 11110xxx  10xxxxxx  10xxxxxx  10xxxxxx
1154static int utf8_len(char *str)
1155{
1156  int len = 0;
1157  int i = 0;
1158  uint8_t *c = (uint8_t*)str;
1159  if (!c || !(*c)) return 0;
1160  if (*c < 0x7F) return 1;
1161  if ((*c & 0xE0) == 0xc0) len = 2;
1162  else if ((*c & 0xF0) == 0xE0 ) len = 3;
1163  else if ((*c & 0xF8) == 0xF0 ) len = 4;
1164  else return 0;
1165  c++;
1166  for (i = len-1; i > 0; i--) {
1167    if ((*c++ & 0xc0) != 0x80) return 0;
1168  }
1169  return len;
1170}
1171
1172//get utf8 length and width at same time
1173static int utf8_lnw(int* width, char* str, int bytes)
1174{
1175  wchar_t wc;
1176  int length = 1;
1177  *width = 1;
1178//  if (str < 0x7F) return length;
1179  length = mbtowc(&wc, str, bytes);
1180  switch (length) {
1181  case -1:
1182    mbtowc(0,0,4);
1183  case 0:
1184    *width = 0;
1185    length = 0;
1186    break;
1187  default:
1188  *width = wcwidth(wc);
1189  }
1190  return length;
1191}
1192
1193//try to estimate width of next "glyph" in terminal buffer
1194//combining chars 0x300-0x36F shall be zero width
1195static int utf8_width(char *str, int bytes)
1196{
1197  wchar_t wc;
1198  switch (mbtowc(&wc, str, bytes)) {
1199  case -1:
1200    mbtowc(0,0,4);
1201  case 0:
1202    return -1;
1203  default:
1204  return wcwidth(wc);
1205  }
1206  return 0;
1207}
1208
1209static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
1210{
1211  int len = 0;
1212  char *c = utf8_scratch;
1213  c[*sta_p] = key;
1214  if (!(*sta_p))  *c = key;
1215  if (*c < 0x7F) { *sta_p = 1; return 1; }
1216  if ((*c & 0xE0) == 0xc0) len = 2;
1217  else if ((*c & 0xF0) == 0xE0 ) len = 3;
1218  else if ((*c & 0xF8) == 0xF0 ) len = 4;
1219  else {*sta_p = 0; return 0; }
1220
1221  (*sta_p)++;
1222
1223  if (*sta_p == 1) return 0;
1224  if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
1225
1226  if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
1227
1228  return 0;
1229}
1230
1231static char* utf8_last(char* str, int size)
1232{
1233  char* end = str+size;
1234  int pos = size;
1235  int len = 0;
1236  int width = 0;
1237  while (pos >= 0) {
1238    len = utf8_lnw(&width, end, size-pos);
1239    if (len && width) return end;
1240    end--; pos--;
1241  }
1242  return 0;
1243}
1244
1245static int draw_str_until(int *drawn, char *str, int width, int bytes)
1246{
1247  int rune_width = 0;
1248  int rune_bytes = 0;
1249  int max_bytes = bytes;
1250  int max_width = width;
1251  char* end = str;
1252  for (;width && bytes;) {
1253    rune_bytes = utf8_lnw(&rune_width, end, 4);
1254    if (!rune_bytes) break;
1255    if (width - rune_width < 0) goto write_bytes;
1256    width -= rune_width;
1257    bytes -= rune_bytes;
1258    end += rune_bytes;
1259  }
1260  for (;bytes;) {
1261    rune_bytes = utf8_lnw(&rune_width, end, 4);
1262    if (!rune_bytes) break;
1263    if (rune_width) break;
1264    bytes -= rune_bytes;
1265    end += rune_bytes;
1266  }
1267write_bytes:
1268  fwrite(str, max_bytes-bytes, 1, stdout);
1269  *drawn = max_bytes-bytes;
1270  return max_width-width;
1271}
1272
1273static int cur_left(int count0, int count1, char* unused)
1274{
1275  int count = count0*count1;
1276  for (;count--;) {
1277    if (!TT.cur_col) return 1;
1278
1279    TT.cur_col--;
1280    check_cursor_bounds();//has bit ugly recursion hidden here
1281  }
1282  return 1;
1283}
1284
1285static int cur_right(int count0, int count1, char* unused)
1286{
1287  int count = count0*count1;
1288  for (;count--;) {
1289    if (c_r->line->str_len <= 1) return 1;
1290    if (TT.cur_col >= c_r->line->str_len-1) {
1291      TT.cur_col = utf8_last(c_r->line->str_data, c_r->line->str_len)
1292        - c_r->line->str_data;
1293      return 1;
1294    }
1295    TT.cur_col++;
1296    if (utf8_width(&c_r->line->str_data[TT.cur_col],
1297          c_r->line->str_len-TT.cur_col) <= 0)
1298      cur_right(1, 1, 0);
1299  }
1300  return 1;
1301}
1302
1303static int cur_up(int count0, int count1, char* unused)
1304{
1305  int count = count0*count1;
1306  for (;count-- && c_r->up;)
1307    c_r = c_r->up;
1308
1309  check_cursor_bounds();
1310  adjust_screen_buffer();
1311  return 1;
1312}
1313
1314static int cur_down(int count0, int count1, char* unused)
1315{
1316  int count = count0*count1;
1317  for (;count-- && c_r->down;)
1318    c_r = c_r->down;
1319
1320  check_cursor_bounds();
1321  adjust_screen_buffer();
1322  return 1;
1323}
1324
1325