/* 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.
*/
#define FOR_vi
#include "toys.h"
GLOBALS(
int cur_col;
int cur_row;
unsigned screen_height;
unsigned screen_width;
int vi_mode;
)
/*
*
* TODO:
* BUGS: screen pos adjust does not cover "widelines"
*
*
* 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;
};
static void draw_page();
static int draw_str_until(int *drawn, char *str, int width, int bytes);
static void draw_char(char c, int x, int y, int highlight);
//utf8 support
static int utf8_lnw(int* width, char* str, int bytes);
static int utf8_dec(char key, char *utf8_scratch, int *sta_p) ;
static int utf8_len(char *str);
static int utf8_width(char *str, int bytes);
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)
{
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;
}
// Add an entry to the end of a doubly linked list
struct double_list *dlist_insert(struct double_list **list, char *data)
{
struct double_list *new = xmalloc(sizeof(struct double_list));
new->data = data;
dlist_insert_nomalloc(list, new);
return new;
}
void linelist_unload()
{
}
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) {
char *line = xzalloc(80);
ssize_t alc = 80;
lst = (struct linelist*)dlist_add((struct double_list**)&lst,
xzalloc(sizeof(struct str_line)));
lst->line->alloc_len = alc;
lst->line->str_len = 0;
lst->line->str_data = line;
text = lst;
dlist_terminate(text->up);
return 1;
}
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;
int i = 0;
val = strtol(cmd, &cmd_e, 10);
if (errno || val == 0) {
val = 1;
}
else {
cmd = cmd_e;
}
for (; 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':
if (c_r && c_r->line->str_len)
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 && 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;
int utf_l = 0;
char* line = 0;
int bytes = 0;
int drawn = 0;
int x = 0;
struct linelist *scr_buf= scr_r;
//clear screen
tty_esc("2J");
tty_esc("H");
tty_jump(0, 0);
//draw lines until cursor row
for (; y < TT.screen_height; ) {
if (line && bytes) {
draw_str_until(&drawn, line, TT.screen_width, bytes);
bytes = drawn ? (bytes-drawn) : 0;
line = bytes ? (line+drawn) : 0;
y++;
tty_jump(0, y);
} else if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
if (scr_buf == c_r)
break;
line = scr_buf->line->str_data;
bytes = scr_buf->line->str_len;
scr_buf = scr_buf->down;
} else {
if (scr_buf == c_r)
break;
y++;
tty_jump(0, y);
//printf(" \n");
if (scr_buf) scr_buf = scr_buf->down;
}
}
//draw cursor row until cursor
//this is to calculate cursor position on screen and possible insert
line = scr_buf->line->str_data;
bytes = TT.cur_col;
for (; y < TT.screen_height; ) {
if (bytes) {
x = draw_str_until(&drawn, line, TT.screen_width, bytes);
bytes = drawn ? (bytes-drawn) : 0;
line = bytes ? (line+drawn) : 0;
}
if (!bytes) break;
y++;
tty_jump(0, y);
}
if (TT.vi_mode == 2 && il->str_len) {
line = il->str_data;
bytes = il->str_len;
cx_scr = x;
cy_scr = y;
x = draw_str_until(&drawn, line, TT.screen_width-x, bytes);
bytes = drawn ? (bytes-drawn) : 0;
line = bytes ? (line+drawn) : 0;
cx_scr += x;
for (; y < TT.screen_height; ) {
if (bytes) {
x = draw_str_until(&drawn, line, TT.screen_width, bytes);
bytes = drawn ? (bytes-drawn) : 0;
line = bytes ? (line+drawn) : 0;
cx_scr = x;
}
if (!bytes) break;
y++;
cy_scr = y;
tty_jump(0, y);
}
} else {
cy_scr = y;
cx_scr = x;
}
line = scr_buf->line->str_data+TT.cur_col;
bytes = scr_buf->line->str_len-TT.cur_col;
scr_buf = scr_buf->down;
x = draw_str_until(&drawn,line, TT.screen_width-x, bytes);
bytes = drawn ? (bytes-drawn) : 0;
line = bytes ? (line+drawn) : 0;
y++;
tty_jump(0, y);
//draw until end
for (; y < TT.screen_height; ) {
if (line && bytes) {
draw_str_until(&drawn, line, TT.screen_width, bytes);
bytes = drawn ? (bytes-drawn) : 0;
line = bytes ? (line+drawn) : 0;
y++;
tty_jump(0, y);
} else if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) {
line = scr_buf->line->str_data;
bytes = scr_buf->line->str_len;
scr_buf = scr_buf->down;
} else {
y++;
tty_jump(0, y);
if (scr_buf) scr_buf = scr_buf->down;
}
}
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");
utf_l = utf8_len(&c_r->line->str_data[TT.cur_col]);
if (utf_l) {
char t[5] = {0, 0, 0, 0, 0};
strncpy(t, &c_r->line->str_data[TT.cur_col], utf_l);
printf("utf: %d %s", utf_l, 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);
tty_esc("0m");
} 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;
int i = 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 (i = len-1; i > 0; i--) {
if ((*c++ & 0xc0) != 0x80) return 0;
}
return len;
}
//get utf8 length and width at same time
static int utf8_lnw(int* width, char* str, int bytes)
{
wchar_t wc;
int length = 1;
*width = 1;
// if (str < 0x7F) return length;
length = mbtowc(&wc, str, bytes);
switch (length) {
case -1:
mbtowc(0,0,4);
case 0:
*width = 0;
length = 0;
break;
default:
*width = wcwidth(wc);
}
return length;
}
//try to estimate width of next "glyph" in terminal buffer
//combining chars 0x300-0x36F shall be zero width
static int utf8_width(char *str, int bytes)
{
wchar_t wc;
switch (mbtowc(&wc, str, bytes)) {
case -1:
mbtowc(0,0,4);
case 0:
return -1;
default:
return wcwidth(wc);
}
return 0;
}
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 int draw_str_until(int *drawn, char *str, int width, int bytes)
{
int rune_width = 0;
int rune_bytes = 0;
int max_bytes = bytes;
int max_width = width;
char* end = str;
for (;width && bytes;) {
rune_bytes = utf8_lnw(&rune_width, end, 4);
if (!rune_bytes) break;
if (width - rune_width < 0) goto write_bytes;
width -= rune_width;
bytes -= rune_bytes;
end += rune_bytes;
}
for (;bytes;) {
rune_bytes = utf8_lnw(&rune_width, end, 4);
if (!rune_bytes) break;
if (rune_width) break;
bytes -= rune_bytes;
end += rune_bytes;
}
write_bytes:
fwrite(str, max_bytes-bytes, 1, stdout);
*drawn = max_bytes-bytes;
return max_width-width;
}
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 (c_r->line->str_len <= 1) return;
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();
}