aboutsummaryrefslogtreecommitdiff
path: root/toys/pending
diff options
context:
space:
mode:
Diffstat (limited to 'toys/pending')
-rw-r--r--toys/pending/vi.c893
1 files changed, 879 insertions, 14 deletions
diff --git a/toys/pending/vi.c b/toys/pending/vi.c
index bf8db841..db2f42c9 100644
--- a/toys/pending/vi.c
+++ b/toys/pending/vi.c
@@ -1,17 +1,15 @@
/* vi.c - You can't spell "evil" without "vi".
*
* Copyright 2015 Rob Landley <rob@landley.net>
+ * Copyright 2019 Jarno Mäkipää <jmakip87@gmail.com>
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html
-
USE_VI(NEWTOY(vi, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
-
config VI
bool "vi"
default n
help
usage: vi FILE
-
Visual text editor. Predates the existence of standardized cursor keys,
so the controls are weird and historical.
*/
@@ -20,29 +18,896 @@ config VI
#include "toys.h"
GLOBALS(
- struct linestack *ls;
- char *statline;
+ struct termios default_opts;
+ struct linestack *ls;
+ char *statline;
+ int cur_col;
+ int cur_row;
+ unsigned screen_height;
+ unsigned screen_width;
+ int vi_mode;
)
+/*
+ *
+ * TODO:
+ * BUGS: screen pos adjust does not cover "widelines"
+ * utf8 problems with some files. perhaps use lib utf8 functions instead
+ * append to EOL does not show input but works when ESC out
+ *
+ *
+ * REFACTOR: use dllist functions where possible.
+ * draw_page dont draw full page at time if nothing changed...
+ * ex callbacks
+ *
+ * FEATURE: ex: / ? % //atleast easy cases
+ * vi: x dw d$ d0
+ * vi: yw yy (y0 y$)
+ * vi+ex: gg G //line movements
+ * ex: r
+ * ex: !external programs
+ * ex: w filename //only writes to same file now
+ * big file support?
+ */
+
+
struct linestack_show {
struct linestack_show *next;
long top, left;
int x, width, y, height;
};
-// linestack, what to show, where to show it
-void linestack_show(struct linestack *ls, struct linestack_show *lss)
+static void draw_page();
+static void draw_char(char c, int x, int y, int highlight);
+//utf8 support
+static int utf8_dec(char key, char* utf8_scratch,int* sta_p) ;
+static int utf8_len(char* str);
+static int draw_rune(char* c,int x,int y, int highlight);
+
+
+static void cur_left();
+static void cur_right();
+static void cur_up();
+static void cur_down();
+static void check_cursor_bounds();
+static void adjust_screen_buffer();
+
+
+struct str_line {
+ int alloc_len;
+ int str_len;
+ char* str_data;
+};
+
+//lib dllist uses next and prev kinda opposite what im used to so I just
+//renamed both ends to up and down
+struct linelist {
+ struct linelist *up;//next
+ struct linelist *down;//prev
+ struct str_line *line;
+};
+//inserted line not yet pushed to buffer
+struct str_line *il;
+struct linelist *text; //file loaded into buffer
+struct linelist *scr_r;//current screen coord 0 row
+struct linelist *c_r;//cursor position row
+int modified;
+
+void dlist_insert_nomalloc(struct double_list **list, struct double_list *new)
{
- return;
+ if (*list) {
+ new->next = *list;
+ new->prev = (*list)->prev;
+ if((*list)->prev) (*list)->prev->next = new;
+ (*list)->prev = new;
+ } else *list = new->next = new->prev = new;
}
-void vi_main(void)
+
+// Add an entry to the end of a doubly linked list
+struct double_list *dlist_insert(struct double_list **list, char *data)
{
- int i;
+ struct double_list *new = xmalloc(sizeof(struct double_list));
+ new->data = data;
+ dlist_insert_nomalloc(list, new);
- if (!(TT.ls = linestack_load(*toys.optargs)))
- TT.ls = xzalloc(sizeof(struct linestack));
+ return new;
+}
+void linelist_unload()
+{
- for (i=0; i<TT.ls->len; i++)
- printf("%.*s\n", (int)TT.ls->idx[i].len, (char *)TT.ls->idx[i].ptr);
+}
+
+void write_file(char* filename)
+{
+ struct linelist *lst = text;
+ FILE *fp = 0;
+ if (!filename)
+ filename = (char*)*toys.optargs;
+ fp = fopen(filename,"w");
+ if (!fp) return ;
+ while(lst) {
+ fprintf(fp,"%s\n",lst->line->str_data);
+ lst = lst->down;
+ }
+ fclose(fp);
+}
+
+int linelist_load(char* filename)
+{
+ struct linelist *lst = c_r;//cursor position or 0
+ FILE* fp = 0;
+ if (!filename)
+ filename = (char*)*toys.optargs;
+
+ fp = fopen(filename, "r");
+ if (!fp) return 0;
+
+
+ for (;;) {
+ char* line = xzalloc(80);
+ ssize_t alc =80;
+ ssize_t len;
+ if ((len = getline(&line, (void *)&alc, fp))== -1) {
+ if (errno == EINVAL || errno == ENOMEM) {
+ printf("error %d\n",errno);
+ }
+ free(line);
+ break;
+ }
+ lst = (struct linelist*)dlist_add((struct double_list**)&lst,
+ xzalloc(sizeof(struct str_line)));
+ lst->line->alloc_len = alc;
+ lst->line->str_len = len;
+ lst->line->str_data = line;
+
+ if (lst->line->str_data[len-1]=='\n') {
+ lst->line->str_data[len-1]=0;
+ lst->line->str_len--;
+ }
+ if (text == 0) {
+ text = lst;
+ }
+
+ }
+ if (text) {
+ dlist_terminate(text->up);
+ }
+ fclose(fp);
+ return 1;
+
+}
+//TODO this is overly complicated refactor with lib dllist
+int ex_dd(int count)
+{
+ struct linelist* lst = c_r;
+ if (c_r==text && text == scr_r) {
+ if (!text->down && !text->up && text->line) {
+ text->line->str_len=1;
+ sprintf(text->line->str_data," ");
+ goto success_exit;
+ }
+ if (text->down) {
+ text =text->down;
+ text->up=0;
+ c_r=text;
+ scr_r=text;
+ free(lst->line->str_data);
+ free(lst->line);
+ free(lst);
+ }
+ goto recursion_exit;
+ }
+ //TODO use lib dllist stuff
+ if (lst)
+ {
+ if (lst->down) {
+ lst->down->up = lst->up;
+ }
+ if (lst->up) {
+ lst->up->down = lst->down;
+ }
+ if (scr_r == c_r) {
+ scr_r =c_r->down ? c_r->down : c_r->up;
+ }
+ if (c_r->down)
+ c_r = c_r->down;
+ else {
+ c_r = c_r->up;
+ count = 1;
+ }
+ free(lst->line->str_data);
+ free(lst->line);
+ free(lst);
+ }
+
+recursion_exit:
+ count--;
+ //make this recursive
+ if (count)
+ return ex_dd(count);
+success_exit:
+ check_cursor_bounds();
+ adjust_screen_buffer();
+ return 1;
+}
+
+int ex_dw(int count)
+{
+ return 1;
+}
+
+int ex_deol(int count)
+{
+ return 1;
+}
+//does not work with utf8 yet
+int vi_x(int count)
+{
+ char* s;
+ int* l;
+ int* p;
+ if (!c_r)
+ return 0;
+ s = c_r->line->str_data;
+ l = &c_r->line->str_len;
+ p = &TT.cur_col;
+ if (!(*l)) return 0;
+ if ((*p) == (*l)-1) {
+ s[*p]=0;
+ if (*p) (*p)--;
+ (*l)--;
+ } else {
+ memmove(s+(*p),s+(*p)+1,(*l)-(*p));
+ s[*l]=0;
+ (*l)--;
+ }
+ count--;
+ return (count) ? vi_x(count) : 1;
+}
+
+//move commands does not behave correct way yet.
+//only jump to next space for now.
+int vi_movw(int count)
+{
+ if (!c_r)
+ return 0;
+ //could we call moveend first
+ while(c_r->line->str_data[TT.cur_col] > ' ')
+ TT.cur_col++;
+ while(c_r->line->str_data[TT.cur_col] <= ' ') {
+ TT.cur_col++;
+ if (!c_r->line->str_data[TT.cur_col]) {
+ //we could call j and g0
+ if (!c_r->down) return 0;
+ c_r = c_r->down;
+ TT.cur_col=0;
+ }
+ }
+ count--;
+ if (count>1)
+ return vi_movw(count);
+
+ check_cursor_bounds();
+ adjust_screen_buffer();
+ return 1;
+}
+
+int vi_movb(int count)
+{
+ if (!c_r)
+ return 0;
+ if (!TT.cur_col) {
+ if (!c_r->up) return 0;
+ c_r = c_r->up;
+ TT.cur_col=(c_r->line->str_len) ? c_r->line->str_len-1 : 0;
+ goto exit_function;
+ }
+ if (TT.cur_col)
+ TT.cur_col--;
+ while(c_r->line->str_data[TT.cur_col] <= ' ') {
+ if (TT.cur_col) TT.cur_col--;
+ else goto exit_function;
+ }
+ while(c_r->line->str_data[TT.cur_col] > ' ') {
+ if (TT.cur_col)TT.cur_col--;
+ else goto exit_function;
+ }
+ TT.cur_col++;
+exit_function:
+ count--;
+ if (count>1)
+ return vi_movb(count);
+ check_cursor_bounds();
+ adjust_screen_buffer();
+ return 1;
+}
+
+int vi_move(int count)
+{
+ if (!c_r)
+ return 0;
+ if (TT.cur_col < c_r->line->str_len)
+ TT.cur_col++;
+ if (c_r->line->str_data[TT.cur_col] <= ' ' || count > 1)
+ vi_movw(count); //find next word;
+ while(c_r->line->str_data[TT.cur_col] > ' ')
+ TT.cur_col++;
+ if (TT.cur_col) TT.cur_col--;
+ check_cursor_bounds();
+ adjust_screen_buffer();
+ return 1;
+}
+
+void i_insert()
+{
+ char* t = xzalloc(c_r->line->alloc_len);
+ char* s = c_r->line->str_data;
+ int sel = c_r->line->str_len-TT.cur_col;
+ strncpy(t,&s[TT.cur_col],sel);
+ t[sel+1] = 0;
+ if (c_r->line->alloc_len< c_r->line->str_len+il->str_len+5) {
+ c_r->line->str_data = xrealloc(c_r->line->str_data,c_r->line->alloc_len*2+il->alloc_len*2);
+ c_r->line->alloc_len = c_r->line->alloc_len*2 + 2*il->alloc_len;
+ memset(&c_r->line->str_data[c_r->line->str_len],0,c_r->line->alloc_len-c_r->line->str_len);
+ s = c_r->line->str_data;
+ }
+ strcpy(&s[TT.cur_col],il->str_data);
+ strcpy(&s[TT.cur_col+il->str_len],t);
+ TT.cur_col += il->str_len;
+ if (TT.cur_col) TT.cur_col--;
+ c_r->line->str_len+=il->str_len;
+ free(t);
+
+}
+//new line at split pos;
+void i_split()
+{
+ struct str_line* l = xmalloc(sizeof(struct str_line));
+ int l_a = c_r->line->alloc_len;
+ int l_len = c_r->line->str_len-TT.cur_col;
+ l->str_data = xzalloc(l_a);
+ l->alloc_len=l_a;
+ l->str_len = l_len;
+ strncpy(l->str_data,&c_r->line->str_data[TT.cur_col],l_len);
+ l->str_data[l_len] = 0;
+ c_r->line->str_len-=l_len;
+ c_r->line->str_data[c_r->line->str_len] = 0;
+ c_r = (struct linelist*)dlist_insert((struct double_list**)&c_r,(char*)l);
+ c_r->line = l;
+ TT.cur_col=0;
+ check_cursor_bounds();
+ adjust_screen_buffer();
+}
+
+struct vi_cmd_param {
+ const char* cmd;
+ int (*vi_cmd_ptr)(int);
+};
+struct vi_cmd_param vi_cmds[7] =
+{
+ {"dd",&ex_dd},
+ {"dw",&ex_dw},
+ {"d$",&ex_deol},
+ {"w",&vi_movw},
+ {"b",&vi_movb},
+ {"e",&vi_move},
+ {"x",&vi_x},
+};
+int run_vi_cmd(char* cmd)
+{
+ int val = 0;
+ char* cmd_e;
+ errno = 0;
+ val = strtol(cmd, &cmd_e, 10);
+ if (errno || val == 0) {
+ val = 1;
+ }
+ else {
+ cmd = cmd_e;
+ }
+ for(int i=0;i<7;i++) {
+ if (strstr(cmd,vi_cmds[i].cmd)) {
+ return vi_cmds[i].vi_cmd_ptr(val);
+ }
+ }
+ return 0;
+
+}
+
+int search_str(char* s)
+{
+ struct linelist* lst = c_r;
+ char *c = strstr(&c_r->line->str_data[TT.cur_col],s);
+ if (c) {
+ TT.cur_col = c_r->line->str_data-c;
+ TT.cur_col=c-c_r->line->str_data;
+ }
+ else for(;!c;) {
+ lst = lst->down;
+ if (!lst) return 1;
+ c = strstr(&lst->line->str_data[TT.cur_col],s);
+ }
+ c_r=lst;
+ TT.cur_col=c-c_r->line->str_data;
+ return 0;
+}
+
+int run_ex_cmd(char* cmd)
+{
+ if (cmd[0] == '/') {
+ //search pattern
+ if (!search_str(&cmd[1]) ) {
+ check_cursor_bounds();
+ adjust_screen_buffer();
+ }
+ } else if (cmd[0] == '?') {
+
+ } else if (cmd[0] == ':') {
+ if (strstr(&cmd[1],"q!")) {
+ //exit_application;
+ return -1;
+ }
+ else if (strstr(&cmd[1],"wq")) {
+ write_file(0);
+ return -1;
+ }
+ else if (strstr(&cmd[1],"w")) {
+ write_file(0);
+ return 1;
+ }
+ }
+ return 0;
+
+}
+
+void vi_main(void)
+{
+ char keybuf[16];
+ char utf8_code[8];
+ int utf8_dec_p = 0;
+ int key = 0;
+ char vi_buf[16];
+ int vi_buf_pos=0;
+ il = xzalloc(sizeof(struct str_line));
+ il->str_data = xzalloc(80);
+ il->alloc_len = 80;
+ keybuf[0] = 0;
+ memset(vi_buf,0,16);
+ memset(utf8_code,0,8);
+ linelist_load(0);
+ scr_r = text;
+ c_r = text;
+ TT.cur_row = 0;
+ TT.cur_col = 0;
+ TT.screen_width = 80;
+ TT.screen_height = 24;
+ TT.vi_mode = 1;
+ terminal_size(&TT.screen_width, &TT.screen_height);
+ TT.screen_height -=2; //TODO this is hack fix visual alignment
+ set_terminal(0,1,0,0);
+ //writes stdout into different xterm buffer so when we exit
+ //we dont get scroll log full of junk
+ tty_esc("?1049h");
+ tty_esc("H");
+ xflush();
+ draw_page();
+ while(1) {
+ key = scan_key(keybuf,-1);
+ printf("key %d\n",key);
+ switch (key) {
+ case -1:
+ case 3:
+ case 4:
+ goto cleanup_vi;
+ }
+ if (TT.vi_mode == 1) { //NORMAL
+ switch (key) {
+ case 'h':
+ cur_left();
+ break;
+ case 'j':
+ cur_down();
+ break;
+ case 'k':
+ cur_up();
+ break;
+ case 'l':
+ cur_right();
+ break;
+ case '/':
+ case '?':
+ case ':':
+ TT.vi_mode = 0;
+ il->str_data[0]=key;
+ il->str_len++;
+ break;
+ case 'a':
+ TT.cur_col++;
+ case 'i':
+ TT.vi_mode = 2;
+ break;
+ case 27:
+ vi_buf[0] = 0;
+ vi_buf_pos = 0;
+ break;
+ default:
+ if (key > 0x20 && key < 0x7B) {
+ vi_buf[vi_buf_pos] = key;
+ vi_buf_pos++;
+ if (run_vi_cmd(vi_buf)) {
+ memset(vi_buf,0,16);
+ vi_buf_pos=0;
+ }
+ else if (vi_buf_pos==16) {
+ vi_buf_pos = 0;
+ }
+
+ }
+
+ break;
+ }
+ } else if (TT.vi_mode == 0) { //EX MODE
+ switch (key) {
+ case 27:
+ TT.vi_mode=1;
+ il->str_len = 0;
+ memset(il->str_data,0,il->alloc_len);
+ break;
+ case 0x7F:
+ case 0x08:
+ if (il->str_len){
+ il->str_data[il->str_len] = 0;
+ if (il->str_len>1) il->str_len--;
+ }
+ break;
+ case 0x0D:
+ if (run_ex_cmd(il->str_data) == -1)
+ goto cleanup_vi;
+ TT.vi_mode=1;
+ il->str_len = 0;
+ memset(il->str_data,0,il->alloc_len);
+ break;
+ default: //add chars to ex command until ENTER
+ if (key >= 0x20 && key < 0x7F) { //might be utf?
+ if (il->str_len == il->alloc_len)
+ {
+ il->str_data = realloc(il->str_data,il->alloc_len*2);
+ il->alloc_len *=2;
+ }
+ il->str_data[il->str_len] = key;
+ il->str_len++;
+ }
+ break;
+ }
+ } else if (TT.vi_mode == 2) {//INSERT MODE
+ switch (key) {
+ case 27:
+ i_insert();
+ TT.vi_mode=1;
+ il->str_len = 0;
+ memset(il->str_data,0,il->alloc_len);
+ break;
+ case 0x7F:
+ case 0x08:
+ if (il->str_len)
+ il->str_data[il->str_len--] = 0;
+ break;
+ case 0x09:
+ //TODO implement real tabs
+ il->str_data[il->str_len++] = ' ';
+ il->str_data[il->str_len++] = ' ';
+ break;
+
+ case 0x0D:
+ //insert newline
+ //
+ i_insert();
+ il->str_len = 0;
+ memset(il->str_data,0,il->alloc_len);
+ i_split();
+ break;
+ default:
+ if (key >= 0x20 /*&& key < 0x7F) {
+ if (il->str_len == il->alloc_len)
+ {
+ il->str_data = realloc(il->str_data,il->alloc_len*2);
+ il->alloc_len *=2;
+ }
+ il->str_data[il->str_len] = key;
+ il->str_len++;
+ } else if (key > 0x7F */&& utf8_dec(key, utf8_code, &utf8_dec_p)) {
+ if (il->str_len+utf8_dec_p+1 >= il->alloc_len)
+ {
+ il->str_data = realloc(il->str_data,il->alloc_len*2);
+ il->alloc_len *=2;
+ }
+ strcpy(il->str_data+il->str_len,utf8_code);
+ il->str_len +=utf8_dec_p;
+ utf8_dec_p=0;
+ *utf8_code=0;
+
+ }
+ break;
+ }
+ }
+
+ draw_page();
+
+ }
+cleanup_vi:
+ tty_reset();
+ tty_esc("?1049l");
+}
+
+static void draw_page()
+{
+ unsigned y = 0;
+ int cy_scr =0;
+ int cx_scr =0;
+ struct linelist* scr_buf= scr_r;
+ //clear screen
+ tty_esc("2J");
+ tty_esc("H");
+
+
+ tty_jump(0,0);
+ for(; y < TT.screen_height; ) {
+ if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
+ for(int p = 0; p < scr_buf->line->str_len;y++) {
+ unsigned x = 0;
+ for(;x<TT.screen_width;x++) {
+ if (p < scr_buf->line->str_len) {
+ int hi = 0;
+ if (scr_buf == c_r && p == TT.cur_col) {
+ if (TT.vi_mode == 2) {
+ tty_jump(x,y);
+
+ tty_esc("1m"); //bold
+ printf("%s",il->str_data);
+ x+=il->str_len;
+ tty_esc("0m");
+ }
+ cy_scr = y;
+ cx_scr = x;
+ }
+ int l = draw_rune(&scr_buf->line->str_data[p],x,y,hi);
+ if (!l)
+ break;
+ p+=l;
+ if (l>2) x++;//traditional chinese is somehow 2 width in tty???
+ }
+ else {
+ if (scr_buf == c_r && p == TT.cur_col) {
+ if (TT.vi_mode == 2) {
+ tty_jump(x,y);
+
+ tty_esc("1m"); //bold
+ printf("%s",il->str_data);
+ x+=il->str_len;
+ tty_esc("0m");
+ }
+ cy_scr = y;
+ cx_scr = x;
+ }
+ break;
+ }
+ }
+ printf("\r\n");
+ }
+ }
+ else {
+ if (scr_buf == c_r){
+ cy_scr = y;
+ cx_scr = 0;
+ if (TT.vi_mode == 2) {
+ tty_jump(0,y);
+ tty_esc("1m"); //bold
+ printf("%s",il->str_data);
+ cx_scr +=il->str_len;
+ tty_esc("0m");
+ } else draw_char(' ',0,y,1);
+ }
+ y++;
+ }
+ printf("\n");
+ if (scr_buf->down)
+ scr_buf=scr_buf->down;
+ else break;
+ }
+ for(;y < TT.screen_height;y++) {
+ printf("\n");
+ }
+
+ tty_jump(0, TT.screen_height);
+ switch (TT.vi_mode) {
+ case 0:
+ tty_esc("30;44m");
+ printf("COMMAND|");
+ break;
+ case 1:
+ tty_esc("30;42m");
+ printf("NORMAL|");
+ break;
+ case 2:
+ tty_esc("30;41m");
+ printf("INSERT|");
+ break;
+
+ }
+ //DEBUG
+ tty_esc("47m");
+ tty_esc("30m");
+ int i = utf8_len(&c_r->line->str_data[TT.cur_col]);
+ if (i) {
+ char t[5] = {0,0,0,0,0};
+ strncpy(t,&c_r->line->str_data[TT.cur_col],i);
+ printf("utf: %d %s",i,t);
+ }
+ printf("| %d, %d\n",cx_scr,cy_scr); //screen coord
+
+ tty_jump(TT.screen_width-12, TT.screen_height);
+ printf("| %d, %d\n",TT.cur_row,TT.cur_col);
+ tty_esc("37m");
+ tty_esc("40m");
+ if (!TT.vi_mode) {
+ tty_esc("1m");
+ tty_jump(0,TT.screen_height+1);
+ printf("%s",il->str_data);
+ } else tty_jump(cx_scr,cy_scr);
+ xflush();
+
+}
+static void draw_char(char c, int x, int y, int highlight)
+{
+ tty_jump(x,y);
+ if (highlight) {
+ tty_esc("30m"); //foreground black
+ tty_esc("47m"); //background white
+ }
+ printf("%c",c);
+}
+//utf rune draw
+//printf and useless copy could be replaced by direct write() to stdout
+static int draw_rune(char* c,int x,int y, int highlight)
+{
+ int l = utf8_len(c);
+ char t[5] = {0,0,0,0,0};
+ if (!l) return 0;
+ tty_jump(x,y);
+ tty_esc("0m");
+ if (highlight) {
+ tty_esc("30m"); //foreground black
+ tty_esc("47m"); //background white
+ }
+ strncpy(t,c,5);
+ printf("%s",t);
+ tty_esc("0m");
+ return l;
+}
+
+static void check_cursor_bounds()
+{
+ if (c_r->line->str_len-1 < TT.cur_col) {
+ if (c_r->line->str_len == 0)
+ TT.cur_col = 0;
+ else
+ TT.cur_col = c_r->line->str_len-1;
+ }
+}
+
+static void adjust_screen_buffer()
+{
+ //search cursor and screen TODO move this perhaps
+ struct linelist* t = text;
+ int c = -1;
+ int s = -1;
+ int i = 0;
+ for(;;) {
+ i++;
+ if (t == c_r)
+ c = i;
+ if (t == scr_r)
+ s = i;
+ t = t->down;
+ if ( ((c != -1) && (s != -1)) || t == 0)
+ break;
+ }
+ if (c <= s) {
+ scr_r = c_r;
+ }
+ else if ( c > s ) {
+ //should count multiline long strings!
+ int distance = c - s +1;
+ //TODO instead iterate scr_r up and check strlen%screen_width
+ //for each iteration
+ if (distance >= (int)TT.screen_height) {
+ int adj = distance - TT.screen_height;
+ while(adj--) {
+ scr_r = scr_r->down;
+ }
+ }
+ }
+ TT.cur_row = c;
+
+}
+
+//return 0 if not ASCII nor UTF-8
+//this is not fully tested
+//naive implementation with branches
+//there is better branchless lookup table versions out there
+//1 0xxxxxxx
+//2 110xxxxx 10xxxxxx
+//3 1110xxxx 10xxxxxx 10xxxxxx
+//4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+static int utf8_len(char* str)
+{
+ int len=0;
+ uint8_t *c = (uint8_t*)str;
+ if (!c || !(*c)) return 0;
+ if (*c < 0x7F) return 1;
+ if ((*c & 0xE0) == 0xc0) len = 2;
+ else if ((*c & 0xF0) == 0xE0 ) len = 3;
+ else if ((*c & 0xF8) == 0xF0 ) len = 4;
+ else return 0;
+ c++;
+ for(int i = len-1;i>0;i--) {
+ if ((*c++ & 0xc0)!=0x80) return 0;
+ }
+ return len;
+}
+
+static int utf8_dec(char key, char* utf8_scratch,int* sta_p)
+{
+ int len = 0;
+ char* c = utf8_scratch;
+ c[*sta_p] = key;
+ if (!(*sta_p)) *c = key;
+ if (*c < 0x7F) { *sta_p = 1; return 1; }
+ if ((*c & 0xE0) == 0xc0) len = 2;
+ else if ((*c & 0xF0) == 0xE0 ) len = 3;
+ else if ((*c & 0xF8) == 0xF0 ) len = 4;
+ else {*sta_p = 0; return 0; }
+
+ (*sta_p)++;
+
+ if (*sta_p == 1) return 0;
+ if ((c[*sta_p-1] & 0xc0)!=0x80) {*sta_p = 0; return 0; }
+
+ if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
+
+ return 0;
+}
+
+static void cur_left()
+{
+ if (!TT.cur_col) return;
+ TT.cur_col--;
+
+ if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left();
+}
+
+static void cur_right()
+{
+ if (TT.cur_col == c_r->line->str_len-1) return;
+ TT.cur_col++;
+ if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_right();
+}
+
+static void cur_up()
+{
+ if (c_r->up != 0)
+ c_r = c_r->up;
+ if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left();
+ check_cursor_bounds();
+ adjust_screen_buffer();
+}
+
+static void cur_down()
+{
+ if (c_r->down != 0)
+ c_r = c_r->down;
+ if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left();
+ check_cursor_bounds();
+ adjust_screen_buffer();
}