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