aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/vi.c
diff options
context:
space:
mode:
Diffstat (limited to 'toys/pending/vi.c')
-rw-r--r--toys/pending/vi.c273
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;
}