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