From 4144c47d9e927cb822816158812327185056bbbe Mon Sep 17 00:00:00 2001 From: WormHeamer Date: Thu, 1 Jan 2026 17:42:35 -0500 Subject: fuzzy file select, try to deduplicate open buffers --- main.c | 534 +++++++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 358 insertions(+), 176 deletions(-) diff --git a/main.c b/main.c index 5570277..479235a 100644 --- a/main.c +++ b/main.c @@ -63,202 +63,216 @@ typedef struct { EditMode mode; u32 count; - Str msg; - Str input_line, input_prompt; - - Str search; - int search_dir; - u32 search_char; - int search_char_dir; - int search_char_incl; + /* these unfortunately have to be global so that draw() will know what + * to do when a window resizes during input + */ + Str msg; + Str input_line, input_prompt; + u32 optc, opti; + Str *optv; + u32 *optvi; + + Str search; + int search_dir; + u32 search_char; + int search_char_dir; + int search_char_incl; } Editor; Editor e = { 0 }; int ed_buf_open(Editor *e, const char *path) { - if (e->bufn == ED_BUF_MAX) return -1; - EditBuf b = { 0 }; - b.arena = arena_init(1L << 30); - b.txt = FREELIST_NEW(&e->txt_free, &b.arena); if (path) { - b.path = str_dup(str_from_cstr(path), &b.arena); - b.type = ED_BUF_FILE; - txt_load(b.txt, path); - } else { - b.path = S("*scratch*"); - b.type = ED_BUF_SCRATCH; - txt_load_empty(b.txt); + Str s = str_from_cstr(path); + for (u32 i = 0; i < e->bufn; i++) { + if (str_eql(e->buf[i].path, s)) return i; + } } - b.cur = txt_end(b.txt); - e->buf[e->bufn] = b; - return e->bufn++; + + if (e->bufn == ED_BUF_MAX) return -1; + EditBuf b = { 0 }; + b.arena = arena_init(1L << 30); + b.txt = FREELIST_NEW(&e->txt_free, &b.arena); + if (path) { + b.path = str_dup(str_from_cstr(path), &b.arena); + b.type = ED_BUF_FILE; + txt_load(b.txt, path); + } else { + b.path = S("*scratch*"); + b.type = ED_BUF_SCRATCH; + txt_load_empty(b.txt); + } + b.cur = txt_end(b.txt); + e->buf[e->bufn] = b; + return e->bufn++; } void ed_buf_free(EditBuf *b) { - txt_free(b->txt); - arena_free(&b->arena); + txt_free(b->txt); + arena_free(&b->arena); } void ed_buf_change_path(Editor *e, u32 i, Str s) { - EditBuf *eb = &e->buf[i]; - eb->path = str_dup(s, &eb->arena); - eb->type = ED_BUF_FILE; + EditBuf *eb = &e->buf[i]; + eb->path = str_dup(s, &eb->arena); + eb->type = ED_BUF_FILE; } void ed_init(Editor *e) { - memset(e, 0, sizeof(Editor)); - e->scratch = arena_init(1L << 30); - e->bufi = ed_buf_open(e, NULL); - Str s = S("(Scratch buffer, type whatever)"); - e->buf[e->bufi].cur = txt_insert(e->buf[e->bufi].cur, s.s, s.n); + memset(e, 0, sizeof(Editor)); + e->scratch = arena_init(1L << 30); + e->bufi = ed_buf_open(e, NULL); + Str s = S("(Scratch buffer, type whatever)"); + e->buf[e->bufi].cur = txt_insert(e->buf[e->bufi].cur, s.s, s.n); } void ed_fini(Editor *e) { - for (u32 i = 0; i < e->bufn; i++) { - ed_buf_free(&e->buf[i]); - } - if (e->search.s) free(e->search.s); + for (u32 i = 0; i < e->bufn; i++) { + ed_buf_free(&e->buf[i]); + } + if (e->search.s) free(e->search.s); } u32 ed_buf_close(Editor *e, u32 i) { - ed_buf_free(&e->buf[i]); - if (i + 1 < e->bufn) { - MOVE(&e->buf[i], &e->buf[i+1], e->bufn - (i + 1)); - } - e->bufn--; - return i > 0 ? i - 1 : 0; + ed_buf_free(&e->buf[i]); + if (i + 1 < e->bufn) { + MOVE(&e->buf[i], &e->buf[i+1], e->bufn - (i + 1)); + } + e->bufn--; + return i > 0 ? i - 1 : 0; } int ed_buf_save(Editor *e, u32 i) { - EditBuf *b = &e->buf[i]; - if (b->type == ED_BUF_FILE) { - return txt_save(b->txt, str_to_cstr(b->path, &e->scratch)); - } else { - return 0; - } + EditBuf *b = &e->buf[i]; + if (b->type == ED_BUF_FILE) { + return txt_save(b->txt, str_to_cstr(b->path, &e->scratch)); + } else { + return 0; + } } Str str_printf(Arena *a, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int n = vsnprintf(NULL, 0, fmt, ap); - va_end(ap); - va_start(ap, fmt); - char *buf = new_arr(a, char, n + 1); - vsnprintf(buf, n + 1, fmt, ap); - va_end(ap); - return (Str) { buf, n }; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + va_start(ap, fmt); + char *buf = new_arr(a, char, n + 1); + vsnprintf(buf, n + 1, fmt, ap); + va_end(ap); + return (Str) { buf, n }; } static inline int is_space(u32 c) { - return c <= 0x20 && c != 0; + return c <= 0x20 && c != 0; } TxtLoc next_word(TxtLoc l) { - while (!at_end(l) && is_space(txt_chr(l))) l = cnext(l); - while (!at_end(l) && !is_space(txt_chr(l))) l = cnext(l); - return l; + while (!at_end(l) && is_space(txt_chr(l))) l = cnext(l); + while (!at_end(l) && !is_space(txt_chr(l))) l = cnext(l); + return l; } TxtLoc prev_word(TxtLoc l) { - while (!at_start(l)) { - TxtLoc n = bprev(l); - if (!is_space(txt_chr(n))) break; - l = n; - } - while (!at_start(l)) { - TxtLoc n = bprev(l); - if (is_space(txt_chr(n))) break; - l = n; - } - return l; + while (!at_start(l)) { + TxtLoc n = bprev(l); + if (!is_space(txt_chr(n))) break; + l = n; + } + while (!at_start(l)) { + TxtLoc n = bprev(l); + if (is_space(txt_chr(n))) break; + l = n; + } + return l; } static inline u32 bracket_opp(u32 c) { - switch (c) { - case '{': return '}'; - case '}': return '{'; - case '[': return ']'; - case ']': return '['; - case '(': return ')'; - case ')': return '('; - case '<': return '>'; - case '>': return '<'; - default: - return c; - } + switch (c) { + case '{': return '}'; + case '}': return '{'; + case '[': return ']'; + case ']': return '['; + case '(': return ')'; + case ')': return '('; + case '<': return '>'; + case '>': return '<'; + default: + return c; + } } static inline int bracket_dir(u32 c) { - switch (c) { - case '{': case '[': case '(': case '<': return 1; - case '}': case ']': case ')': case '>': return -1; - default: - return 0; - } + switch (c) { + case '{': case '[': case '(': case '<': return 1; + case '}': case ']': case ')': case '>': return -1; + default: + return 0; + } } int match_bracket(TxtLoc l, TxtLoc *out, u32 c) { - u32 depth = 1; - u32 o = bracket_opp(c); - if (txt_chr(l) != c) return 0; - int dir = bracket_dir(c); - if (dir < 0) { - while (!at_start(l) && depth > 0) { - l = cprev(l); - u32 x = txt_chr(l); - if (x == c) depth++; - else if (x == o) depth--; - } - } else if (dir > 0) { - while (!at_end(l) && depth > 0) { - l = cnext(l); - u32 x = txt_chr(l); - if (x == c) depth++; - else if (x == o) depth--; - } - - } - if (depth == 0) { - *out = l; - return 1; - } else { - return 0; - } + u32 depth = 1; + u32 o = bracket_opp(c); + if (txt_chr(l) != c) return 0; + int dir = bracket_dir(c); + if (dir < 0) { + while (!at_start(l) && depth > 0) { + l = cprev(l); + u32 x = txt_chr(l); + if (x == c) depth++; + else if (x == o) depth--; + } + } else if (dir > 0) { + while (!at_end(l) && depth > 0) { + l = cnext(l); + u32 x = txt_chr(l); + if (x == c) depth++; + else if (x == o) depth--; + } + + } + if (depth == 0) { + *out = l; + return 1; + } else { + return 0; + } } TxtLoc next_func_end(TxtLoc l) { - l = start_of_line(l); - while (!at_end(l)) { - l = next_line_start(l); - if (txt_chr(l) == '}' && txt_chr(cnext(l)) == '\n') break; - } - return l; + l = start_of_line(l); + while (!at_end(l)) { + l = next_line_start(l); + if (txt_chr(l) == '}' && txt_chr(cnext(l)) == '\n') break; + } + return l; } TxtLoc prev_func_end(TxtLoc l) { - l = start_of_line(l); - while (!at_start(l)) { - l = prev_line_start(l); - if (txt_chr(l) == '}' && txt_chr(cnext(l)) == '\n') break; - } - return l; + l = start_of_line(l); + while (!at_start(l)) { + l = prev_line_start(l); + if (txt_chr(l) == '}' && txt_chr(cnext(l)) == '\n') break; + } + return l; } TxtLoc prev_func(TxtLoc l) { - match_bracket(prev_func_end(l), &l, '}'); - return l; + match_bracket(prev_func_end(l), &l, '}'); + return l; } TxtLoc next_func(TxtLoc l) { - match_bracket(l, &l, '{'); - match_bracket(next_func_end(l), &l, '}'); - return l; + match_bracket(l, &l, '{'); + match_bracket(next_func_end(l), &l, '}'); + return l; } int empty_line(TxtLoc l) { - u8 b = txt_byte(start_of_line(l)); - return b == '\n' || b == 0 /* last line of buffer */; + u8 b = txt_byte(start_of_line(l)); + return b == '\n' || b == 0 /* last line of buffer */ + ; } TxtLoc next_par(TxtLoc l) { @@ -438,11 +452,7 @@ void find_view_window(TxtLoc l, TxtLoc *start, TxtLoc *end, u32 lines) { *end = b; } -void draw(void *ctx) { - (void)ctx; - EditBuf *eb = &e.buf[e.bufi]; - - vui_clear(); +void draw_buf(EditBuf *eb) { int lmarg = 0; int x = lmarg, y = 0; TxtLoc start, end; @@ -517,48 +527,106 @@ void draw(void *ctx) { } } +void draw_opts(Str *optv, u32 optc, u32 *optvi, u32 opti) { + u32 x = 0; + u32 y = 0; + VuiAttr a = A_DEFAULT | A_ITALIC; + vui_clear(); + x += vui_putsna(0, y, e.input_prompt.s, e.input_prompt.n, a); + x += vui_putsna(x, y, e.input_line.s, e.input_line.n, a); + vui_curs_pos(x, y); + vui_curs_shape(VUI_CURS_BAR); + y++; + u32 n = LINES; + u32 tn = n / 2; + u32 top = opti > tn ? opti - tn : 0; + u32 bot = optc - top > n ? n : optc - top; + for (u32 i = top; i < bot; i++) { + VuiAttr a = A_DEFAULT; + if (i == opti) a |= A_REVERSE; + vui_putsna(2, y++, optv[optvi[i]].s, optv[optvi[i]].n, a); + } +} + +void draw(void *ctx) { + (void)ctx; + if (e.optv) { + draw_opts(e.optv, e.optc, e.optvi, e.opti); + } else { + draw_buf(&e.buf[e.bufi]); + } +} + TxtLoc logical_line_start(TxtLoc l) { l = start_of_line(l); while (txt_chr(l) == '\t') l = cnext(l); return l; } -Str get_input_line(Str prompt) { +typedef struct { + Arena *a; + Str prompt; + DYNARR(char) s; +} LineEditor; + +typedef enum { + LEDIT_EMPTY, + LEDIT_DONE, + LEDIT_CONT, + LEDIT_UP, + LEDIT_DOWN +} LineEditStatus; + +static LineEditor line_editor(Str prompt, Arena *a) { + e.input_line = (Str) { 0, 0 }; e.input_prompt = prompt; - DYNARR(char) s = { 0 }; + return (LineEditor) { a, prompt, { 0 }}; +} + +static LineEditStatus line_edit(LineEditor *le, u32 c) { + switch (c) { + case '\r': + e.input_line = (Str) { 0, 0 }; + e.input_prompt = (Str) { 0, 0 }; + return LEDIT_DONE; + case KEY_UP: + return LEDIT_UP; + case KEY_DOWN: + return LEDIT_DOWN; + case KEY_ESC: + e.input_line = (Str) { 0, 0 }; + e.input_prompt = (Str) { 0, 0 }; + return LEDIT_EMPTY; + case KEY_BKSP: + if (le->s.n > 0) le->s.n--; + break; + case 0x17 /* ^W */: + while (le->s.n > 0 && is_space(le->s.v[le->s.n-1])) le->s.n--; + while (le->s.n > 0 && !is_space(le->s.v[le->s.n-1])) le->s.n--; + break; + default: + if (c > 0 && c <= KEY_UTF8_MAX && c >= ' ') { + u32 n = utf8_encode_len(&c, 1); + DA_AFIT(&le->s, le->a, le->s.n + n); + utf8_encode(&le->s.v[le->s.n], &c, n); + le->s.n += n; + } + e.opti = 0; + break; + } + e.input_line = (Str) { le->s.v, le->s.n }; + return LEDIT_CONT; +} + +Str get_input_line(Str prompt) { + LineEditor le = line_editor(prompt, &e.scratch); for (;;) { draw(NULL); vui_blit(); - u32 c = vui_key(); - switch (c) { - case '\r': - goto done; - case KEY_ESC: - e.input_line = (Str) { 0, 0 }; - e.input_prompt = (Str) { 0, 0 }; - return (Str) { 0, 0 }; - case KEY_BKSP: - if (s.n > 0) s.n--; - break; - case 0x17 /* ^W */: - while (s.n > 0 && is_space(s.v[s.n-1])) s.n--; - while (s.n > 0 && !is_space(s.v[s.n-1])) s.n--; - break; - default: - if (c > 0 && c <= KEY_UTF8_MAX && c >= ' ') { - u32 n = utf8_encode_len(&c, 1); - DA_AFIT(&s, &e.scratch, s.n + n); - utf8_encode(&s.v[s.n], &c, n); - s.n += n; - } - break; - } - e.input_line = (Str) { s.v, s.n }; + LineEditStatus st = line_edit(&le, vui_key()); + if (st == LEDIT_EMPTY) return (Str) { 0, 0 }; + if (st == LEDIT_DONE) return (Str) { le.s.v, le.s.n }; } -done:; - e.input_line = (Str) { 0, 0 }; - e.input_prompt = (Str) { 0, 0 }; - return (Str) { s.v, s.n }; } int search_next_regex(TxtLoc l, Str src, TxtLoc *out) { @@ -998,12 +1066,119 @@ static TxtLoc indent_dedent_lines(TxtLoc start, TxtLoc end, int n) { return txt_at(start.t, ofs); } +int *opt_sort_scr; + +static inline char to_lower(char c) { + /* TODO: consider utf-8 support */ + if (c >= 'A' && c <= 'Z') return c + ('a' - 'A'); + return c; +} + +int fz_score(Str s, Str p) { + if (!p.n || str_eql(s, p)) return 0; + int n = 1; + u32 pi = 0, si = 0; + for (;;) { + if (to_lower(p.s[pi]) == to_lower(s.s[si])) { + si++; + pi++; + if (si == s.n) { + return pi < p.n ? -1 : n; + } else if (pi == p.n) { + return n; + } + } else { + si++; + n++; + if (si == s.n) return -1; + } + } +} + +void qsort_opt(u32 *dest, int *scr, int n) { + int i = 0, j = n - 1; + int x = scr[n / 2]; + do { + while (scr[i] < x) i++; + while (x < scr[j]) j--; + if (i <= j) { + int t = dest[i]; + dest[i] = dest[j]; + dest[j] = t; + t = scr[i]; + scr[i] = scr[j]; + scr[j] = t; + i++; + j--; + } + } while (i <= j); + if (0 < j) qsort_opt(dest, scr, j + 1); + if (i+1 < n) qsort_opt(dest + i, scr + i, n - i); +} + +u32 sort_opt_idx(u32 *dest, Str *src, u32 n, Str pat, Arena *scratch) { + int *scr = new_arr(scratch, int, n); + u32 scrn = 0; + for (u32 i = 0; i < n; i++) { + int s = fz_score(src[i], pat); + if (s != -1) { + scr[scrn] = s; + dest[scrn] = i; + scrn++; + } + } + qsort_opt(dest, scr, scrn); + return scrn; +} + +int select_opt(Str *optv, u32 optc, Str prompt) { + if (!optc) return -1; + e.optc = optc; + e.optvi = new_arr(&e.scratch, u32, optc); + e.optv = optv; + LineEditor le = line_editor(prompt, &e.scratch); + int r = -1; + for (;;) { + Arena s = e.scratch; + e.optc = sort_opt_idx(e.optvi, optv, optc, e.input_line, &e.scratch); + draw(NULL); + vui_blit(); + e.scratch = s; + LineEditStatus st = line_edit(&le, vui_key()); + if (st == LEDIT_EMPTY) break; + if (st == LEDIT_DONE) { + r = e.optvi[e.opti]; + break; + } + if (st == LEDIT_DOWN) e.opti = (e.opti + 1) % e.optc; + if (st == LEDIT_UP) e.opti = e.opti ? e.opti - 1 : e.optc - 1; + } + e.optc = 0; + e.optv = NULL; + return r; +} + +#include + +Str select_file_in(const char *path) { + DIR *d = opendir(path); + if (!d) return (Str) { 0, 0 }; + struct dirent *de; + DYNARR(Str) opt = { 0 }; + DA_APUSH(&opt, &e.scratch, S("..")); + while ((de = readdir(d))) { + if (de->d_name[0] == '.') continue; + Str n = str_dup(str_from_cstr(de->d_name), &e.scratch); + DA_APUSH(&opt, &e.scratch, n); + } + int o = select_opt(opt.v, opt.n, S("File: ")); + if (o == -1) return (Str) { 0, 0 }; + return opt.v[o]; +} + Str select_file(void) { - /* TODO: implement */ - vui_clear(); - vui_blit(); - vui_key(); - return (Str) { 0 }; + Str s = select_file_in("."); + return s; } int main(int argc, const char **argv) { @@ -1166,6 +1341,13 @@ int main(int argc, const char **argv) { if (s.n > 0) e.bufi = ed_buf_open(&e, str_to_cstr(s, &e.scratch)); } break; + case 'F': { + Str s = select_file(); + if (s.n > 0) { + e.bufi = ed_buf_close(&e, e.bufi); + e.bufi = ed_buf_open(&e, str_to_cstr(s, &e.scratch)); + } + } break; case 'm': build_file(eb->path, 1); break; -- cgit v1.2.3