diff options
author | Jarno Mäkipää <jmakip87@gmail.com> | 2019-03-23 14:02:04 -0500 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2019-03-23 14:02:04 -0500 |
commit | 32c617d476e5edcc70c4918ed2d451d0c27597cb (patch) | |
tree | aa54685e960b769dc8965428808270ff8e1bdec1 /toys | |
parent | d1598daf209b6804e1131ae43e3a9315a334f553 (diff) | |
download | toybox-32c617d476e5edcc70c4918ed2d451d0c27597cb.tar.gz |
vi.c double_list based implementation.
Has beginnings of reading file, saving file, hjkl movement, insert, ex
(only w, wq, q!), search with /, some other normal mode actions (dd, w, b,
e), some utf8 support
Everything is still very unfinished and partly behaves wrongly comparing
to original vi. But simple tasks like modifying short config files
should be possible.
Some things like draw_page needs serious refactor since it now writes
whole screen after every keypress. Didint bother to refactor yet if
linked list needs to be replaced with something else...
Diffstat (limited to 'toys')
-rw-r--r-- | toys/pending/vi.c | 893 |
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(); } |