vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: cc1eb1cf3113d81c8432531acf2eedb3463c68b5
Parent: a8b1076b26fee0d81383908dabbffedab42fe068
Author: Randy Palamar
Date:   Mon, 18 Nov 2024 05:25:24 -0700

simplify selection iteration and fix some edge cases

Diffstat:
Mterminal.c | 37+++++++++++++++++++++++++++++--------
Mutil.c | 29+++++++++++++++++++++++++++++
Mutil.h | 32++++++++++++++++++++------------
Mvtgl.c | 119+++++++++++++++++++++++++------------------------------------------------------
4 files changed, 115 insertions(+), 102 deletions(-)

diff --git a/terminal.c b/terminal.c @@ -35,23 +35,44 @@ get_word_around_cell(Term *t, iv2 cell) Range result = {.start = cell, .end = cell}; Cell *row = t->views[t->view_idx].fb.rows[cell.y]; - b32 isspace = ISSPACE(row[cell.x].cp); + b32 isspace = ISSPACE(row[cell.x].cp) && !(row[cell.x].bg & ATTR_WDUMMY); while (result.start.x > 0) { - Cell nc = row[result.start.x - 1]; - if (!(nc.bg & ATTR_WDUMMY) && isspace != ISSPACE(nc.cp)) + Cell *nc = row + result.start.x - 1; + if (!(nc->bg & ATTR_WDUMMY) && isspace != ISSPACE(nc->cp)) break; result.start.x--; } while (result.end.x < t->size.w - 1) { - Cell nc = row[result.end.x + 1]; - if (!(nc.bg & ATTR_WDUMMY) && isspace != ISSPACE(nc.cp)) + Cell *nc = row + result.end.x + 1; + if (!(nc->bg & ATTR_WDUMMY) && isspace != ISSPACE(nc->cp)) break; result.end.x++; } - /* NOTE: ATTR_WDUMMY is invalid for start and end of range */ - if (row[result.start.x].bg & ATTR_WDUMMY) result.start.x++; - if (row[result.end.x].bg & ATTR_WDUMMY) result.end.x--; + return result; +} + +static Range +get_char_around_cell(Term *t, iv2 cell) +{ + Range result = {.start = cell, .end = cell}; + + /* NOTE: if the cell is WDUMMY move back until we find the starting + * WIDE cell. then set the range end to the last WDUMMY cell */ + Cell *row = t->views[t->view_idx].fb.rows[cell.y]; + if (row[cell.x].bg & ATTR_WDUMMY) { + while (!(row[cell.x].bg & ATTR_WIDE) && cell.x > 0) + cell.x--; + ASSERT(row[cell.x].bg & ATTR_WIDE); + result.start = cell; + } + if (row[cell.x].bg & ATTR_WIDE) { + while ((row[cell.x + 1].bg & ATTR_WDUMMY) && cell.x + 1 < t->size.w) + cell.x++; + ASSERT(row[cell.x].bg & ATTR_WDUMMY); + result.end = cell; + } + return result; } diff --git a/util.c b/util.c @@ -373,6 +373,35 @@ stream_push_f64(Stream *s, f64 f, i64 prec) } } +static SelectionIterator +selection_iterator(Range s, Row *rows, u32 term_width) +{ + SelectionIterator result; + result.rows = rows; + result.cursor = (iv2){.x = -1, .y = -1}; + result.next = s.start; + result.end = s.end; + result.term_width = term_width; + return result; +} + +static Cell * +selection_next(SelectionIterator *s) +{ + Cell *result = 0; + + if (!equal_iv2(s->cursor, s->end)) { + s->cursor = s->next; + result = s->rows[s->next.y] + s->next.x++; + if (s->next.x == s->term_width) { + s->next.x = 0; + s->next.y++; + } + } + + return result; +} + static s8 utf8_encode(u32 cp) { diff --git a/util.h b/util.h @@ -366,6 +366,26 @@ struct conversion_result { union { i32 i; f32 f; Colour colour;}; }; +enum selection_states { + SS_NONE, + SS_CHAR, + SS_WORDS, +}; + +typedef struct { + Range range; + Range anchor; + enum selection_states state; +} Selection; + +typedef struct { + Row *rows; + iv2 cursor; + iv2 next; + iv2 end; + u32 term_width; +} SelectionIterator; + #include <immintrin.h> #include "util.c" @@ -488,18 +508,6 @@ typedef struct { iv2 last_cell_report; } InteractionState; -enum selection_states { - SS_NONE, - SS_CHAR, - SS_WORDS, -}; - -typedef struct { - Range range; - Range anchor; - enum selection_states state; -} Selection; - #define ESC_ARG_SIZ 6 typedef struct { s8 raw; diff --git a/vtgl.c b/vtgl.c @@ -534,35 +534,10 @@ render_framebuffer(Term *t, RenderCell *render_buf) /* NOTE: draw selection if active */ if (is_valid_range(t->selection.range)) { - Range sel = t->selection.range; - iv2 curs = sel.start; - iv2 end = sel.end; - /* NOTE: do full rows first */ - for (; curs.y < end.y; curs.y++) { - for (u32 i = curs.x; i < t->size.w; i++) { - Cell *c = &tv->fb.rows[curs.y][i]; - RenderCell *rc = render_buf + curs.y * t->size.w + i; - rc->fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); - if (c->bg & ATTR_WIDE) { - for (u32 j = 1; c[j].bg & ATTR_WDUMMY; j++) { - rc[j].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); - i++; - } - } - } - curs.x = 0; - } - /* NOTE: do the last row */ - for (u32 i = curs.x; i <= end.x; i++) { - Cell *c = &tv->fb.rows[curs.y][i]; - RenderCell *rc = render_buf + curs.y * t->size.w + i; + SelectionIterator si = selection_iterator(t->selection.range, tv->fb.rows, t->size.w); + for (Cell *c = selection_next(&si); c; c = selection_next(&si)) { + RenderCell *rc = render_buf + si.cursor.y * t->size.w + si.cursor.x; rc->fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); - if (c->bg & ATTR_WIDE) { - for (u32 j = 1; c[j].bg & ATTR_WDUMMY; j++) { - rc[j].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); - i++; - } - } } } @@ -606,39 +581,27 @@ mouse_to_cell_space(Term *t, v2 mouse) } static void -stream_push_selection(Stream *s, TermView *tv, Range sel, u32 term_width) +stream_push_selection(Stream *s, Row *rows, Range sel, u32 term_width) { - iv2 curs = sel.start; - iv2 end = sel.end; - s->errors |= !is_valid_range(sel); if (s->errors) return; - /* NOTE: do full rows first */ + SelectionIterator si = selection_iterator(sel, rows, term_width); u32 last_non_space_idx = 0; - for (; curs.y < end.y; curs.y++) { - for (; curs.x < term_width; curs.x++) { - Cell c = tv->fb.rows[curs.y][curs.x]; - if (c.bg & ATTR_WDUMMY) - continue; - stream_push_s8(s, utf8_encode(c.cp)); - if (!ISSPACE(c.cp)) - last_non_space_idx = s->widx; - } - s->widx = last_non_space_idx; - stream_push_byte(s, '\n'); - curs.x = 0; - } - - /* NOTE: do the last row */ - for (; curs.x <= end.x; curs.x++) { - Cell c = tv->fb.rows[curs.y][curs.x]; - if (c.bg & ATTR_WDUMMY) + for (Cell *c = selection_next(&si); c; c = selection_next(&si)) { + if (c->bg & ATTR_WDUMMY) continue; - stream_push_s8(s, utf8_encode(c.cp)); - if (!ISSPACE(c.cp)) + + stream_push_s8(s, utf8_encode(c->cp)); + if (!ISSPACE(c->cp)) last_non_space_idx = s->widx; + + if (si.next.y != si.cursor.y) { + s->widx = last_non_space_idx; + stream_push_byte(s, '\n'); + } } + s->widx = last_non_space_idx; } @@ -650,14 +613,10 @@ begin_selection(Term *t, u32 click_count, v2 mouse) sel->range.end = INVALID_RANGE_END; iv2 cell = mouse_to_cell_space(t, mouse); - if (t->views[t->view_idx].fb.rows[cell.y][cell.x].bg & ATTR_WDUMMY) { - ASSERT(t->views[t->view_idx].fb.rows[cell.y][cell.x - 1].bg & ATTR_WIDE); - cell.x--; - } switch (sel->state) { case SS_WORDS: sel->anchor = sel->range = get_word_around_cell(t, cell); break; - case SS_CHAR: sel->anchor = (Range){.start = cell, .end = cell}; break; + case SS_CHAR: sel->anchor = get_char_around_cell(t, cell); break; case SS_NONE: break; } @@ -675,36 +634,30 @@ update_selection(Term *t, TerminalInput *input) Selection *sel = &t->selection; iv2 new_p = mouse_to_cell_space(t, input->mouse); - - if (t->views[t->view_idx].fb.rows[new_p.y][new_p.x].bg & ATTR_WDUMMY) { - ASSERT(t->views[t->view_idx].fb.rows[new_p.y][new_p.x - 1].bg & ATTR_WIDE); - new_p.x--; + Range new, old_range = sel->range; + switch (sel->state) { + case SS_WORDS: new = get_word_around_cell(t, new_p); break; + case SS_CHAR: new = get_char_around_cell(t, new_p); break; + case SS_NONE: INVALID_CODE_PATH; break; } - Range old_range = sel->range; - - if (sel->state != SS_WORDS) { + if (sel->anchor.start.y < new.start.y) { sel->range.start = sel->anchor.start; - sel->range.end = new_p; + sel->range.end = new.end; + } else if (sel->anchor.start.y > new.start.y) { + sel->range.start = new.start; + sel->range.end = sel->anchor.end; } else { - Range word = get_word_around_cell(t, new_p); - if (sel->anchor.start.y < word.start.y) { - sel->range.start = sel->anchor.start; - sel->range.end = word.end; - } else if (sel->anchor.start.y > word.start.y) { - sel->range.start = word.start; + if (new.start.x < sel->anchor.start.x) { + sel->range.start = new.start; sel->range.end = sel->anchor.end; } else { - if (word.start.x < sel->anchor.start.x) { - sel->range.start = word.start; - sel->range.end = sel->anchor.end; - } else { - sel->range.start = sel->anchor.start; - sel->range.end = word.end; - } + sel->range.start = sel->anchor.start; + sel->range.end = new.end; } } - sel->range = normalize_range(sel->range); + sel->range = normalize_range(sel->range); + if (!equal_range(old_range, sel->range)) t->gl.flags |= UPDATE_RENDER_BUFFER; } @@ -712,7 +665,7 @@ update_selection(Term *t, TerminalInput *input) KEYBIND_FN(copy) { Stream buf = arena_stream(t->arena_for_frame); - stream_push_selection(&buf, t->views + t->view_idx, t->selection.range, t->size.w); + stream_push_selection(&buf, t->views[t->view_idx].fb.rows, t->selection.range, t->size.w); platform->set_clipboard(&buf, a.i); return 1; } @@ -1235,7 +1188,9 @@ DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection) { Term *t = memory->memory; Range result = t->selection.range; - if (out) stream_push_selection(out, t->views + t->view_idx, t->selection.range, t->size.w); + if (out) + stream_push_selection(out, t->views[t->view_idx].fb.rows, + t->selection.range, t->size.w); return result; }