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, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
9 
10 config VI
11   bool "vi"
12   default n
13   help
14     usage: vi FILE
15     Visual text editor. Predates the existence of standardized cursor keys,
16     so the controls are weird and historical.
17 */
18 
19 #define FOR_vi
20 #include "toys.h"
21 
22 GLOBALS(
23     int cur_col;
24     int cur_row;
25     unsigned screen_height;
26     unsigned screen_width;
27     int vi_mode;
28 )
29 
30 /*
31  *
32  * TODO:
33  * BUGS:  screen pos adjust does not cover "widelines"
34  *
35  *
36  * REFACTOR:  use dllist functions where possible.
37  *            draw_page dont draw full page at time if nothing changed...
38  *            ex callbacks
39  *
40  * FEATURE:   ex: / ? %   //atleast easy cases
41  *            vi: x dw d$ d0
42  *            vi: yw yy (y0 y$)
43  *            vi+ex: gg G //line movements
44  *            ex: r
45  *            ex: !external programs
46  *            ex: w filename //only writes to same file now
47  *            big file support?
48  */
49 
50 
51 struct linestack_show {
52   struct linestack_show *next;
53   long top, left;
54   int x, width, y, height;
55 };
56 
57 static void draw_page();
58 static int draw_str_until(int *drawn, char *str, int width, int bytes);
59 static void draw_char(char c, int x, int y, int highlight);
60 //utf8 support
61 static int utf8_lnw(int* width, char* str, int bytes);
62 static int utf8_dec(char key, char *utf8_scratch, int *sta_p) ;
63 static int utf8_len(char *str);
64 static int utf8_width(char *str, int bytes);
65 static int draw_rune(char *c, int x, int y, int highlight);
66 
67 
68 static void cur_left();
69 static void cur_right();
70 static void cur_up();
71 static void cur_down();
72 static void check_cursor_bounds();
73 static void adjust_screen_buffer();
74 
75 
76 struct str_line {
77   int alloc_len;
78   int str_len;
79   char *str_data;
80 };
81 
82 //lib dllist uses next and prev kinda opposite what im used to so I just
83 //renamed both ends to up and down
84 struct linelist {
85   struct linelist *up;//next
86   struct linelist *down;//prev
87   struct str_line *line;
88 };
89 //inserted line not yet pushed to buffer
90 struct str_line *il;
91 struct linelist *text; //file loaded into buffer
92 struct linelist *scr_r;//current screen coord 0 row
93 struct linelist *c_r;//cursor position row
94 int modified;
95 
dlist_insert_nomalloc(struct double_list ** list,struct double_list * new)96 void dlist_insert_nomalloc(struct double_list **list, struct double_list *new)
97 {
98   if (*list) {
99     new->next = *list;
100     new->prev = (*list)->prev;
101     if ((*list)->prev) (*list)->prev->next = new;
102     (*list)->prev = new;
103   } else *list = new->next = new->prev = new;
104 }
105 
106 
107 // Add an entry to the end of a doubly linked list
dlist_insert(struct double_list ** list,char * data)108 struct double_list *dlist_insert(struct double_list **list, char *data)
109 {
110   struct double_list *new = xmalloc(sizeof(struct double_list));
111   new->data = data;
112   dlist_insert_nomalloc(list, new);
113 
114   return new;
115 }
linelist_unload()116 void linelist_unload()
117 {
118 
119 }
120 
write_file(char * filename)121 void write_file(char *filename)
122 {
123   struct linelist *lst = text;
124   FILE *fp = 0;
125   if (!filename)
126     filename = (char*)*toys.optargs;
127   fp = fopen(filename, "w");
128   if (!fp) return ;
129   while (lst) {
130     fprintf(fp, "%s\n", lst->line->str_data);
131     lst = lst->down;
132   }
133   fclose(fp);
134 }
135 
linelist_load(char * filename)136 int linelist_load(char *filename)
137 {
138   struct linelist *lst = c_r;//cursor position or 0
139   FILE *fp = 0;
140   if (!filename)
141     filename = (char*)*toys.optargs;
142 
143   fp = fopen(filename, "r");
144   if (!fp) {
145     char *line = xzalloc(80);
146     ssize_t alc = 80;
147     lst = (struct linelist*)dlist_add((struct double_list**)&lst,
148         xzalloc(sizeof(struct str_line)));
149     lst->line->alloc_len = alc;
150     lst->line->str_len = 0;
151     lst->line->str_data = line;
152     text = lst;
153     dlist_terminate(text->up);
154     return 1;
155   }
156 
157   for (;;) {
158     char *line = xzalloc(80);
159     ssize_t alc = 80;
160     ssize_t len;
161     if ((len = getline(&line, (void *)&alc, fp)) == -1) {
162       if (errno == EINVAL || errno == ENOMEM) {
163         printf("error %d\n", errno);
164       }
165       free(line);
166       break;
167     }
168     lst = (struct linelist*)dlist_add((struct double_list**)&lst,
169         xzalloc(sizeof(struct str_line)));
170     lst->line->alloc_len = alc;
171     lst->line->str_len = len;
172     lst->line->str_data = line;
173 
174     if (lst->line->str_data[len-1] == '\n') {
175       lst->line->str_data[len-1] = 0;
176       lst->line->str_len--;
177     }
178     if (text == 0) {
179       text = lst;
180     }
181 
182   }
183   if (text) {
184     dlist_terminate(text->up);
185   }
186   fclose(fp);
187   return 1;
188 
189 }
190 //TODO this is overly complicated refactor with lib dllist
ex_dd(int count)191 int ex_dd(int count)
192 {
193   struct linelist *lst = c_r;
194   if (c_r == text && text == scr_r) {
195     if (!text->down && !text->up && text->line) {
196       text->line->str_len = 1;
197       sprintf(text->line->str_data, " ");
198       goto success_exit;
199     }
200     if (text->down) {
201       text = text->down;
202       text->up = 0;
203       c_r = text;
204       scr_r = text;
205       free(lst->line->str_data);
206       free(lst->line);
207       free(lst);
208     }
209     goto recursion_exit;
210   }
211   //TODO use lib dllist stuff
212   if (lst)
213   {
214     if (lst->down) {
215       lst->down->up = lst->up;
216     }
217     if (lst->up) {
218       lst->up->down = lst->down;
219     }
220     if (scr_r == c_r) {
221       scr_r = c_r->down ? c_r->down : c_r->up;
222     }
223     if (c_r->down)
224       c_r = c_r->down;
225     else {
226       c_r = c_r->up;
227       count = 1;
228     }
229     free(lst->line->str_data);
230     free(lst->line);
231     free(lst);
232   }
233 
234 recursion_exit:
235   count--;
236   //make this recursive
237   if (count)
238     return ex_dd(count);
239 success_exit:
240   check_cursor_bounds();
241   adjust_screen_buffer();
242   return 1;
243 }
244 
ex_dw(int count)245 int ex_dw(int count)
246 {
247   return 1;
248 }
249 
ex_deol(int count)250 int ex_deol(int count)
251 {
252   return 1;
253 }
254 
255 //does not work with utf8 yet
vi_x(int count)256 int vi_x(int count)
257 {
258   char *s;
259   int *l;
260   int *p;
261   if (!c_r)
262     return 0;
263   s = c_r->line->str_data;
264   l = &c_r->line->str_len;
265   p = &TT.cur_col;
266   if (!(*l)) return 0;
267   if ((*p) == (*l)-1) {
268     s[*p] = 0;
269     if (*p) (*p)--;
270     (*l)--;
271   } else {
272     memmove(s+(*p), s+(*p)+1, (*l)-(*p));
273     s[*l] = 0;
274     (*l)--;
275   }
276   count--;
277   return (count) ? vi_x(count) : 1;
278 }
279 
280 //move commands does not behave correct way yet.
281 //only jump to next space for now.
vi_movw(int count)282 int vi_movw(int count)
283 {
284   if (!c_r)
285     return 0;
286   //could we call moveend first
287   while (c_r->line->str_data[TT.cur_col] > ' ')
288     TT.cur_col++;
289   while (c_r->line->str_data[TT.cur_col] <= ' ') {
290     TT.cur_col++;
291     if (!c_r->line->str_data[TT.cur_col]) {
292       //we could call j and g0
293       if (!c_r->down) return 0;
294       c_r = c_r->down;
295       TT.cur_col = 0;
296     }
297   }
298   count--;
299   if (count>1)
300     return vi_movw(count);
301 
302   check_cursor_bounds();
303   adjust_screen_buffer();
304   return 1;
305 }
306 
vi_movb(int count)307 int vi_movb(int count)
308 {
309   if (!c_r)
310     return 0;
311   if (!TT.cur_col) {
312       if (!c_r->up) return 0;
313       c_r = c_r->up;
314       TT.cur_col = (c_r->line->str_len) ? c_r->line->str_len-1 : 0;
315       goto exit_function;
316   }
317   if (TT.cur_col)
318       TT.cur_col--;
319   while (c_r->line->str_data[TT.cur_col] <= ' ') {
320     if (TT.cur_col) TT.cur_col--;
321     else goto exit_function;
322   }
323   while (c_r->line->str_data[TT.cur_col] > ' ') {
324     if (TT.cur_col)TT.cur_col--;
325     else goto exit_function;
326   }
327   TT.cur_col++;
328 exit_function:
329   count--;
330   if (count>1)
331     return vi_movb(count);
332   check_cursor_bounds();
333   adjust_screen_buffer();
334   return 1;
335 }
336 
vi_move(int count)337 int vi_move(int count)
338 {
339   if (!c_r)
340     return 0;
341   if (TT.cur_col < c_r->line->str_len)
342     TT.cur_col++;
343   if (c_r->line->str_data[TT.cur_col] <= ' ' || count > 1)
344     vi_movw(count); //find next word;
345   while (c_r->line->str_data[TT.cur_col] > ' ')
346     TT.cur_col++;
347   if (TT.cur_col) TT.cur_col--;
348 
349   check_cursor_bounds();
350   adjust_screen_buffer();
351   return 1;
352 }
353 
i_insert()354 void i_insert()
355 {
356   char *t = xzalloc(c_r->line->alloc_len);
357   char *s = c_r->line->str_data;
358   int sel = c_r->line->str_len-TT.cur_col;
359   strncpy(t, &s[TT.cur_col], sel);
360   t[sel+1] = 0;
361   if (c_r->line->alloc_len < c_r->line->str_len+il->str_len+5) {
362     c_r->line->str_data = xrealloc(c_r->line->str_data,
363       c_r->line->alloc_len*2+il->alloc_len*2);
364 
365     c_r->line->alloc_len = c_r->line->alloc_len*2+2*il->alloc_len;
366     memset(&c_r->line->str_data[c_r->line->str_len], 0,
367         c_r->line->alloc_len-c_r->line->str_len);
368 
369     s = c_r->line->str_data;
370   }
371   strcpy(&s[TT.cur_col], il->str_data);
372   strcpy(&s[TT.cur_col+il->str_len], t);
373   TT.cur_col += il->str_len;
374   if (TT.cur_col) TT.cur_col--;
375   c_r->line->str_len += il->str_len;
376   free(t);
377 
378 }
379 
380 //new line at split pos;
i_split()381 void i_split()
382 {
383   struct str_line *l = xmalloc(sizeof(struct str_line));
384   int l_a = c_r->line->alloc_len;
385   int l_len = c_r->line->str_len-TT.cur_col;
386   l->str_data = xzalloc(l_a);
387   l->alloc_len = l_a;
388   l->str_len = l_len;
389   strncpy(l->str_data, &c_r->line->str_data[TT.cur_col], l_len);
390   l->str_data[l_len] = 0;
391   c_r->line->str_len -= l_len;
392   c_r->line->str_data[c_r->line->str_len] = 0;
393   c_r = (struct linelist*)dlist_insert((struct double_list**)&c_r, (char*)l);
394   c_r->line = l;
395   TT.cur_col = 0;
396   check_cursor_bounds();
397   adjust_screen_buffer();
398 }
399 
400 struct vi_cmd_param {
401   const char *cmd;
402   int (*vi_cmd_ptr)(int);
403 };
404 
405 struct vi_cmd_param vi_cmds[7] =
406 {
407   {"dd", &ex_dd},
408   {"dw", &ex_dw},
409   {"d$", &ex_deol},
410   {"w", &vi_movw},
411   {"b", &vi_movb},
412   {"e", &vi_move},
413   {"x", &vi_x},
414 };
415 
run_vi_cmd(char * cmd)416 int run_vi_cmd(char *cmd)
417 {
418   int val = 0;
419   char *cmd_e;
420   errno = 0;
421   int i = 0;
422   val = strtol(cmd, &cmd_e, 10);
423   if (errno || val == 0) {
424     val = 1;
425   }
426   else {
427     cmd = cmd_e;
428   }
429   for (; i<7; i++) {
430     if (strstr(cmd, vi_cmds[i].cmd)) {
431       return vi_cmds[i].vi_cmd_ptr(val);
432     }
433   }
434   return 0;
435 
436 }
437 
search_str(char * s)438 int search_str(char *s)
439 {
440   struct linelist *lst = c_r;
441   char *c = strstr(&c_r->line->str_data[TT.cur_col], s);
442   if (c) {
443     TT.cur_col = c_r->line->str_data-c;
444   TT.cur_col = c-c_r->line->str_data;
445   }
446   else for (; !c;) {
447     lst = lst->down;
448     if (!lst) return 1;
449     c = strstr(&lst->line->str_data[TT.cur_col], s);
450   }
451   c_r = lst;
452   TT.cur_col = c-c_r->line->str_data;
453   return 0;
454 }
455 
run_ex_cmd(char * cmd)456 int run_ex_cmd(char *cmd)
457 {
458   if (cmd[0] == '/') {
459     //search pattern
460     if (!search_str(&cmd[1]) ) {
461       check_cursor_bounds();
462       adjust_screen_buffer();
463     }
464   } else if (cmd[0] == '?') {
465 
466   } else if (cmd[0] == ':') {
467     if (strstr(&cmd[1], "q!")) {
468       //exit_application;
469       return -1;
470     }
471     else if (strstr(&cmd[1], "wq")) {
472       write_file(0);
473       return -1;
474     }
475     else if (strstr(&cmd[1], "w")) {
476       write_file(0);
477       return 1;
478     }
479   }
480   return 0;
481 
482 }
483 
vi_main(void)484 void vi_main(void)
485 {
486   char keybuf[16];
487   char utf8_code[8];
488   int utf8_dec_p = 0;
489   int key = 0;
490   char vi_buf[16];
491   int vi_buf_pos = 0;
492   il = xzalloc(sizeof(struct str_line));
493   il->str_data = xzalloc(80);
494   il->alloc_len = 80;
495   keybuf[0] = 0;
496   memset(vi_buf, 0, 16);
497   memset(utf8_code, 0, 8);
498   linelist_load(0);
499   scr_r = text;
500   c_r = text;
501   TT.cur_row = 0;
502   TT.cur_col = 0;
503   TT.screen_width = 80;
504   TT.screen_height = 24;
505   TT.vi_mode = 1;
506   terminal_size(&TT.screen_width, &TT.screen_height);
507   TT.screen_height -= 2; //TODO this is hack fix visual alignment
508   set_terminal(0, 1, 0, 0);
509   //writes stdout into different xterm buffer so when we exit
510   //we dont get scroll log full of junk
511   tty_esc("?1049h");
512   tty_esc("H");
513   xflush();
514   draw_page();
515   while(1) {
516     key = scan_key(keybuf, -1);
517     printf("key %d\n", key);
518     switch (key) {
519       case -1:
520       case 3:
521       case 4:
522         goto cleanup_vi;
523     }
524     if (TT.vi_mode == 1) { //NORMAL
525       switch (key) {
526         case 'h':
527           cur_left();
528           break;
529         case 'j':
530           cur_down();
531           break;
532         case 'k':
533           cur_up();
534           break;
535         case 'l':
536           cur_right();
537           break;
538         case '/':
539         case '?':
540         case ':':
541           TT.vi_mode = 0;
542           il->str_data[0]=key;
543           il->str_len++;
544           break;
545         case 'a':
546           if (c_r && c_r->line->str_len)
547             TT.cur_col++;
548         case 'i':
549           TT.vi_mode = 2;
550           break;
551         case 27:
552           vi_buf[0] = 0;
553           vi_buf_pos = 0;
554           break;
555         default:
556           if (key > 0x20 && key < 0x7B) {
557             vi_buf[vi_buf_pos] = key;
558             vi_buf_pos++;
559             if (run_vi_cmd(vi_buf)) {
560               memset(vi_buf, 0, 16);
561               vi_buf_pos = 0;
562             }
563             else if (vi_buf_pos == 16) {
564               vi_buf_pos = 0;
565             }
566 
567           }
568 
569           break;
570       }
571     } else if (TT.vi_mode == 0) { //EX MODE
572       switch (key) {
573         case 27:
574           TT.vi_mode = 1;
575           il->str_len = 0;
576           memset(il->str_data, 0, il->alloc_len);
577           break;
578         case 0x7F:
579         case 0x08:
580           if (il->str_len) {
581             il->str_data[il->str_len] = 0;
582             if (il->str_len > 1) il->str_len--;
583           }
584           break;
585         case 0x0D:
586             if (run_ex_cmd(il->str_data) == -1)
587               goto cleanup_vi;
588           TT.vi_mode = 1;
589           il->str_len = 0;
590           memset(il->str_data, 0, il->alloc_len);
591           break;
592         default: //add chars to ex command until ENTER
593           if (key >= 0x20 && key < 0x7F) { //might be utf?
594             if (il->str_len == il->alloc_len) {
595               il->str_data = realloc(il->str_data, il->alloc_len*2);
596               il->alloc_len *= 2;
597             }
598             il->str_data[il->str_len] = key;
599             il->str_len++;
600           }
601           break;
602       }
603     } else if (TT.vi_mode == 2) {//INSERT MODE
604       switch (key) {
605         case 27:
606           i_insert();
607           TT.vi_mode = 1;
608           il->str_len = 0;
609           memset(il->str_data, 0, il->alloc_len);
610           break;
611         case 0x7F:
612         case 0x08:
613           if (il->str_len)
614             il->str_data[il->str_len--] = 0;
615           break;
616         case 0x09:
617           //TODO implement real tabs
618           il->str_data[il->str_len++] = ' ';
619           il->str_data[il->str_len++] = ' ';
620           break;
621 
622         case 0x0D:
623           //insert newline
624           //
625           i_insert();
626           il->str_len = 0;
627           memset(il->str_data, 0, il->alloc_len);
628           i_split();
629           break;
630         default:
631           if (key >= 0x20 && utf8_dec(key, utf8_code, &utf8_dec_p)) {
632             if (il->str_len+utf8_dec_p+1 >= il->alloc_len) {
633               il->str_data = realloc(il->str_data, il->alloc_len*2);
634               il->alloc_len *= 2;
635             }
636             strcpy(il->str_data+il->str_len, utf8_code);
637             il->str_len += utf8_dec_p;
638             utf8_dec_p = 0;
639             *utf8_code = 0;
640 
641           }
642           break;
643       }
644     }
645 
646     draw_page();
647 
648   }
649 cleanup_vi:
650   tty_reset();
651   tty_esc("?1049l");
652 }
653 
draw_page()654 static void draw_page()
655 {
656   unsigned y = 0;
657   int cy_scr = 0;
658   int cx_scr = 0;
659   int utf_l = 0;
660 
661   char* line = 0;
662   int bytes = 0;
663   int drawn = 0;
664   int x = 0;
665   struct linelist *scr_buf= scr_r;
666   //clear screen
667   tty_esc("2J");
668   tty_esc("H");
669 
670   tty_jump(0, 0);
671 
672   //draw lines until cursor row
673   for (; y < TT.screen_height; ) {
674     if (line && bytes) {
675       draw_str_until(&drawn, line, TT.screen_width, bytes);
676       bytes = drawn ? (bytes-drawn) : 0;
677       line = bytes ? (line+drawn) : 0;
678       y++;
679       tty_jump(0, y);
680     } else if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
681       if (scr_buf == c_r)
682         break;
683       line = scr_buf->line->str_data;
684       bytes = scr_buf->line->str_len;
685       scr_buf = scr_buf->down;
686     } else {
687       if (scr_buf == c_r)
688         break;
689       y++;
690       tty_jump(0, y);
691       //printf(" \n");
692       if (scr_buf) scr_buf = scr_buf->down;
693     }
694 
695   }
696   //draw cursor row until cursor
697   //this is to calculate cursor position on screen and possible insert
698   line = scr_buf->line->str_data;
699   bytes = TT.cur_col;
700   for (; y < TT.screen_height; ) {
701     if (bytes) {
702       x = draw_str_until(&drawn, line, TT.screen_width, bytes);
703       bytes = drawn ? (bytes-drawn) : 0;
704       line = bytes ? (line+drawn) : 0;
705     }
706     if (!bytes) break;
707     y++;
708     tty_jump(0, y);
709   }
710   if (TT.vi_mode == 2 && il->str_len) {
711     line = il->str_data;
712     bytes = il->str_len;
713     cx_scr = x;
714     cy_scr = y;
715     x = draw_str_until(&drawn, line, TT.screen_width-x, bytes);
716     bytes = drawn ? (bytes-drawn) : 0;
717     line = bytes ? (line+drawn) : 0;
718     cx_scr += x;
719     for (; y < TT.screen_height; ) {
720       if (bytes) {
721         x = draw_str_until(&drawn, line, TT.screen_width, bytes);
722         bytes = drawn ? (bytes-drawn) : 0;
723         line = bytes ? (line+drawn) : 0;
724         cx_scr = x;
725       }
726       if (!bytes) break;
727       y++;
728       cy_scr = y;
729       tty_jump(0, y);
730     }
731   } else {
732     cy_scr = y;
733     cx_scr = x;
734   }
735   line = scr_buf->line->str_data+TT.cur_col;
736   bytes = scr_buf->line->str_len-TT.cur_col;
737   scr_buf = scr_buf->down;
738   x = draw_str_until(&drawn,line, TT.screen_width-x, bytes);
739   bytes = drawn ? (bytes-drawn) : 0;
740   line = bytes ? (line+drawn) : 0;
741   y++;
742   tty_jump(0, y);
743 
744 //draw until end
745   for (; y < TT.screen_height; ) {
746     if (line && bytes) {
747       draw_str_until(&drawn, line, TT.screen_width, bytes);
748       bytes = drawn ? (bytes-drawn) : 0;
749       line = bytes ? (line+drawn) : 0;
750       y++;
751       tty_jump(0, y);
752     } else if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
753       line = scr_buf->line->str_data;
754       bytes = scr_buf->line->str_len;
755       scr_buf = scr_buf->down;
756     } else {
757       y++;
758       tty_jump(0, y);
759       if (scr_buf) scr_buf = scr_buf->down;
760     }
761 
762   }
763 
764   tty_jump(0, TT.screen_height);
765   switch (TT.vi_mode) {
766     case 0:
767     tty_esc("30;44m");
768     printf("COMMAND|");
769     break;
770     case 1:
771     tty_esc("30;42m");
772     printf("NORMAL|");
773     break;
774     case 2:
775     tty_esc("30;41m");
776     printf("INSERT|");
777     break;
778 
779   }
780   //DEBUG
781   tty_esc("47m");
782   tty_esc("30m");
783   utf_l = utf8_len(&c_r->line->str_data[TT.cur_col]);
784   if (utf_l) {
785     char t[5] = {0, 0, 0, 0, 0};
786     strncpy(t, &c_r->line->str_data[TT.cur_col], utf_l);
787     printf("utf: %d %s", utf_l, t);
788   }
789   printf("| %d, %d\n", cx_scr, cy_scr); //screen coord
790 
791   tty_jump(TT.screen_width-12, TT.screen_height);
792   printf("| %d, %d\n", TT.cur_row, TT.cur_col);
793   tty_esc("37m");
794   tty_esc("40m");
795   if (!TT.vi_mode) {
796     tty_esc("1m");
797     tty_jump(0, TT.screen_height+1);
798     printf("%s", il->str_data);
799     tty_esc("0m");
800   } else tty_jump(cx_scr, cy_scr);
801 
802   xflush();
803 
804 }
805 
draw_char(char c,int x,int y,int highlight)806 static void draw_char(char c, int x, int y, int highlight)
807 {
808   tty_jump(x, y);
809   if (highlight) {
810     tty_esc("30m"); //foreground black
811     tty_esc("47m"); //background white
812   }
813   printf("%c", c);
814 }
815 
816 //utf rune draw
817 //printf and useless copy could be replaced by direct write() to stdout
draw_rune(char * c,int x,int y,int highlight)818 static int draw_rune(char *c, int x, int y, int highlight)
819 {
820   int l = utf8_len(c);
821   char t[5] = {0, 0, 0, 0, 0};
822   if (!l) return 0;
823   tty_jump(x, y);
824   tty_esc("0m");
825   if (highlight) {
826     tty_esc("30m"); //foreground black
827     tty_esc("47m"); //background white
828   }
829   strncpy(t, c, 5);
830   printf("%s", t);
831   tty_esc("0m");
832   return l;
833 }
834 
check_cursor_bounds()835 static void check_cursor_bounds()
836 {
837   if (c_r->line->str_len-1 < TT.cur_col) {
838     if (c_r->line->str_len == 0)
839       TT.cur_col = 0;
840     else
841       TT.cur_col = c_r->line->str_len-1;
842   }
843 }
844 
adjust_screen_buffer()845 static void adjust_screen_buffer()
846 {
847   //search cursor and screen TODO move this perhaps
848   struct linelist *t = text;
849   int c = -1;
850   int s = -1;
851   int i = 0;
852   for (;;) {
853     i++;
854     if (t == c_r)
855       c = i;
856     if (t == scr_r)
857       s = i;
858     t = t->down;
859     if ( ((c != -1) && (s != -1)) || t == 0)
860       break;
861   }
862   if (c <= s) {
863     scr_r = c_r;
864   }
865   else if ( c > s ) {
866     //should count multiline long strings!
867     int distance = c - s + 1;
868     //TODO instead iterate scr_r up and check strlen%screen_width
869     //for each iteration
870     if (distance >= (int)TT.screen_height) {
871       int adj = distance - TT.screen_height;
872       while(adj--) {
873         scr_r = scr_r->down;
874       }
875     }
876   }
877   TT.cur_row = c;
878 
879 }
880 
881 //return 0 if not ASCII nor UTF-8
882 //this is not fully tested
883 //naive implementation with branches
884 //there is better branchless lookup table versions out there
885 //1 0xxxxxxx
886 //2 110xxxxx  10xxxxxx
887 //3 1110xxxx  10xxxxxx  10xxxxxx
888 //4 11110xxx  10xxxxxx  10xxxxxx  10xxxxxx
utf8_len(char * str)889 static int utf8_len(char *str)
890 {
891   int len = 0;
892   int i = 0;
893   uint8_t *c = (uint8_t*)str;
894   if (!c || !(*c)) return 0;
895   if (*c < 0x7F) return 1;
896   if ((*c & 0xE0) == 0xc0) len = 2;
897   else if ((*c & 0xF0) == 0xE0 ) len = 3;
898   else if ((*c & 0xF8) == 0xF0 ) len = 4;
899   else return 0;
900   c++;
901   for (i = len-1; i > 0; i--) {
902     if ((*c++ & 0xc0) != 0x80) return 0;
903   }
904   return len;
905 }
906 
907 //get utf8 length and width at same time
utf8_lnw(int * width,char * str,int bytes)908 static int utf8_lnw(int* width, char* str, int bytes)
909 {
910   wchar_t wc;
911   int length = 1;
912   *width = 1;
913 //  if (str < 0x7F) return length;
914   length = mbtowc(&wc, str, bytes);
915   switch (length) {
916   case -1:
917     mbtowc(0,0,4);
918   case 0:
919     *width = 0;
920     length = 0;
921     break;
922   default:
923   *width = wcwidth(wc);
924   }
925   return length;
926 }
927 
928 //try to estimate width of next "glyph" in terminal buffer
929 //combining chars 0x300-0x36F shall be zero width
utf8_width(char * str,int bytes)930 static int utf8_width(char *str, int bytes)
931 {
932   wchar_t wc;
933   switch (mbtowc(&wc, str, bytes)) {
934   case -1:
935     mbtowc(0,0,4);
936   case 0:
937     return -1;
938   default:
939   return wcwidth(wc);
940   }
941   return 0;
942 }
943 
utf8_dec(char key,char * utf8_scratch,int * sta_p)944 static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
945 {
946   int len = 0;
947   char *c = utf8_scratch;
948   c[*sta_p] = key;
949   if (!(*sta_p))  *c = key;
950   if (*c < 0x7F) { *sta_p = 1; return 1; }
951   if ((*c & 0xE0) == 0xc0) len = 2;
952   else if ((*c & 0xF0) == 0xE0 ) len = 3;
953   else if ((*c & 0xF8) == 0xF0 ) len = 4;
954   else {*sta_p = 0; return 0; }
955 
956   (*sta_p)++;
957 
958   if (*sta_p == 1) return 0;
959   if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
960 
961   if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
962 
963   return 0;
964 }
965 
draw_str_until(int * drawn,char * str,int width,int bytes)966 static int draw_str_until(int *drawn, char *str, int width, int bytes)
967 {
968   int rune_width = 0;
969   int rune_bytes = 0;
970   int max_bytes = bytes;
971   int max_width = width;
972   char* end = str;
973   for (;width && bytes;) {
974     rune_bytes = utf8_lnw(&rune_width, end, 4);
975     if (!rune_bytes) break;
976     if (width - rune_width < 0) goto write_bytes;
977     width -= rune_width;
978     bytes -= rune_bytes;
979     end += rune_bytes;
980   }
981   for (;bytes;) {
982     rune_bytes = utf8_lnw(&rune_width, end, 4);
983     if (!rune_bytes) break;
984     if (rune_width) break;
985     bytes -= rune_bytes;
986     end += rune_bytes;
987   }
988 write_bytes:
989   fwrite(str, max_bytes-bytes, 1, stdout);
990   *drawn = max_bytes-bytes;
991   return max_width-width;
992 }
993 
cur_left()994 static void cur_left()
995 {
996   if (!TT.cur_col) return;
997   TT.cur_col--;
998 
999   if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left();
1000 }
1001 
cur_right()1002 static void cur_right()
1003 {
1004   if (c_r->line->str_len <= 1) return;
1005   if (TT.cur_col == c_r->line->str_len-1) return;
1006   TT.cur_col++;
1007   if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_right();
1008 }
1009 
cur_up()1010 static void cur_up()
1011 {
1012   if (c_r->up != 0)
1013     c_r = c_r->up;
1014 
1015   if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left();
1016   check_cursor_bounds();
1017   adjust_screen_buffer();
1018 }
1019 
cur_down()1020 static void cur_down()
1021 {
1022   if (c_r->down != 0)
1023     c_r = c_r->down;
1024 
1025   if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left();
1026   check_cursor_bounds();
1027   adjust_screen_buffer();
1028 }
1029