xref: /third_party/toybox/toys/pending/vi.c (revision 0f66f451)
1/* vi.c - You can't spell "evil" without "vi".
2 *
3 * Copyright 2015 Rob Landley <rob@landley.net>
4 * Copyright 2019 Jarno Mäkipää <jmakip87@gmail.com>
5 *
6 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html
7
8USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
9
10config VI
11  bool "vi"
12  default n
13  help
14    usage: vi [-s script] FILE
15    -s script: run script file
16    Visual text editor. Predates the existence of standardized cursor keys,
17    so the controls are weird and historical.
18*/
19
20#define FOR_vi
21#include "toys.h"
22
23GLOBALS(
24  char *s;
25  int vi_mode, tabstop, list;
26  int cur_col, cur_row, scr_row;
27  int drawn_row, drawn_col;
28  int count0, count1, vi_mov_flag;
29  unsigned screen_height, screen_width;
30  char vi_reg, *last_search;
31  struct str_line {
32    int alloc;
33    int len;
34    char *data;
35  } *il;
36  size_t screen, cursor; //offsets
37  //yank buffer
38  struct yank_buf {
39    char reg;
40    int alloc;
41    char* data;
42  } yank;
43
44  int modified;
45  size_t filesize;
46// mem_block contains RO data that is either original file as mmap
47// or heap allocated inserted data
48//
49//
50//
51  struct block_list {
52    struct block_list *next, *prev;
53    struct mem_block {
54      size_t size;
55      size_t len;
56      enum alloc_flag {
57        MMAP,  //can be munmap() before exit()
58        HEAP,  //can be free() before exit()
59        STACK, //global or stack perhaps toybuf
60      } alloc;
61      const char *data;
62    } *node;
63  } *text;
64
65// slices do not contain actual allocated data but slices of data in mem_block
66// when file is first opened it has only one slice.
67// after inserting data into middle new mem_block is allocated for insert data
68// and 3 slices are created, where first and last slice are pointing to original
69// mem_block with offsets, and middle slice is pointing to newly allocated block
70// When deleting, data is not freed but mem_blocks are sliced more such way that
71// deleted data left between 2 slices
72  struct slice_list {
73    struct slice_list *next, *prev;
74    struct slice {
75      size_t len;
76      const char *data;
77    } *node;
78  } *slices;
79)
80
81static const char *blank = " \n\r\t";
82static const char *specials = ",.:;=-+*/(){}<>[]!@#$%^&|\\?\"\'";
83
84//get utf8 length and width at same time
85static int utf8_lnw(int *width, char *s, int bytes)
86{
87  unsigned wc;
88  int length = 1;
89
90  if (*s == '\t') *width = TT.tabstop;
91  else {
92    length = utf8towc(&wc, s, bytes);
93    if (length < 1) length = 0, *width = 0;
94    else *width = wcwidth(wc);
95  }
96  return length;
97}
98
99static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
100{
101  int len = 0;
102  char *c = utf8_scratch;
103  c[*sta_p] = key;
104  if (!(*sta_p))  *c = key;
105  if (*c < 0x7F) { *sta_p = 1; return 1; }
106  if ((*c & 0xE0) == 0xc0) len = 2;
107  else if ((*c & 0xF0) == 0xE0 ) len = 3;
108  else if ((*c & 0xF8) == 0xF0 ) len = 4;
109  else {*sta_p = 0; return 0; }
110
111  (*sta_p)++;
112
113  if (*sta_p == 1) return 0;
114  if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
115
116  if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
117
118  return 0;
119}
120
121static char* utf8_last(char* str, int size)
122{
123  char* end = str+size;
124  int pos = size, len, width = 0;
125  for (;pos >= 0; end--, pos--) {
126    len = utf8_lnw(&width, end, size-pos);
127    if (len && width) return end;
128  }
129  return 0;
130}
131
132struct double_list *dlist_add_before(struct double_list **head,
133  struct double_list **list, char *data)
134{
135  struct double_list *new = xmalloc(sizeof(struct double_list));
136  new->data = data;
137  if (*list == *head) *head = new;
138
139  dlist_add_nomalloc(list, new);
140  return new;
141}
142
143struct double_list *dlist_add_after(struct double_list **head,
144  struct double_list **list, char *data)
145{
146  struct double_list *new = xmalloc(sizeof(struct double_list));
147  new->data = data;
148
149  if (*list) {
150    new->prev = *list;
151    new->next = (*list)->next;
152    (*list)->next->prev = new;
153    (*list)->next = new;
154  } else *head = *list = new->next = new->prev = new;
155  return new;
156}
157
158// str must be already allocated
159// ownership of allocated data is moved
160// data, pre allocated data
161// offset, offset in whole text
162// size, data allocation size of given data
163// len, length of the string
164// type, define allocation type for cleanup purposes at app exit
165static int insert_str(const char *data, size_t offset, size_t size, size_t len,
166  enum alloc_flag type)
167{
168  struct mem_block *b = xmalloc(sizeof(struct mem_block));
169  struct slice *next = xmalloc(sizeof(struct slice));
170  struct slice_list *s = TT.slices;
171  b->size = size;
172  b->len = len;
173  b->alloc = type;
174  b->data = data;
175  next->len = len;
176  next->data = data;
177
178  //mem blocks can be just added unordered
179  TT.text = (struct block_list *)dlist_add((struct double_list **)&TT.text,
180    (char *)b);
181
182  if (!s) {
183    TT.slices = (struct slice_list *)dlist_add(
184      (struct double_list **)&TT.slices,
185      (char *)next);
186  } else {
187    size_t pos = 0;
188    //search insertation point for slice
189    do {
190      if (pos<=offset && pos+s->node->len>offset) break;
191      pos += s->node->len;
192      s = s->next;
193      if (s == TT.slices) return -1; //error out of bounds
194    } while (1);
195    //need to cut previous slice into 2 since insert is in middle
196    if (pos+s->node->len>offset && pos!=offset) {
197      struct slice *tail = xmalloc(sizeof(struct slice));
198      tail->len = s->node->len-(offset-pos);
199      tail->data = s->node->data+(offset-pos);
200      s->node->len = offset-pos;
201      //pos = offset;
202      s = (struct slice_list *)dlist_add_after(
203        (struct double_list **)&TT.slices,
204        (struct double_list **)&s,
205        (char *)tail);
206
207      s = (struct slice_list *)dlist_add_before(
208        (struct double_list **)&TT.slices,
209        (struct double_list **)&s,
210        (char *)next);
211    } else if (pos==offset) {
212      // insert before
213      s = (struct slice_list *)dlist_add_before(
214        (struct double_list **)&TT.slices,
215        (struct double_list **)&s,
216        (char *)next);
217    } else {
218      // insert after
219      s = (struct slice_list *)dlist_add_after((struct double_list **)&TT.slices,
220      (struct double_list **)&s,
221      (char *)next);
222    }
223  }
224  return 0;
225}
226
227// this will not free any memory
228// will only create more slices depending on position
229static int cut_str(size_t offset, size_t len)
230{
231  struct slice_list *e, *s = TT.slices;
232  size_t end = offset+len;
233  size_t epos, spos = 0;
234  if (!s) return -1;
235
236  //find start and end slices
237  for (;;) {
238    if (spos<=offset && spos+s->node->len>offset) break;
239    spos += s->node->len;
240    s = s->next;
241
242    if (s == TT.slices) return -1; //error out of bounds
243  }
244
245  for (e = s, epos = spos; ; ) {
246    if (epos<=end && epos+e->node->len>end) break;
247    epos += e->node->len;
248    e = e->next;
249
250    if (e == TT.slices) return -1; //error out of bounds
251  }
252
253  for (;;) {
254    if (spos == offset && ( end >= spos+s->node->len)) {
255      //cut full
256      spos += s->node->len;
257      offset += s->node->len;
258      s = dlist_pop(&s);
259      if (s == TT.slices) TT.slices = s->next;
260
261    } else if (spos < offset && ( end >= spos+s->node->len)) {
262      //cut end
263      size_t clip = s->node->len - (offset - spos);
264      offset = spos+s->node->len;
265      spos += s->node->len;
266      s->node->len -= clip;
267    } else if (spos == offset && s == e) {
268      //cut begin
269      size_t clip = end - offset;
270      s->node->len -= clip;
271      s->node->data += clip;
272      break;
273    } else {
274      //cut middle
275      struct slice *tail = xmalloc(sizeof(struct slice));
276      size_t clip = end-offset;
277      tail->len = s->node->len-(offset-spos)-clip;
278      tail->data = s->node->data+(offset-spos)+clip;
279      s->node->len = offset-spos; //wrong?
280      s = (struct slice_list *)dlist_add_after(
281        (struct double_list **)&TT.slices,
282        (struct double_list **)&s,
283        (char *)tail);
284      break;
285    }
286    if (s == e) break;
287
288    s = s->next;
289  }
290
291  return 0;
292}
293
294//find offset position in slices
295static struct slice_list *slice_offset(size_t *start, size_t offset)
296{
297  struct slice_list *s = TT.slices;
298  size_t spos = 0;
299
300  //find start
301  for ( ;s ; ) {
302    if (spos<=offset && spos+s->node->len>offset) break;
303
304    spos += s->node->len;
305    s = s->next;
306
307    if (s == TT.slices) s = 0; //error out of bounds
308  }
309  if (s) *start = spos;
310  return s;
311}
312
313static size_t text_strchr(size_t offset, char c)
314{
315  struct slice_list *s = TT.slices;
316  size_t epos, spos = 0;
317  int i = 0;
318
319  //find start
320  if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
321
322  i = offset-spos;
323  epos = spos+i;
324  do {
325    for (; i < s->node->len; i++, epos++)
326      if (s->node->data[i] == c) return epos;
327    s = s->next;
328    i = 0;
329  } while (s != TT.slices);
330
331  return SIZE_MAX;
332}
333
334static size_t text_strrchr(size_t offset, char c)
335{
336  struct slice_list *s = TT.slices;
337  size_t epos, spos = 0;
338  int i = 0;
339
340  //find start
341  if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
342
343  i = offset-spos;
344  epos = spos+i;
345  do {
346    for (; i >= 0; i--, epos--)
347      if (s->node->data[i] == c) return epos;
348    s = s->prev;
349    i = s->node->len-1;
350  } while (s != TT.slices->prev); //tail
351
352  return SIZE_MAX;
353}
354
355static size_t text_filesize()
356{
357  struct slice_list *s = TT.slices;
358  size_t pos = 0;
359  if (s) do {
360
361    pos += s->node->len;
362    s = s->next;
363
364  } while (s != TT.slices);
365
366  return pos;
367}
368
369static int text_count(size_t start, size_t end, char c)
370{
371  struct slice_list *s = TT.slices;
372  size_t i, count = 0, spos = 0;
373  if (!(s = slice_offset(&spos, start))) return 0;
374  i = start-spos;
375  if (s) do {
376    for (; i < s->node->len && spos+i<end; i++)
377      if (s->node->data[i] == c) count++;
378    if (spos+i>=end) return count;
379
380    spos += s->node->len;
381    i = 0;
382    s = s->next;
383
384  } while (s != TT.slices);
385
386  return count;
387}
388
389static char text_byte(size_t offset)
390{
391  struct slice_list *s = TT.slices;
392  size_t spos = 0;
393  //find start
394  if (!(s = slice_offset(&spos, offset))) return 0;
395  return s->node->data[offset-spos];
396}
397
398//utf-8 codepoint -1 if not valid, 0 if out_of_bounds, len if valid
399//copies data to dest if dest is not 0
400static int text_codepoint(char *dest, size_t offset)
401{
402  char scratch[8] = {0};
403  int state = 0, finished = 0;
404
405  for (;!(finished = utf8_dec(text_byte(offset), scratch, &state)); offset++)
406    if (!state) return -1;
407
408  if (!finished && !state) return -1;
409  if (dest) memcpy(dest, scratch, 8);
410
411  return strlen(scratch);
412}
413
414static size_t text_sol(size_t offset)
415{
416  size_t pos;
417  if (!TT.filesize || !offset) return 0;
418  else if (TT.filesize <= offset) return TT.filesize-1;
419  else if ((pos = text_strrchr(offset-1, '\n')) == SIZE_MAX) return 0;
420  else if (pos < offset) return pos+1;
421  return offset;
422}
423
424static size_t text_eol(size_t offset)
425{
426  if (!TT.filesize) offset = 1;
427  else if (TT.filesize <= offset) return TT.filesize-1;
428  else if ((offset = text_strchr(offset, '\n')) == SIZE_MAX)
429    return TT.filesize-1;
430  return offset;
431}
432
433static size_t text_nsol(size_t offset)
434{
435  offset = text_eol(offset);
436  if (text_byte(offset) == '\n') offset++;
437  if (offset >= TT.filesize) offset--;
438  return offset;
439}
440
441static size_t text_psol(size_t offset)
442{
443  offset = text_sol(offset);
444  if (offset) offset--;
445  if (offset && text_byte(offset-1) != '\n') offset = text_sol(offset-1);
446  return offset;
447}
448
449static size_t text_getline(char *dest, size_t offset, size_t max_len)
450{
451  struct slice_list *s = TT.slices;
452  size_t end, spos = 0;
453  int i, j = 0;
454
455  if (dest) *dest = 0;
456
457  if (!s) return 0;
458  if ((end = text_strchr(offset, '\n')) == SIZE_MAX)
459    if ((end = TT.filesize)  > offset+max_len) return 0;
460
461  //find start
462  if (!(s = slice_offset(&spos, offset))) return 0;
463
464  i = offset-spos;
465  j = end-offset+1;
466  if (dest) do {
467    for (; i < s->node->len && j; i++, j--, dest++)
468      *dest = s->node->data[i];
469    s = s->next;
470    i = 0;
471  } while (s != TT.slices && j);
472
473  if (dest) *dest = 0;
474
475  return end-offset;
476}
477
478//copying is needed when file has lot of inserts that are
479//just few char long, but not always. Advanced search should
480//check big slices directly and just copy edge cases.
481//Also this is only line based search multiline
482//and regexec should be done instead.
483static size_t text_strstr(size_t offset, char *str)
484{
485  size_t bytes, pos = offset;
486  char *s = 0;
487  do {
488    bytes = text_getline(toybuf, pos, ARRAY_LEN(toybuf));
489    if (!bytes) pos++; //empty line
490    else if ((s = strstr(toybuf, str))) return pos+(s-toybuf);
491    else pos += bytes;
492  } while (pos < TT.filesize);
493
494  return SIZE_MAX;
495}
496
497static void block_list_free(void *node)
498{
499  struct block_list *d = node;
500
501  if (d->node->alloc == HEAP) free((void *)d->node->data);
502  else if (d->node->alloc == MMAP) munmap((void *)d->node->data, d->node->size);
503
504  free(d->node);
505  free(d);
506}
507
508static void linelist_unload()
509{
510  llist_traverse((void *)TT.slices, llist_free_double);
511  llist_traverse((void *)TT.text, block_list_free);
512  TT.slices = 0, TT.text = 0;
513}
514
515static int linelist_load(char *filename)
516{
517  if (!filename) filename = (char*)*toys.optargs;
518
519  if (filename) {
520    int fd = open(filename, O_RDONLY);
521    long long size;
522
523    if (fd == -1 || !(size = fdlength(fd))) {
524      insert_str("", 0, 0, 0, STACK);
525      TT.filesize = 0;
526
527      return 0;
528    }
529    insert_str(xmmap(0, size, PROT_READ, MAP_SHARED, fd, 0), 0, size,size,MMAP);
530    xclose(fd);
531    TT.filesize = text_filesize();
532  }
533
534  return 1;
535}
536
537static void write_file(char *filename)
538{
539  struct slice_list *s = TT.slices;
540  struct stat st;
541  int fd = 0;
542  if (!s) return;
543
544  if (!filename) filename = (char*)*toys.optargs;
545
546  sprintf(toybuf, "%s.swp", filename);
547
548  if ( (fd = xopen(toybuf, O_WRONLY | O_CREAT | O_TRUNC)) <0) return;
549
550  do {
551    xwrite(fd, (void *)s->node->data, s->node->len );
552    s = s->next;
553  } while (s != TT.slices);
554
555  linelist_unload();
556
557  xclose(fd);
558  if (!stat(filename, &st)) chmod(toybuf, st.st_mode);
559  else chmod(toybuf, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
560  xrename(toybuf, filename);
561  linelist_load(filename);
562}
563
564//jump into valid offset index
565//and valid utf8 codepoint
566static void check_cursor_bounds()
567{
568  char buf[8] = {0};
569  int len, width = 0;
570  if (!TT.filesize) TT.cursor = 0;
571
572  for (;;) {
573    if (TT.cursor < 1) {
574      TT.cursor = 0;
575      return;
576    } else if (TT.cursor >= TT.filesize-1) {
577      TT.cursor = TT.filesize-1;
578      return;
579    }
580    if ((len = text_codepoint(buf, TT.cursor)) < 1) {
581      TT.cursor--; //we are not in valid data try jump over
582      continue;
583    }
584    if (utf8_lnw(&width, buf, len) && width) break;
585    else TT.cursor--; //combine char jump over
586  }
587}
588
589// TT.vi_mov_flag is used for special cases when certain move
590// acts differently depending is there DELETE/YANK or NOP
591// Also commands such as G does not default to count0=1
592// 0x1 = Command needs argument (f,F,r...)
593// 0x2 = Move 1 right on yank/delete/insert (e, $...)
594// 0x4 = yank/delete last line fully
595// 0x10000000 = redraw after cursor needed
596// 0x20000000 = full redraw needed
597// 0x40000000 = count0 not given
598// 0x80000000 = move was reverse
599
600//TODO rewrite the logic, difficulties counting lines
601//and with big files scroll should not rely in knowing
602//absoluteline numbers
603static void adjust_screen_buffer()
604{
605  size_t c, s;
606  TT.cur_row = 0, TT.scr_row = 0;
607  if (!TT.cursor) {
608    TT.screen = 0;
609    TT.vi_mov_flag = 0x20000000;
610    return;
611  } else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) {
612     //give up, file is big, do full redraw
613
614    TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
615    TT.vi_mov_flag = 0x20000000;
616    return;
617  }
618
619  s = text_count(0, TT.screen, '\n');
620  c = text_count(0, TT.cursor, '\n');
621  if (s >= c) {
622    TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
623    s = c;
624    TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
625  } else {
626    int distance = c-s+1;
627    if (distance > (int)TT.screen_height) {
628      int n, adj = distance-TT.screen_height;
629      TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
630      for (;adj; adj--, s++)
631        if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen)
632          TT.screen = n+1;
633    }
634  }
635
636  TT.scr_row = s;
637  TT.cur_row = c;
638
639}
640
641//TODO search yank buffer by register
642//TODO yanks could be separate slices so no need to copy data
643//now only supports default register
644static int vi_yank(char reg, size_t from, int flags)
645{
646  size_t start = from, end = TT.cursor;
647  char *str;
648
649  memset(TT.yank.data, 0, TT.yank.alloc);
650  if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
651  else TT.cursor = start; //yank moves cursor to left pos always?
652
653  if (TT.yank.alloc < end-from) {
654    size_t new_bounds = (1+end-from)/1024;
655    new_bounds += ((1+end-from)%1024) ? 1 : 0;
656    new_bounds *= 1024;
657    TT.yank.data = xrealloc(TT.yank.data, new_bounds);
658    TT.yank.alloc = new_bounds;
659  }
660
661  //this is naive copy
662  for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start);
663
664  *str = 0;
665
666  return 1;
667}
668
669static int vi_delete(char reg, size_t from, int flags)
670{
671  size_t start = from, end = TT.cursor;
672
673  vi_yank(reg, from, flags);
674
675  if (TT.vi_mov_flag&0x80000000)
676    start = TT.cursor, end = from;
677
678  //pre adjust cursor move one right until at next valid rune
679  if (TT.vi_mov_flag&2) {
680    //TODO
681  }
682  //do slice cut
683  cut_str(start, end-start);
684
685  //cursor is at start at after delete
686  TT.cursor = start;
687  TT.filesize = text_filesize();
688  //find line start by strrchr(/n) ++
689  //set cur_col with crunch_n_str maybe?
690  TT.vi_mov_flag |= 0x30000000;
691
692  return 1;
693}
694
695static int vi_change(char reg, size_t to, int flags)
696{
697  vi_delete(reg, to, flags);
698  TT.vi_mode = 2;
699  return 1;
700}
701
702static int cur_left(int count0, int count1, char *unused)
703{
704  int count = count0*count1;
705  TT.vi_mov_flag |= 0x80000000;
706  for (;count && TT.cursor; count--) {
707    TT.cursor--;
708    if (text_byte(TT.cursor) == '\n') TT.cursor++;
709    check_cursor_bounds();
710  }
711  return 1;
712}
713
714static int cur_right(int count0, int count1, char *unused)
715{
716  int count = count0*count1, len, width = 0;
717  char buf[8] = {0};
718
719  for (;count; count--) {
720    len = text_codepoint(buf, TT.cursor);
721
722    if (*buf == '\n') break;
723    else if (len > 0) TT.cursor += len;
724    else TT.cursor++;
725
726    for (;TT.cursor < TT.filesize;) {
727      if ((len = text_codepoint(buf, TT.cursor)) < 1) {
728        TT.cursor++; //we are not in valid data try jump over
729        continue;
730      }
731
732      if (utf8_lnw(&width, buf, len) && width) break;
733      else TT.cursor += len;
734    }
735  }
736  check_cursor_bounds();
737  return 1;
738}
739
740//TODO column shift
741static int cur_up(int count0, int count1, char *unused)
742{
743  int count = count0*count1;
744  for (;count--;) TT.cursor = text_psol(TT.cursor);
745
746  TT.vi_mov_flag |= 0x80000000;
747  check_cursor_bounds();
748  return 1;
749}
750
751//TODO column shift
752static int cur_down(int count0, int count1, char *unused)
753{
754  int count = count0*count1;
755  for (;count--;) TT.cursor = text_nsol(TT.cursor);
756  check_cursor_bounds();
757  return 1;
758}
759
760static int vi_H(int count0, int count1, char *unused)
761{
762  TT.cursor = text_sol(TT.screen);
763  return 1;
764}
765
766static int vi_L(int count0, int count1, char *unused)
767{
768  TT.cursor = text_sol(TT.screen);
769  cur_down(TT.screen_height-1, 1, 0);
770  return 1;
771}
772
773static int vi_M(int count0, int count1, char *unused)
774{
775  TT.cursor = text_sol(TT.screen);
776  cur_down(TT.screen_height/2, 1, 0);
777  return 1;
778}
779
780static int search_str(char *s)
781{
782  size_t pos = text_strstr(TT.cursor+1, s);
783
784  if (TT.last_search != s) {
785    free(TT.last_search);
786    TT.last_search = xstrdup(s);
787  }
788
789  if (pos != SIZE_MAX) TT.cursor = pos;
790  check_cursor_bounds();
791  return 0;
792}
793
794static int vi_yy(char reg, int count0, int count1)
795{
796  size_t history = TT.cursor;
797  size_t pos = text_sol(TT.cursor); //go left to first char on line
798  TT.vi_mov_flag |= 0x4;
799
800  for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
801
802  vi_yank(reg, pos, 0);
803
804  TT.cursor = history;
805  return 1;
806}
807
808static int vi_dd(char reg, int count0, int count1)
809{
810  size_t pos = text_sol(TT.cursor); //go left to first char on line
811  TT.vi_mov_flag |= 0x30000000;
812
813  for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
814
815  if (pos == TT.cursor && TT.filesize) pos--;
816  vi_delete(reg, pos, 0);
817  check_cursor_bounds();
818  return 1;
819}
820
821static int vi_x(char reg, int count0, int count1)
822{
823  size_t from = TT.cursor;
824
825  if (text_byte(TT.cursor) == '\n') {
826    cur_left(count0-1, 1, 0);
827  }
828  else {
829    cur_right(count0-1, 1, 0);
830    if (text_byte(TT.cursor) == '\n') TT.vi_mov_flag |= 2;
831    else cur_right(1, 1, 0);
832  }
833
834  vi_delete(reg, from, 0);
835  check_cursor_bounds();
836  return 1;
837}
838
839static int vi_movw(int count0, int count1, char *unused)
840{
841  int count = count0*count1;
842  while (count--) {
843    char c = text_byte(TT.cursor);
844    do {
845      if (TT.cursor > TT.filesize-1) break;
846      //if at empty jump to non empty
847      if (c == '\n') {
848        if (++TT.cursor > TT.filesize-1) break;
849        if ((c = text_byte(TT.cursor)) == '\n') break;
850        continue;
851      } else if (strchr(blank, c)) do {
852        if (++TT.cursor > TT.filesize-1) break;
853        c = text_byte(TT.cursor);
854      } while (strchr(blank, c));
855      //if at special jump to non special
856      else if (strchr(specials, c)) do {
857        if (++TT.cursor > TT.filesize-1) break;
858        c = text_byte(TT.cursor);
859      } while (strchr(specials, c));
860      //else jump to empty or spesial
861      else do {
862        if (++TT.cursor > TT.filesize-1) break;
863        c = text_byte(TT.cursor);
864      } while (c && !strchr(blank, c) && !strchr(specials, c));
865
866    } while (strchr(blank, c) && c != '\n'); //never stop at empty
867  }
868  check_cursor_bounds();
869  return 1;
870}
871
872static int vi_movb(int count0, int count1, char *unused)
873{
874  int count = count0*count1;
875  int type = 0;
876  char c;
877  while (count--) {
878    c = text_byte(TT.cursor);
879    do {
880      if (!TT.cursor) break;
881      //if at empty jump to non empty
882      if (strchr(blank, c)) do {
883        if (!--TT.cursor) break;
884        c = text_byte(TT.cursor);
885      } while (strchr(blank, c));
886      //if at special jump to non special
887      else if (strchr(specials, c)) do {
888        if (!--TT.cursor) break;
889        type = 0;
890        c = text_byte(TT.cursor);
891      } while (strchr(specials, c));
892      //else jump to empty or spesial
893      else do {
894        if (!--TT.cursor) break;
895        type = 1;
896        c = text_byte(TT.cursor);
897      } while (!strchr(blank, c) && !strchr(specials, c));
898
899    } while (strchr(blank, c)); //never stop at empty
900  }
901  //find first
902  for (;TT.cursor; TT.cursor--) {
903    c = text_byte(TT.cursor-1);
904    if (type && !strchr(blank, c) && !strchr(specials, c)) break;
905    else if (!type && !strchr(specials, c)) break;
906  }
907
908  TT.vi_mov_flag |= 0x80000000;
909  check_cursor_bounds();
910  return 1;
911}
912
913static int vi_move(int count0, int count1, char *unused)
914{
915  int count = count0*count1;
916  int type = 0;
917  char c;
918
919  if (count>1) vi_movw(count-1, 1, unused);
920
921  c = text_byte(TT.cursor);
922  if (strchr(specials, c)) type = 1;
923  TT.cursor++;
924  for (;TT.cursor < TT.filesize-1; TT.cursor++) {
925    c = text_byte(TT.cursor+1);
926    if (!type && (strchr(blank, c) || strchr(specials, c))) break;
927    else if (type && !strchr(specials, c)) break;
928  }
929
930  TT.vi_mov_flag |= 2;
931  check_cursor_bounds();
932  return 1;
933}
934
935
936static void i_insert(char *str, int len)
937{
938  if (!str || !len) return;
939
940  insert_str(xstrdup(str), TT.cursor, len, len, HEAP);
941  TT.cursor += len;
942  TT.filesize = text_filesize();
943  TT.vi_mov_flag |= 0x30000000;
944}
945
946static int vi_zero(int count0, int count1, char *unused)
947{
948  TT.cursor = text_sol(TT.cursor);
949  TT.cur_col = 0;
950  TT.vi_mov_flag |= 0x80000000;
951  return 1;
952}
953
954static int vi_dollar(int count0, int count1, char *unused)
955{
956  size_t new = text_strchr(TT.cursor, '\n');
957
958  if (new != TT.cursor) {
959    TT.cursor = new - 1;
960    TT.vi_mov_flag |= 2;
961    check_cursor_bounds();
962  }
963  return 1;
964}
965
966static void vi_eol()
967{
968  TT.cursor = text_strchr(TT.cursor, '\n');
969  check_cursor_bounds();
970}
971
972static void ctrl_b()
973{
974  int i;
975
976  for (i=0; i<TT.screen_height-2; ++i) {
977    TT.screen = text_psol(TT.screen);
978    // TODO: retain x offset.
979    TT.cursor = text_psol(TT.screen);
980  }
981}
982
983static void ctrl_f()
984{
985  int i;
986
987  for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
988  // TODO: real vi keeps the x position.
989  if (TT.screen > TT.cursor) TT.cursor = TT.screen;
990}
991
992static void ctrl_e()
993{
994  TT.screen = text_nsol(TT.screen);
995  // TODO: real vi keeps the x position.
996  if (TT.screen > TT.cursor) TT.cursor = TT.screen;
997}
998
999static void ctrl_y()
1000{
1001  TT.screen = text_psol(TT.screen);
1002  // TODO: only if we're on the bottom line
1003  TT.cursor = text_psol(TT.cursor);
1004  // TODO: real vi keeps the x position.
1005}
1006
1007//TODO check register where to push from
1008static int vi_push(char reg, int count0, int count1)
1009{
1010  //if row changes during push original cursor position is kept
1011  //vi inconsistancy
1012  //if yank ends with \n push is linemode else push in place+1
1013  size_t history = TT.cursor;
1014  char *start = TT.yank.data;
1015  char *eol = strchr(start, '\n');
1016
1017  if (start[strlen(start)-1] == '\n') {
1018    if ((TT.cursor = text_strchr(TT.cursor, '\n')) == SIZE_MAX)
1019      TT.cursor = TT.filesize;
1020    else TT.cursor = text_nsol(TT.cursor);
1021  } else cur_right(1, 1, 0);
1022
1023  i_insert(start, strlen(start));
1024  if (eol) {
1025    TT.vi_mov_flag |= 0x10000000;
1026    TT.cursor = history;
1027  }
1028
1029  return 1;
1030}
1031
1032static int vi_find_c(int count0, int count1, char *symbol)
1033{
1034////  int count = count0*count1;
1035  size_t pos = text_strchr(TT.cursor, *symbol);
1036  if (pos != SIZE_MAX) TT.cursor = pos;
1037  return 1;
1038}
1039
1040static int vi_find_cb(int count0, int count1, char *symbol)
1041{
1042  //do backward search
1043  size_t pos = text_strrchr(TT.cursor, *symbol);
1044  if (pos != SIZE_MAX) TT.cursor = pos;
1045  return 1;
1046}
1047
1048//if count is not spesified should go to last line
1049static int vi_go(int count0, int count1, char *symbol)
1050{
1051  size_t prev_cursor = TT.cursor;
1052  int count = count0*count1-1;
1053  TT.cursor = 0;
1054
1055  if (TT.vi_mov_flag&0x40000000 && (TT.cursor = TT.filesize) > 0)
1056    TT.cursor = text_sol(TT.cursor-1);
1057  else if (count) {
1058    size_t next = 0;
1059    for ( ;count && (next = text_strchr(next+1, '\n')) != SIZE_MAX; count--)
1060      TT.cursor = next;
1061    TT.cursor++;
1062  }
1063
1064  check_cursor_bounds();  //adjusts cursor column
1065  if (prev_cursor > TT.cursor) TT.vi_mov_flag |= 0x80000000;
1066
1067  return 1;
1068}
1069
1070static int vi_o(char reg, int count0, int count1)
1071{
1072  TT.cursor = text_eol(TT.cursor);
1073  insert_str(xstrdup("\n"), TT.cursor++, 1, 1, HEAP);
1074  TT.vi_mov_flag |= 0x30000000;
1075  TT.vi_mode = 2;
1076  return 1;
1077}
1078
1079static int vi_O(char reg, int count0, int count1)
1080{
1081  TT.cursor = text_psol(TT.cursor);
1082  return vi_o(reg, count0, count1);
1083}
1084
1085static int vi_D(char reg, int count0, int count1)
1086{
1087  size_t pos = TT.cursor;
1088  if (!count0) return 1;
1089  vi_eol();
1090  vi_delete(reg, pos, 0);
1091  if (--count0) vi_dd(reg, count0, 1);
1092
1093  check_cursor_bounds();
1094  return 1;
1095}
1096
1097static int vi_I(char reg, int count0, int count1)
1098{
1099  TT.cursor = text_sol(TT.cursor);
1100  TT.vi_mode = 2;
1101  return 1;
1102}
1103
1104static int vi_join(char reg, int count0, int count1)
1105{
1106  size_t next;
1107  while (count0--) {
1108    //just strchr(/n) and cut_str(pos, 1);
1109    if ((next = text_strchr(TT.cursor, '\n')) == SIZE_MAX) break;
1110    TT.cursor = next+1;
1111    vi_delete(reg, TT.cursor-1, 0);
1112  }
1113  return 1;
1114}
1115
1116static int vi_find_next(char reg, int count0, int count1)
1117{
1118  if (TT.last_search) search_str(TT.last_search);
1119  return 1;
1120}
1121
1122//NOTES
1123//vi-mode cmd syntax is
1124//("[REG])[COUNT0]CMD[COUNT1](MOV)
1125//where:
1126//-------------------------------------------------------------
1127//"[REG] is optional buffer where deleted/yanked text goes REG can be
1128//  atleast 0-9, a-z or default "
1129//[COUNT] is optional multiplier for cmd execution if there is 2 COUNT
1130//  operations they are multiplied together
1131//CMD is operation to be executed
1132//(MOV) is movement operation, some CMD does not require MOV and some
1133//  have special cases such as dd, yy, also movements can work without
1134//  CMD
1135//ex commands can be even more complicated than this....
1136//
1137struct vi_cmd_param {
1138  const char* cmd;
1139  unsigned flags;
1140  int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS
1141};
1142struct vi_mov_param {
1143  const char* mov;
1144  unsigned flags;
1145  int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params
1146};
1147//special cases without MOV and such
1148struct vi_special_param {
1149  const char *cmd;
1150  int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1
1151};
1152struct vi_special_param vi_special[] =
1153{
1154  {"D", &vi_D},
1155  {"I", &vi_I},
1156  {"J", &vi_join},
1157  {"O", &vi_O},
1158  {"n", &vi_find_next},
1159  {"o", &vi_o},
1160  {"p", &vi_push},
1161  {"x", &vi_x},
1162  {"dd", &vi_dd},
1163  {"yy", &vi_yy},
1164};
1165//there is around ~47 vi moves
1166//some of them need extra params
1167//such as f and '
1168struct vi_mov_param vi_movs[] =
1169{
1170  {"0", 0, &vi_zero},
1171  {"b", 0, &vi_movb},
1172  {"e", 0, &vi_move},
1173  {"G", 0, &vi_go},
1174  {"H", 0, &vi_H},
1175  {"h", 0, &cur_left},
1176  {"j", 0, &cur_down},
1177  {"k", 0, &cur_up},
1178  {"L", 0, &vi_L},
1179  {"l", 0, &cur_right},
1180  {"M", 0, &vi_M},
1181  {"w", 0, &vi_movw},
1182  {"$", 0, &vi_dollar},
1183  {"f", 1, &vi_find_c},
1184  {"F", 1, &vi_find_cb},
1185};
1186//change and delete unfortunately behave different depending on move command,
1187//such as ce cw are same, but dw and de are not...
1188//also dw stops at w position and cw seem to stop at e pos+1...
1189//so after movement we need to possibly set up some flags before executing
1190//command, and command needs to adjust...
1191struct vi_cmd_param vi_cmds[] =
1192{
1193  {"c", 1, &vi_change},
1194  {"d", 1, &vi_delete},
1195  {"y", 1, &vi_yank},
1196};
1197
1198static int run_vi_cmd(char *cmd)
1199{
1200  int i = 0, val = 0;
1201  char *cmd_e;
1202  int (*vi_cmd)(char, size_t, int) = 0;
1203  int (*vi_mov)(int, int, char*) = 0;
1204
1205  TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0;
1206  TT.vi_reg = '"';
1207
1208  if (*cmd == '"') {
1209    cmd++;
1210    TT.vi_reg = *cmd; //TODO check validity
1211    cmd++;
1212  }
1213  errno = 0;
1214  val = strtol(cmd, &cmd_e, 10);
1215  if (errno || val == 0) val = 1, TT.vi_mov_flag |= 0x40000000;
1216  else cmd = cmd_e;
1217  TT.count0 = val;
1218
1219  for (i = 0; i < ARRAY_LEN(vi_special); i++) {
1220    if (strstr(cmd, vi_special[i].cmd)) {
1221      return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1);
1222    }
1223  }
1224
1225  for (i = 0; i < ARRAY_LEN(vi_cmds); i++) {
1226    if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) {
1227      vi_cmd = vi_cmds[i].vi_cmd;
1228      cmd += strlen(vi_cmds[i].cmd);
1229      break;
1230    }
1231  }
1232  errno = 0;
1233  val = strtol(cmd, &cmd_e, 10);
1234  if (errno || val == 0) val = 1;
1235  else cmd = cmd_e;
1236  TT.count1 = val;
1237
1238  for (i = 0; i < ARRAY_LEN(vi_movs); i++) {
1239    if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) {
1240      vi_mov = vi_movs[i].vi_mov;
1241      TT.vi_mov_flag |= vi_movs[i].flags;
1242      cmd++;
1243      if (TT.vi_mov_flag&1 && !(*cmd)) return 0;
1244      break;
1245    }
1246  }
1247  if (vi_mov) {
1248    int prev_cursor = TT.cursor;
1249    if (vi_mov(TT.count0, TT.count1, cmd)) {
1250      if (vi_cmd) return (vi_cmd(TT.vi_reg, prev_cursor, TT.vi_mov_flag));
1251      else return 1;
1252    } else return 0; //return some error
1253  }
1254  return 0;
1255}
1256
1257
1258static int run_ex_cmd(char *cmd)
1259{
1260  if (cmd[0] == '/') {
1261    search_str(&cmd[1]);
1262  } else if (cmd[0] == '?') {
1263    // TODO: backwards search.
1264  } else if (cmd[0] == ':') {
1265    if (!strcmp(&cmd[1], "q") || !strcmp(&cmd[1], "q!")) {
1266      // TODO: if no !, check whether file modified.
1267      //exit_application;
1268      return -1;
1269    }
1270    else if (strstr(&cmd[1], "wq")) {
1271      write_file(0);
1272      return -1;
1273    }
1274    else if (strstr(&cmd[1], "w")) {
1275      write_file(0);
1276      return 1;
1277    }
1278    else if (strstr(&cmd[1], "set list")) {
1279      TT.list = 1;
1280      TT.vi_mov_flag |= 0x30000000;
1281      return 1;
1282    }
1283    else if (strstr(&cmd[1], "set nolist")) {
1284      TT.list = 0;
1285      TT.vi_mov_flag |= 0x30000000;
1286      return 1;
1287    }
1288  }
1289  return 0;
1290
1291}
1292
1293static int vi_crunch(FILE *out, int cols, int wc)
1294{
1295  int ret = 0;
1296  if (wc < 32 && TT.list) {
1297    tty_esc("1m");
1298    ret = crunch_escape(out,cols,wc);
1299    tty_esc("m");
1300  } else if (wc == 0x09) {
1301    if (out) {
1302      int i = TT.tabstop;
1303      for (;i--;) fputs(" ", out);
1304    }
1305    ret = TT.tabstop;
1306  } else if (wc == '\n') return 0;
1307  return ret;
1308}
1309
1310//crunch_str with n bytes restriction for printing substrings or
1311//non null terminated strings
1312static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
1313  int (*escout)(FILE *out, int cols, int wc))
1314{
1315  int columns = 0, col, bytes;
1316  char *start, *end;
1317  unsigned wc;
1318
1319  for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
1320    if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
1321      if (!escmore || wc>255 || !strchr(escmore, wc)) {
1322        if (width-columns<col) break;
1323        if (out) fwrite(end, bytes, 1, out);
1324
1325        continue;
1326      }
1327    }
1328
1329    if (bytes<1) {
1330      bytes = 1;
1331      wc = *end;
1332    }
1333    col = width-columns;
1334    if (col<1) break;
1335    if (escout) {
1336      if ((col = escout(out, col, wc))<0) break;
1337    } else if (out) fwrite(end, 1, bytes, out);
1338  }
1339  *str = end;
1340
1341  return columns;
1342}
1343
1344static void draw_page()
1345{
1346  unsigned y = 0;
1347  int x = 0;
1348
1349  char *line = 0, *end = 0;
1350  int bytes = 0;
1351
1352  //screen coordinates for cursor
1353  int cy_scr = 0, cx_scr = 0;
1354
1355  //variables used only for cursor handling
1356  int aw = 0, iw = 0, clip = 0, margin = 8;
1357
1358  int scroll = 0, redraw = 0;
1359
1360  int SSOL, SOL;
1361
1362
1363  adjust_screen_buffer();
1364  //redraw = 3; //force full redraw
1365  redraw = (TT.vi_mov_flag & 0x30000000)>>28;
1366
1367  scroll = TT.drawn_row-TT.scr_row;
1368  if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
1369  else if (abs(scroll)>TT.screen_height/2) redraw = 3;
1370
1371  tty_jump(0, 0);
1372  if (redraw&2) tty_esc("2J"), tty_esc("H");   //clear screen
1373  else if (scroll>0) printf("\033[%dL", scroll);  //scroll up
1374  else if (scroll<0) printf("\033[%dM", -scroll); //scroll down
1375
1376  SOL = text_sol(TT.cursor);
1377  bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
1378  line = toybuf;
1379
1380  for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL);
1381
1382  cy_scr = y;
1383
1384  //draw cursor row
1385  /////////////////////////////////////////////////////////////
1386  //for long lines line starts to scroll when cursor hits margin
1387  bytes = TT.cursor-SOL; // TT.cur_col;
1388  end = line;
1389
1390
1391  tty_jump(0, y);
1392  tty_esc("2K");
1393  //find cursor position
1394  aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
1395
1396  //if we need to render text that is not inserted to buffer yet
1397  if (TT.vi_mode == 2 && TT.il->len) {
1398    char* iend = TT.il->data; //input end
1399    x = 0;
1400    //find insert end position
1401    iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
1402    clip = (aw+iw) - TT.screen_width+margin;
1403
1404    //if clipped area is bigger than text before insert
1405    if (clip > aw) {
1406      clip -= aw;
1407      iend = TT.il->data;
1408
1409      iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
1410      x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
1411    } else {
1412      iend = TT.il->data;
1413      end = line;
1414
1415      //if clipped area is substring from cursor row start
1416      aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
1417      x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
1418      x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
1419    }
1420  }
1421  //when not inserting but still need to keep cursor inside screen
1422  //margin area
1423  else if ( aw+margin > TT.screen_width) {
1424    clip = aw-TT.screen_width+margin;
1425    end = line;
1426    aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
1427    x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
1428  }
1429  else {
1430    end = line;
1431    x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
1432  }
1433  cx_scr = x;
1434  cy_scr = y;
1435  x += crunch_str(&end, TT.screen_width-x,  stdout, "\t\n", vi_crunch);
1436
1437  //start drawing all other rows that needs update
1438  ///////////////////////////////////////////////////////////////////
1439  y = 0, SSOL = TT.screen, line = toybuf;
1440  bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
1441
1442  //if we moved around in long line might need to redraw everything
1443  if (clip != TT.drawn_col) redraw = 3;
1444
1445  for (; y < TT.screen_height; y++ ) {
1446    int draw_line = 0;
1447    if (SSOL == SOL) {
1448      line = toybuf;
1449      SSOL += bytes+1;
1450      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
1451      continue;
1452    } else if (redraw) draw_line++;
1453    else if (scroll<0 && TT.screen_height-y-1<-scroll)
1454      scroll++, draw_line++;
1455    else if (scroll>0) scroll--, draw_line++;
1456
1457    tty_jump(0, y);
1458    if (draw_line) {
1459      tty_esc("2K");
1460      if (line && strlen(line)) {
1461        aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
1462        crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
1463        if ( *line ) printf("@");
1464      } else printf("\033[2m~\033[m");
1465    }
1466    if (SSOL+bytes < TT.filesize)  {
1467      line = toybuf;
1468      SSOL += bytes+1;
1469      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
1470   } else line = 0;
1471  }
1472
1473  TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
1474
1475  // Finished updating visual area, show status line.
1476  tty_jump(0, TT.screen_height);
1477  tty_esc("2K");
1478  if (TT.vi_mode == 2) printf("\033[1m-- INSERT --\033[m");
1479  if (!TT.vi_mode) {
1480    cx_scr = printf("%s", TT.il->data);
1481    cy_scr = TT.screen_height;
1482    *toybuf = 0;
1483  } else {
1484    // TODO: the row,col display doesn't show the cursor column
1485    // TODO: real vi shows the percentage by lines, not bytes
1486    sprintf(toybuf, "%zu/%zuC  %zu%%  %d,%d", TT.cursor, TT.filesize,
1487      (100*TT.cursor)/(TT.filesize ? : 1), TT.cur_row+1, TT.cur_col+1);
1488    if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
1489  }
1490  tty_jump(TT.screen_width-strlen(toybuf), TT.screen_height);
1491  printf("%s", toybuf);
1492
1493  tty_jump(cx_scr, cy_scr);
1494  xflush(1);
1495}
1496
1497void vi_main(void)
1498{
1499  char stdout_buf[8192];
1500  char keybuf[16] = {0};
1501  char vi_buf[16] = {0};
1502  char utf8_code[8] = {0};
1503  int utf8_dec_p = 0, vi_buf_pos = 0;
1504  FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
1505
1506  TT.il = xzalloc(sizeof(struct str_line));
1507  TT.il->data = xzalloc(80);
1508  TT.yank.data = xzalloc(128);
1509
1510  TT.il->alloc = 80, TT.yank.alloc = 128;
1511
1512  linelist_load(0);
1513
1514  TT.vi_mov_flag = 0x20000000;
1515  TT.vi_mode = 1, TT.tabstop = 8;
1516
1517  TT.screen_width = 80, TT.screen_height = 24;
1518  terminal_size(&TT.screen_width, &TT.screen_height);
1519  TT.screen_height -= 1;
1520
1521  // Avoid flicker.
1522  setbuffer(stdout, stdout_buf, sizeof(stdout_buf));
1523
1524  xsignal(SIGWINCH, generic_signal);
1525  set_terminal(0, 1, 0, 0);
1526  //writes stdout into different xterm buffer so when we exit
1527  //we dont get scroll log full of junk
1528  tty_esc("?1049h");
1529
1530  for (;;) {
1531    int key = 0;
1532
1533    draw_page();
1534    if (script) {
1535      key = fgetc(script);
1536      if (key == EOF) {
1537        fclose(script);
1538        script = 0;
1539        key = scan_key(keybuf, -1);
1540      }
1541    } else key = scan_key(keybuf, -1);
1542
1543    if (key == -1) goto cleanup_vi;
1544    else if (key == -3) {
1545      toys.signal = 0;
1546      terminal_size(&TT.screen_width, &TT.screen_height);
1547      TT.screen_height -= 1; //TODO this is hack fix visual alignment
1548      continue;
1549    }
1550
1551    // TODO: support cursor keys in ex mode too.
1552    if (TT.vi_mode && key>=256) {
1553      key -= 256;
1554      if (key==KEY_UP) cur_up(1, 1, 0);
1555      else if (key==KEY_DOWN) cur_down(1, 1, 0);
1556      else if (key==KEY_LEFT) cur_left(1, 1, 0);
1557      else if (key==KEY_RIGHT) cur_right(1, 1, 0);
1558      else if (key==KEY_HOME) vi_zero(1, 1, 0);
1559      else if (key==KEY_END) vi_dollar(1, 1, 0);
1560      else if (key==KEY_PGDN) ctrl_f();
1561      else if (key==KEY_PGUP) ctrl_b();
1562      continue;
1563    }
1564
1565    if (TT.vi_mode == 1) { //NORMAL
1566      switch (key) {
1567        case '/':
1568        case '?':
1569        case ':':
1570          TT.vi_mode = 0;
1571          TT.il->data[0]=key;
1572          TT.il->len++;
1573          break;
1574        case 'A':
1575          vi_eol();
1576          TT.vi_mode = 2;
1577          break;
1578        case 'a':
1579          cur_right(1, 1, 0);
1580          // FALLTHROUGH
1581        case 'i':
1582          TT.vi_mode = 2;
1583          break;
1584        case 'B'-'@':
1585          ctrl_b();
1586          break;
1587        case 'E'-'@':
1588          ctrl_e();
1589          break;
1590        case 'F'-'@':
1591          ctrl_f();
1592          break;
1593        case 'Y'-'@':
1594          ctrl_y();
1595          break;
1596        case 27:
1597          vi_buf[0] = 0;
1598          vi_buf_pos = 0;
1599          break;
1600        default:
1601          if (key > 0x20 && key < 0x7B) {
1602            vi_buf[vi_buf_pos] = key;//TODO handle input better
1603            vi_buf_pos++;
1604            if (run_vi_cmd(vi_buf)) {
1605              memset(vi_buf, 0, 16);
1606              vi_buf_pos = 0;
1607            }
1608            else if (vi_buf_pos == 16) {
1609              vi_buf_pos = 0;
1610              memset(vi_buf, 0, 16);
1611            }
1612
1613          }
1614
1615          break;
1616      }
1617    } else if (TT.vi_mode == 0) { //EX MODE
1618      switch (key) {
1619        case 0x7F:
1620        case 0x08:
1621          if (TT.il->len > 1) {
1622            TT.il->data[--TT.il->len] = 0;
1623            break;
1624          }
1625          // FALLTHROUGH
1626        case 27:
1627          TT.vi_mode = 1;
1628          TT.il->len = 0;
1629          memset(TT.il->data, 0, TT.il->alloc);
1630          break;
1631        case 0x0A:
1632        case 0x0D:
1633          if (run_ex_cmd(TT.il->data) == -1)
1634            goto cleanup_vi;
1635          TT.vi_mode = 1;
1636          TT.il->len = 0;
1637          memset(TT.il->data, 0, TT.il->alloc);
1638          break;
1639        default: //add chars to ex command until ENTER
1640          if (key >= 0x20 && key < 0x7F) { //might be utf?
1641            if (TT.il->len == TT.il->alloc) {
1642              TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
1643              TT.il->alloc *= 2;
1644            }
1645            TT.il->data[TT.il->len] = key;
1646            TT.il->len++;
1647          }
1648          break;
1649      }
1650    } else if (TT.vi_mode == 2) {//INSERT MODE
1651      switch (key) {
1652        case 27:
1653          i_insert(TT.il->data, TT.il->len);
1654          cur_left(1, 1, 0);
1655          TT.vi_mode = 1;
1656          TT.il->len = 0;
1657          memset(TT.il->data, 0, TT.il->alloc);
1658          break;
1659        case 0x7F:
1660        case 0x08:
1661          if (TT.il->len) {
1662            char *last = utf8_last(TT.il->data, TT.il->len);
1663            int shrink = strlen(last);
1664            memset(last, 0, shrink);
1665            TT.il->len -= shrink;
1666          }
1667          break;
1668        case 0x0A:
1669        case 0x0D:
1670          //insert newline
1671          //
1672          TT.il->data[TT.il->len++] = '\n';
1673          i_insert(TT.il->data, TT.il->len);
1674          TT.il->len = 0;
1675          memset(TT.il->data, 0, TT.il->alloc);
1676          break;
1677        default:
1678          if ((key >= 0x20 || key == 0x09) &&
1679              utf8_dec(key, utf8_code, &utf8_dec_p)) {
1680
1681            if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) {
1682              TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
1683              TT.il->alloc *= 2;
1684            }
1685            strcpy(TT.il->data+TT.il->len, utf8_code);
1686            TT.il->len += utf8_dec_p;
1687            utf8_dec_p = 0;
1688            *utf8_code = 0;
1689
1690          }
1691          break;
1692      }
1693    }
1694  }
1695cleanup_vi:
1696  linelist_unload();
1697  free(TT.il->data), free(TT.il), free(TT.yank.data);
1698  tty_reset();
1699  tty_esc("?1049l");
1700}
1701