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