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