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