diff options
Diffstat (limited to 'toys/pending/vi.c')
-rw-r--r-- | toys/pending/vi.c | 273 |
1 files changed, 154 insertions, 119 deletions
diff --git a/toys/pending/vi.c b/toys/pending/vi.c index bbb0a90b..9e743dc3 100644 --- a/toys/pending/vi.c +++ b/toys/pending/vi.c @@ -12,9 +12,11 @@ config VI default n help usage: vi [-s script] FILE - -s script: run script file + Visual text editor. Predates the existence of standardized cursor keys, so the controls are weird and historical. + + -s script: run script file */ #define FOR_vi @@ -22,6 +24,8 @@ config VI GLOBALS( char *s; + + char *filename; int vi_mode, tabstop, list; int cur_col, cur_row, scr_row; int drawn_row, drawn_col; @@ -41,13 +45,9 @@ GLOBALS( char* data; } yank; - int modified; size_t filesize; // mem_block contains RO data that is either original file as mmap // or heap allocated inserted data -// -// -// struct block_list { struct block_list *next, *prev; struct mem_block { @@ -122,6 +122,7 @@ static char* utf8_last(char* str, int size) { char* end = str+size; int pos = size, len, width = 0; + for (;pos >= 0; end--, pos--) { len = utf8_lnw(&width, end, size-pos); if (len && width) return end; @@ -168,6 +169,7 @@ static int insert_str(const char *data, size_t offset, size_t size, size_t len, struct mem_block *b = xmalloc(sizeof(struct mem_block)); struct slice *next = xmalloc(sizeof(struct slice)); struct slice_list *s = TT.slices; + b->size = size; b->len = len; b->alloc = type; @@ -216,9 +218,7 @@ static int insert_str(const char *data, size_t offset, size_t size, size_t len, (char *)next); } else { // insert after - s = (struct slice_list *)dlist_add_after((struct double_list **)&TT.slices, - (struct double_list **)&s, - (char *)next); + s = (void *)dlist_add_after((void *)&TT.slices, (void *)&s, (void *)next); } } return 0; @@ -231,6 +231,7 @@ static int cut_str(size_t offset, size_t len) struct slice_list *e, *s = TT.slices; size_t end = offset+len; size_t epos, spos = 0; + if (!s) return -1; //find start and end slices @@ -290,6 +291,17 @@ static int cut_str(size_t offset, size_t len) return 0; } +static int modified() +{ + if (TT.text->next != TT.text->prev) return 1; + if (TT.slices->next != TT.slices->prev) return 1; + if (!TT.text || !TT.slices) return 0; + if (!TT.text->node || !TT.slices->node) return 0; + if (TT.text->node->alloc != MMAP) return 1; + if (TT.text->node->len != TT.slices->node->len) return 1; + if (!TT.text->node->len) return 1; + return 0; +} //find offset position in slices static struct slice_list *slice_offset(size_t *start, size_t offset) @@ -356,11 +368,10 @@ static size_t text_filesize() { struct slice_list *s = TT.slices; size_t pos = 0; - if (s) do { + if (s) do { pos += s->node->len; s = s->next; - } while (s != TT.slices); return pos; @@ -390,6 +401,7 @@ static char text_byte(size_t offset) { struct slice_list *s = TT.slices; size_t spos = 0; + //find start if (!(s = slice_offset(&spos, offset))) return 0; return s->node->data[offset-spos]; @@ -414,6 +426,7 @@ static int text_codepoint(char *dest, size_t offset) static size_t text_sol(size_t offset) { size_t pos; + if (!TT.filesize || !offset) return 0; else if (TT.filesize <= offset) return TT.filesize-1; else if ((pos = text_strrchr(offset-1, '\n')) == SIZE_MAX) return 0; @@ -484,6 +497,7 @@ static size_t text_strstr(size_t offset, char *str) { size_t bytes, pos = offset; char *s = 0; + do { bytes = text_getline(toybuf, pos, ARRAY_LEN(toybuf)); if (!bytes) pos++; //empty line @@ -505,6 +519,22 @@ static void block_list_free(void *node) free(d); } +static void show_error(char *fmt, ...) +{ + va_list va; + + printf("\a\e[%dH\e[41m\e[37m\e[K\e[1m", TT.screen_height+1); + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + printf("\e[0m"); + xflush(1); + + // TODO: better integration with status line: remove sleep and keep + // message until next operation. + sleep(1); +} + static void linelist_unload() { llist_traverse((void *)TT.slices, llist_free_double); @@ -512,53 +542,70 @@ static void linelist_unload() TT.slices = 0, TT.text = 0; } -static int linelist_load(char *filename) +static void linelist_load(char *filename, int ignore_missing) { - if (!filename) filename = (char*)*toys.optargs; + int fd; + long long size; - if (filename) { - int fd = open(filename, O_RDONLY); - long long size; - - if (fd == -1 || !(size = fdlength(fd))) { - insert_str("", 0, 0, 0, STACK); - TT.filesize = 0; + if (!filename) filename = TT.filename; + if (!filename) { + // `vi` with no arguments creates a new unnamed file. + insert_str(xstrdup("\n"), 0, 1, 1, HEAP); + return; + } - return 0; - } - insert_str(xmmap(0, size, PROT_READ, MAP_SHARED, fd, 0), 0, size,size,MMAP); - xclose(fd); - TT.filesize = text_filesize(); + fd = open(filename, O_RDONLY); + if (fd == -1) { + if (!ignore_missing) + show_error("Couldn't open \"%s\" for reading: %s", filename, + strerror(errno)); + insert_str(xstrdup("\n"), 0, 1, 1, HEAP); + return; } - return 1; + size = fdlength(fd); + if (size > 0) { + insert_str(xmmap(0,size,PROT_READ,MAP_SHARED,fd,0), 0, size, size, MMAP); + TT.filesize = text_filesize(); + } else if (!size) insert_str(xstrdup("\n"), 0, 1, 1, HEAP); + xclose(fd); } -static void write_file(char *filename) +static int write_file(char *filename) { struct slice_list *s = TT.slices; struct stat st; int fd = 0; - if (!s) return; - if (!filename) filename = (char*)*toys.optargs; + if (!modified()) show_error("Not modified"); + if (!filename) filename = TT.filename; + if (!filename) { + show_error("No file name"); + return -1; + } + + if (stat(filename, &st) == -1) st.st_mode = 0644; sprintf(toybuf, "%s.swp", filename); - if ( (fd = xopen(toybuf, O_WRONLY | O_CREAT | O_TRUNC)) <0) return; + if ((fd = open(toybuf, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode)) == -1) { + show_error("Couldn't open \"%s\" for writing: %s", toybuf, strerror(errno)); + return -1; + } - do { - xwrite(fd, (void *)s->node->data, s->node->len ); - s = s->next; - } while (s != TT.slices); + if (s) { + do { + xwrite(fd, (void *)s->node->data, s->node->len); + s = s->next; + } while (s != TT.slices); + } linelist_unload(); xclose(fd); - if (!stat(filename, &st)) chmod(toybuf, st.st_mode); - else chmod(toybuf, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); xrename(toybuf, filename); - linelist_load(filename); + linelist_load(filename, 0); + return 0; } //jump into valid offset index @@ -567,8 +614,8 @@ static void check_cursor_bounds() { char buf[8] = {0}; int len, width = 0; - if (!TT.filesize) TT.cursor = 0; + if (!TT.filesize) TT.cursor = 0; for (;;) { if (TT.cursor < 1) { TT.cursor = 0; @@ -577,11 +624,9 @@ static void check_cursor_bounds() TT.cursor = TT.filesize-1; return; } - if ((len = text_codepoint(buf, TT.cursor)) < 1) { - TT.cursor--; //we are not in valid data try jump over - continue; - } - if (utf8_lnw(&width, buf, len) && width) break; + // if we are not in valid data try jump over + if ((len = text_codepoint(buf, TT.cursor)) < 1) TT.cursor--; + else if (utf8_lnw(&width, buf, len) && width) break; else TT.cursor--; //combine char jump over } } @@ -635,7 +680,6 @@ static void adjust_screen_buffer() TT.scr_row = s; TT.cur_row = c; - } //TODO search yank buffer by register @@ -672,8 +716,7 @@ static int vi_delete(char reg, size_t from, int flags) vi_yank(reg, from, flags); - if (TT.vi_mov_flag&0x80000000) - start = TT.cursor, end = from; + if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from; //pre adjust cursor move one right until at next valid rune if (TT.vi_mov_flag&2) { @@ -702,6 +745,7 @@ static int vi_change(char reg, size_t to, int flags) static int cur_left(int count0, int count1, char *unused) { int count = count0*count1; + TT.vi_mov_flag |= 0x80000000; for (;count && TT.cursor; count--) { TT.cursor--; @@ -741,8 +785,8 @@ static int cur_right(int count0, int count1, char *unused) static int cur_up(int count0, int count1, char *unused) { int count = count0*count1; - for (;count--;) TT.cursor = text_psol(TT.cursor); + for (;count--;) TT.cursor = text_psol(TT.cursor); TT.vi_mov_flag |= 0x80000000; check_cursor_bounds(); return 1; @@ -752,6 +796,7 @@ static int cur_up(int count0, int count1, char *unused) static int cur_down(int count0, int count1, char *unused) { int count = count0*count1; + for (;count--;) TT.cursor = text_nsol(TT.cursor); check_cursor_bounds(); return 1; @@ -836,6 +881,18 @@ static int vi_x(char reg, int count0, int count1) return 1; } +static int backspace(char reg, int count0, int count1) +{ + size_t from = 0; + size_t to = TT.cursor; + cur_left(1, 1, 0); + from = TT.cursor; + if (from != to) + vi_delete(reg, to, 0); + check_cursor_bounds(); + return 1; +} + static int vi_movw(int count0, int count1, char *unused) { int count = count0*count1; @@ -1011,8 +1068,7 @@ static int vi_push(char reg, int count0, int count1) //vi inconsistancy //if yank ends with \n push is linemode else push in place+1 size_t history = TT.cursor; - char *start = TT.yank.data; - char *eol = strchr(start, '\n'); + char *start = TT.yank.data, *eol = strchr(start, '\n'); if (start[strlen(start)-1] == '\n') { if ((TT.cursor = text_strchr(TT.cursor, '\n')) == SIZE_MAX) @@ -1133,24 +1189,12 @@ static int vi_find_next(char reg, int count0, int count1) // have special cases such as dd, yy, also movements can work without // CMD //ex commands can be even more complicated than this.... -// -struct vi_cmd_param { - const char* cmd; - unsigned flags; - int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS -}; -struct vi_mov_param { - const char* mov; - unsigned flags; - int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params -}; + //special cases without MOV and such struct vi_special_param { const char *cmd; int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1 -}; -struct vi_special_param vi_special[] = -{ +} vi_special[] = { {"D", &vi_D}, {"I", &vi_I}, {"J", &vi_join}, @@ -1162,11 +1206,12 @@ struct vi_special_param vi_special[] = {"dd", &vi_dd}, {"yy", &vi_yy}, }; -//there is around ~47 vi moves -//some of them need extra params -//such as f and ' -struct vi_mov_param vi_movs[] = -{ +//there is around ~47 vi moves, some of them need extra params such as f and ' +struct vi_mov_param { + const char* mov; + unsigned flags; + int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params +} vi_movs[] = { {"0", 0, &vi_zero}, {"b", 0, &vi_movb}, {"e", 0, &vi_move}, @@ -1188,8 +1233,11 @@ struct vi_mov_param vi_movs[] = //also dw stops at w position and cw seem to stop at e pos+1... //so after movement we need to possibly set up some flags before executing //command, and command needs to adjust... -struct vi_cmd_param vi_cmds[] = -{ +struct vi_cmd_param { + const char* cmd; + unsigned flags; + int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS +} vi_cmds[] = { {"c", 1, &vi_change}, {"d", 1, &vi_delete}, {"y", 1, &vi_yank}, @@ -1199,16 +1247,14 @@ static int run_vi_cmd(char *cmd) { int i = 0, val = 0; char *cmd_e; - int (*vi_cmd)(char, size_t, int) = 0; - int (*vi_mov)(int, int, char*) = 0; + int (*vi_cmd)(char, size_t, int) = 0, (*vi_mov)(int, int, char*) = 0; TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0; TT.vi_reg = '"'; if (*cmd == '"') { cmd++; - TT.vi_reg = *cmd; //TODO check validity - cmd++; + TT.vi_reg = *cmd++; //TODO check validity } errno = 0; val = strtol(cmd, &cmd_e, 10); @@ -1216,11 +1262,9 @@ static int run_vi_cmd(char *cmd) else cmd = cmd_e; TT.count0 = val; - for (i = 0; i < ARRAY_LEN(vi_special); i++) { - if (strstr(cmd, vi_special[i].cmd)) { + for (i = 0; i < ARRAY_LEN(vi_special); i++) + if (strstr(cmd, vi_special[i].cmd)) return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1); - } - } for (i = 0; i < ARRAY_LEN(vi_cmds); i++) { if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) { @@ -1255,39 +1299,31 @@ static int run_vi_cmd(char *cmd) } +// Return non-zero to exit. static int run_ex_cmd(char *cmd) { - if (cmd[0] == '/') { - search_str(&cmd[1]); - } else if (cmd[0] == '?') { + if (cmd[0] == '/') search_str(cmd+1); + else if (cmd[0] == '?') { // TODO: backwards search. } else if (cmd[0] == ':') { if (!strcmp(&cmd[1], "q") || !strcmp(&cmd[1], "q!")) { - // TODO: if no !, check whether file modified. - //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; - } - else if (strstr(&cmd[1], "set list")) { + if (cmd[2] != '!' && modified()) + show_error("Unsaved changes (\"q!\" to ignore)"); + else return 1; + } else if (strstr(cmd+1, "w ")) write_file(&cmd[3]); + else if (strstr(cmd+1, "wq")) { + if (!write_file(0)) return 1; + show_error("Unsaved changes (\"q!\" to ignore)"); + } else if (strstr(cmd+1, "w")) write_file(0); + else if (strstr(cmd+1, "set list")) { TT.list = 1; TT.vi_mov_flag |= 0x30000000; - return 1; - } - else if (strstr(&cmd[1], "set nolist")) { + } else if (strstr(cmd+1, "set nolist")) { TT.list = 0; TT.vi_mov_flag |= 0x30000000; - return 1; } } return 0; - } static int vi_crunch(FILE *out, int cols, int wc) @@ -1344,21 +1380,12 @@ static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore, static void draw_page() { unsigned y = 0; - int x = 0; - + int x = 0, bytes = 0; char *line = 0, *end = 0; - int bytes = 0; - //screen coordinates for cursor int cy_scr = 0, cx_scr = 0; - //variables used only for cursor handling - int aw = 0, iw = 0, clip = 0, margin = 8; - - int scroll = 0, redraw = 0; - - int SSOL, SOL; - + int aw = 0, iw = 0, clip = 0, margin = 8, scroll = 0, redraw = 0, SSOL, SOL; adjust_screen_buffer(); //redraw = 3; //force full redraw @@ -1493,7 +1520,7 @@ static void draw_page() void vi_main(void) { - char stdout_buf[BUFSIZ]; + char stdout_buf[8192]; char keybuf[16] = {0}; char vi_buf[16] = {0}; char utf8_code[8] = {0}; @@ -1506,7 +1533,8 @@ void vi_main(void) TT.il->alloc = 80, TT.yank.alloc = 128; - linelist_load(0); + TT.filename = *toys.optargs; + linelist_load(0, 1); TT.vi_mov_flag = 0x20000000; TT.vi_mode = 1, TT.tabstop = 8; @@ -1516,7 +1544,7 @@ void vi_main(void) TT.screen_height -= 1; // Avoid flicker. - setbuf(stdout, stdout_buf); + setbuffer(stdout, stdout_buf, sizeof(stdout_buf)); xsignal(SIGWINCH, generic_signal); set_terminal(0, 1, 0, 0); @@ -1548,6 +1576,12 @@ void vi_main(void) // TODO: support cursor keys in ex mode too. if (TT.vi_mode && key>=256) { key -= 256; + //if handling arrow keys insert what ever is in input buffer before moving + if (TT.il->len) { + i_insert(TT.il->data, TT.il->len); + TT.il->len = 0; + memset(TT.il->data, 0, TT.il->alloc); + } if (key==KEY_UP) cur_up(1, 1, 0); else if (key==KEY_DOWN) cur_down(1, 1, 0); else if (key==KEY_LEFT) cur_left(1, 1, 0); @@ -1594,6 +1628,10 @@ void vi_main(void) vi_buf[0] = 0; vi_buf_pos = 0; break; + case 0x7F: //FALLTHROUGH + case 0x08: + backspace(TT.vi_reg, 1, 1); + break; default: if (key > 0x20 && key < 0x7B) { vi_buf[vi_buf_pos] = key;//TODO handle input better @@ -1627,8 +1665,7 @@ void vi_main(void) break; case 0x0A: case 0x0D: - if (run_ex_cmd(TT.il->data) == -1) - goto cleanup_vi; + if (run_ex_cmd(TT.il->data)) goto cleanup_vi; TT.vi_mode = 1; TT.il->len = 0; memset(TT.il->data, 0, TT.il->alloc); @@ -1660,12 +1697,11 @@ void vi_main(void) int shrink = strlen(last); memset(last, 0, shrink); TT.il->len -= shrink; - } + } else backspace(TT.vi_reg, 1, 1); break; case 0x0A: case 0x0D: //insert newline - // TT.il->data[TT.il->len++] = '\n'; i_insert(TT.il->data, TT.il->len); TT.il->len = 0; @@ -1673,8 +1709,8 @@ void vi_main(void) break; default: if ((key >= 0x20 || key == 0x09) && - utf8_dec(key, utf8_code, &utf8_dec_p)) { - + utf8_dec(key, utf8_code, &utf8_dec_p)) + { if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) { TT.il->data = realloc(TT.il->data, TT.il->alloc*2); TT.il->alloc *= 2; @@ -1683,7 +1719,6 @@ void vi_main(void) TT.il->len += utf8_dec_p; utf8_dec_p = 0; *utf8_code = 0; - } break; } |