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