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