#include #include #include #include #include #include #define ARENA_IMPL #define UTF8_IMPL #include "wrmr.h" #include "arena.h" #include "txt.h" #include "vui.h" #include "utf8.h" Arena scratch = { 0 }; Txt txt = { 0 }; u32 cur = 0; int mode = 0; u32 count = 0; int is_space(u32 c) { return c <= 0x20 && c != 0; } u32 move_word_back(Txt *t, u32 cur) { TxtLoc l = txt_at(t, cur); while (!txt_at_start(t,l)) { TxtLoc n = txt_prev(t, l); if (!is_space(txt_chr(t, n))) break; l = n; } while (!txt_at_start(t,l)) { TxtLoc n = txt_prev(t, l); if (is_space(txt_chr(t, n))) break; l = n; } return txt_ofs(t, l); } u32 move_word_fwd(Txt *t, u32 cur) { TxtLoc l = txt_at(t, cur); while (!txt_at_end(t,l) && is_space(txt_chr(t, l))) l = txt_next(t, l); while (!txt_at_end(t,l) && !is_space(txt_chr(t, l))) l = txt_next(t, l); return txt_ofs(t, l); } u32 move_char_back(Txt *t, u32 cur) { while (cur > 0) { cur--; u8 c = txt_byte(t, txt_at(t, cur)); if ((c & 0xc0) != 0x80) break; } return cur; } u32 move_char_fwd(Txt *t, u32 cur) { u8 b = txt_byte(t, txt_at(t, cur)); u32 n = stdc_leading_ones(b); if (cur + n >= t->len) return t->len; return cur + n + !n; } u32 del_between(Txt *t, u32 a, u32 b) { if (b < a) { u32 t = a; a = b; b = t; } //if (b - a > 10) TRAP(); return txt_delete(t, b, b - a); } TxtLoc next_newline(Txt *t, TxtLoc l) { do l = txt_next(t, l); while (!txt_at_end(t, l) && txt_byte(t, l) != '\n'); return l; } TxtLoc prev_newline(Txt *t, TxtLoc l) { do l = txt_prev(t, l); while (!txt_at_start(t, l) && txt_byte(t, l) != '\n'); return l; } TxtLoc start_of_line(Txt *t, TxtLoc l) { l = prev_newline(t, l); if (!txt_at_start(t,l) && txt_byte(t, l) == '\n') l = txt_next(t, l); return l; } TxtLoc end_of_line(Txt *t, TxtLoc l) { return next_newline(t, start_of_line(t, l)); } u32 get_col(Txt *t, TxtLoc l) { u32 n = 0; for (TxtLoc tmp = start_of_line(t, l); txt_before(tmp, l); txt_chr_next(t, &tmp)) n++; return n; } TxtLoc at_col(Txt *t, TxtLoc l, u32 col) { l = start_of_line(t, l); while (col--) { if (txt_chr_next(t, &l) == '\n') { l = txt_prev(t, l); break; } } return l; } TxtLoc prev_line(Txt *t, TxtLoc l) { TxtLoc start = start_of_line(t, l); return at_col(t, txt_prev(t, start), get_col(t, l)); } TxtLoc next_line(Txt *t, TxtLoc l) { TxtLoc end = end_of_line(t, l); if (txt_at_end(t, end)) return end; return at_col(t, txt_next(t, end), get_col(t, l)); } u32 move_line_up(Txt *t, u32 cur) { return txt_ofs(t, prev_line(t, txt_at(t, cur))); } u32 move_line_down(Txt *t, u32 cur) { return txt_ofs(t, next_line(t, txt_at(t, cur))); } int empty_line(Txt *t, TxtLoc l) { u8 b = txt_byte(t, start_of_line(t, l)); return b == '\n' || b == 0 /* last line of buffer */; } TxtLoc next_par(Txt *t, TxtLoc l) { while (!txt_at_end(t, l) && empty_line(t, l)) l = next_line(t, l); while (!txt_at_end(t, l) && !empty_line(t, l)) l = next_line(t, l); return l; } TxtLoc prev_par(Txt *t, TxtLoc l) { while (!txt_at_start(t, l) && empty_line(t, l)) l = prev_line(t, l); while (!txt_at_start(t, l) && !empty_line(t, l)) l = prev_line(t, l); return l; } u32 move_par_up(Txt *t, u32 cur) { return txt_ofs(t, prev_par(t, txt_at(t, cur))); } u32 move_par_down(Txt *t, u32 cur) { return txt_ofs(t, next_par(t, txt_at(t, cur))); } /* main */ #define ODD_ATTR (FG_CYAN | BG_BLACK) #define EVEN_ATTR (FG_WHITE | BG_BLACK) void find_view_window(Txt *t, u32 curs, TxtLoc *start, TxtLoc *end) { TxtLoc l = txt_at(t, curs); u32 u = LINES / 2; TxtLoc a = l; for (u32 i = 0; i < u; i++) a = prev_newline(t, a); u32 n = 0; TxtLoc b = a; while (!txt_at_end(t, b) && n++ < LINES) b = next_newline(t, b); while (!txt_at_start(t, a) && n++ < LINES) a = prev_newline(t, a); if (!txt_at_start(t,a) && txt_byte(t,a) == '\n') a = txt_next(t, a); *start = a; *end = b; } void draw(void *ctx) { (void)ctx; vui_clear(); int x = 0, y = 0; TxtLoc start, end; find_view_window(&txt, cur, &start, &end); vui_aprintf(-1, 0, ODD_ATTR, "%u piece(s)", txt.ptbl.n); for (u32 i = 0; i < txt.ptbl.n; i++) { TxtPiece *p = &txt.ptbl.v[i]; VuiAttr a = i&1 ? ODD_ATTR : EVEN_ATTR; vui_aprintf(-1, i+1, a, "%u, %u (%s)", p->ofs, p->n, p->buf == TXT_ADD ? "add" : "src"); } TxtLoc l = txt_at(&txt, cur); int cur_found = 0; while (txt_before(start, end)) { if (l.p == start.p && l.i == start.i) { cur_found = 1; vui_curs_pos(x, y); } u32 c = txt_chr_next(&txt, &start); if (c == '\n') { x = 0; y++; } else if (c == '\t') { x++; x += (-x & 7); } else if (c) { vui_chr(x++, y, c); } } ASSERT(start.i <= txt.ptbl.v[start.p].n); if (!cur_found) vui_curs_pos(x, y); u32 c = txt_chr(&txt, l); vui_printf(-1, -4, "%u", count); vui_aprintf(-1, -3, mode ? FG_BCYAN : FG_CYAN, "%s", mode ? "INSERT" : "NORMAL"); vui_printf(-1, -1, "%u - %u.%u - %02x (%c)", cur, l.p, l.i, c, (c < 0x20 || c > 0x7e) ? ' ' : c); u32 used = scratch.beg - scratch.start, max = scratch.end - scratch.start; vui_printf(-1, -2, "scratch %.02f/%.02fk", used/1024.0, max/1024.0); } int motion(u32 c) { u32 last_cur = cur; loop: switch (c) { case KEY_LEFT: case 'h': cur = move_char_back(&txt, cur); break; case KEY_LEFT | KEY_CTRL_BIT: case 'b': cur = move_word_back(&txt, cur); break; case KEY_RIGHT: case 'l': cur = move_char_fwd(&txt, cur); break; case KEY_RIGHT | KEY_CTRL_BIT: case 'w': cur = move_word_fwd(&txt, cur); break; case KEY_UP: case 'k': cur = move_line_up(&txt, cur); break; case KEY_DOWN: case 'j': cur = move_line_down(&txt, cur); break; case KEY_UP | KEY_CTRL_BIT: case '{': cur = move_par_up(&txt, cur); break; case KEY_DOWN | KEY_CTRL_BIT: case '}': cur = move_par_down(&txt, cur); break; case KEY_PGUP: for (u32 i = 0; i < LINES; i += 3) cur = move_line_up(&txt, cur); break; case KEY_PGDN: for (u32 i = 0; i < LINES; i += 3) cur = move_line_down(&txt, cur); break; case KEY_HOME: case '^': case '0': cur = txt_ofs(&txt, start_of_line(&txt, txt_at(&txt, cur))); break; case KEY_END: case '$': cur = txt_ofs(&txt, next_newline(&txt, txt_at(&txt, cur))); break; case KEY_HOME | KEY_CTRL_BIT: case 'g': cur = 0; break; case KEY_END | KEY_CTRL_BIT: case 'G': cur = txt.len; break; case 'f': { u32 n = vui_key(); TxtLoc l = txt_next(&txt, txt_at(&txt, cur)); for (TxtLoc t = l;;) { u32 c = txt_chr_next(&txt, &t); if (c == '\n') break; if (c == n) { cur = txt_ofs(&txt, l); break; } l = t; } } break; case 'F': { u32 n = vui_key(); TxtLoc l = txt_prev(&txt, txt_at(&txt, cur)); for (;;) { u32 c = txt_chr(&txt, l); if (c == '\n') break; if (c == n) { cur = txt_ofs(&txt, l); break; } l = txt_prev(&txt, l); } } break; default: return 0; } if (count > 1 && last_cur != cur) { count--; goto loop; } count = 0; return 1; } int main(int argc, const char **argv) { scratch = arena_init(1L << 20); vui_init(); vui_curs_vis(1); vui_redraw_fn(draw); const char *path = "test.txt"; if (argc > 1) path = argv[1]; if (txt_load(&txt, path)) err(1, "couldn't open file"); cur = txt.len; for (;;) { scratch.beg = scratch.start; draw(NULL); vui_blit(); u32 c = vui_key(); switch (mode) { case 0: switch (c) { case 'q': goto brk; case 'z': case 'Z': case 0x13 /* ^S */: txt_save(&txt, path); //txt_load(&txt, "test.txt"); if (c == 'Z') goto brk; break; case 'i': mode = 1; break; case 'a': { mode = 1; TxtLoc l = txt_at(&txt, cur); TxtLoc e = end_of_line(&txt, l); l = txt_before(l, e) ? txt_next(&txt, l) : e; cur = txt_ofs(&txt, l); } break; case 'I': mode = 1; cur = txt_ofs(&txt, start_of_line(&txt, txt_at(&txt, cur))); break; case 'A': mode = 1; cur = txt_ofs(&txt, end_of_line(&txt, txt_at(&txt, cur))); break; case 'o': mode = 1; cur = txt_ofs(&txt, end_of_line(&txt, txt_at(&txt, cur))); goto ins_newline; case 'O': mode = 1; cur = txt_ofs(&txt, txt_prev(&txt, start_of_line(&txt, txt_at(&txt, cur)))); goto ins_newline; case 'd': { u32 before = cur; if (motion(vui_key())) { del_between(&txt, before, cur); if (before < cur) cur = before; } } break; case 'D': { u32 end = txt_ofs(&txt, end_of_line(&txt, txt_at(&txt, cur))); del_between(&txt, cur, end); } break; case 'C': { u32 end = txt_ofs(&txt, end_of_line(&txt, txt_at(&txt, cur))); del_between(&txt, cur, end); mode = 1; } break; case 'S': { TxtLoc l = start_of_line(&txt, txt_at(&txt, cur)); u32 end = txt_ofs(&txt, end_of_line(&txt, l)); del_between(&txt, txt_ofs(&txt, l), end); cur = txt_ofs(&txt, l); mode = 1; } break; case 'c': { u32 before = cur; if (motion(vui_key())) { del_between(&txt, before, cur); if (before < cur) cur = before; } mode = 1; break; } case 'x': txt_delete_c(&txt, txt_ofs(&txt, txt_next(&txt, txt_at(&txt, cur)))); break; case 's': txt_delete_c(&txt, txt_ofs(&txt, txt_next(&txt, txt_at(&txt, cur)))); mode = 1; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': add_to_count: count = (count % 100000000) * 10 + c - '0'; break; case '0': if (count) goto add_to_count; /* fallthrough */ default: if (!motion(c)) { /* TODO: bell/flash? */ } break; } break; case 1: switch (c) { case KEY_ESC: if (txt_after(txt_at(&txt, cur), start_of_line(&txt, txt_at(&txt, cur)))) cur--; mode = 0; break; case KEY_BKSP: cur = txt_delete_c(&txt, cur); break; case 0x17 /* ^W */: cur = del_between(&txt, move_word_back(&txt, cur), cur); break; case 0x05 /* ^E */: cur = del_between(&txt, cur, move_word_fwd(&txt, cur)); break; case '\r': { ins_newline:; u32 tabs = 0; TxtLoc start = start_of_line(&txt, txt_at(&txt, cur)); for (TxtLoc l = start; txt_byte(&txt, l) == '\t'; l = txt_next(&txt, l)) tabs++; while (txt_byte(&txt, txt_at(&txt, cur - 1)) == '\t') { cur = txt_delete(&txt, cur, 1); } cur = txt_insert_c(&txt, cur, '\n'); while (tabs--) cur = txt_insert_c(&txt, cur, '\t'); } break; default: if ((c == '\t' || c >= ' ') && c <= KEY_UTF8_MAX) cur = txt_insert_c(&txt, cur, c); break; } break; } if (mode == 1) count = 0; } brk: vui_fini(); arena_free(&scratch); return 0; }