Commit: 6136aaecbfc4f3c68a0a9c31cc7d8521b838243b
Parent: f796574d3710d49f36df8e5b06b07db5371980dd
Author: Randy Palamar
Date: Sun, 25 Aug 2024 22:39:22 -0600
proper wide character support
Diffstat:
M | terminal.c | | | 33 | ++++++++++++++++++++++++++------- |
M | util.h | | | 2 | ++ |
M | vtgl.c | | | 114 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
3 files changed, 97 insertions(+), 52 deletions(-)
diff --git a/terminal.c b/terminal.c
@@ -28,14 +28,26 @@ static const u8 utf8overhangmask[32] = {
static Range
get_word_around_cell(Term *t, iv2 cell)
{
- Range result = {.start = cell, .end = cell};
- Framebuffer *fb = &t->views[t->view_idx].fb;
+ Range result = {.start = cell, .end = cell};
+ Cell *row = t->views[t->view_idx].fb.rows[cell.y];
- b32 isspace = ISSPACE(fb->rows[cell.y][cell.x].cp);
- while (result.start.x > 0 && isspace == ISSPACE(fb->rows[cell.y][result.start.x - 1].cp))
+ b32 isspace = ISSPACE(row[cell.x].cp);
+ while (result.start.x > 0) {
+ Cell nc = row[result.start.x - 1];
+ if (!(nc.style.attr & ATTR_WDUMMY) && isspace != ISSPACE(nc.cp))
+ break;
result.start.x--;
- while (result.end.x < t->size.w - 1 && isspace == ISSPACE(fb->rows[cell.y][result.end.x + 1].cp))
+ }
+ while (result.end.x < t->size.w - 1) {
+ Cell nc = row[result.end.x + 1];
+ if (!(nc.style.attr & 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].style.attr & ATTR_WDUMMY) result.start.x++;
+ if (row[result.end.x].style.attr & ATTR_WDUMMY) result.end.x--;
return result;
}
@@ -609,6 +621,7 @@ set_scrolling_region(Term *t, CSI *csi)
t->top = t->bot;
t->bot = tmp;
}
+ cursor_move_to(t, 0, 0);
}
static void
@@ -1081,7 +1094,7 @@ push_line(Term *t, Line *line, Arena a)
}
if (t->cursor.pos.x + width > t->size.w) {
- /* NOTE: make space character if mode enabled else
+ /* NOTE: make space for character if mode enabled else
* clobber whatever was on the end of the line */
if (t->mode & TM_AUTO_WRAP)
push_newline(t, 1);
@@ -1093,7 +1106,13 @@ push_line(Term *t, Line *line, Arena a)
c->cp = cp;
c->style = t->cursor.style;
- /* TODO: wide characters (need some blank spaces?) */
+ if (width == 2) {
+ c->style.attr |= ATTR_WIDE;
+ if (t->cursor.pos.x + 1 < t->size.w) {
+ Cell *nc = c + 1;
+ nc->style.attr |= ATTR_WDUMMY;
+ }
+ }
if (t->cursor.pos.x + width < t->size.w)
cursor_step_column(t, width);
diff --git a/util.h b/util.h
@@ -105,6 +105,8 @@ enum cell_attribute {
ATTR_INVERSE = 1 << 5,
ATTR_INVISIBLE = 1 << 6,
ATTR_STRUCK = 1 << 7,
+ ATTR_WIDE = 1 << 8,
+ ATTR_WDUMMY = 1 << 9, /* NOTE: used to skip cells when copying */
};
typedef struct {
diff --git a/vtgl.c b/vtgl.c
@@ -312,6 +312,24 @@ push_cell(RenderPushBuffer *rpb, Term *t, Cell c, Rect r, f32 font_text_dy)
rpb->texcolours[idx + 1].y = (cs.attr & ATTR_INVERSE)? cs.fg.rgba : cs.bg.rgba;
}
+static void
+push_cell_row(Term *t, Cell *row, u32 len, b32 inverse, Rect cr, RenderPushBuffer *rpb)
+{
+ ASSERT(inverse == 0 || inverse == 1);
+ v2 cs = cr.size;
+ v2 csw = {.w = cs.w * 2, .h = cs.h};
+ for (u32 c = 0; c < len; c++) {
+ Cell cell = row[c];
+ if (cell.style.attr & ATTR_WDUMMY)
+ continue;
+ if (cell.style.attr & ATTR_WIDE) cr.size = csw;
+ else cr.size = cs;
+ cell.style.attr ^= inverse * ATTR_INVERSE;
+ push_cell(rpb, t, cell, cr, t->fa.deltay);
+ cr.pos.x += cr.size.w;
+ }
+}
+
/* NOTE: In this program we render to an offscreen render target that is later drawn
* to the screen as a full window quad. Therefore render_framebuffer must take care
* to handle all necessary padding (window and cell). Outside of here everyone should
@@ -319,32 +337,15 @@ push_cell(RenderPushBuffer *rpb, Term *t, Cell c, Rect r, f32 font_text_dy)
static void
render_framebuffer(Term *t, RenderPushBuffer *rpb)
{
- v2 tl = get_terminal_top_left(t);
- v2 cs = get_cell_size(t);
+ v2 tl = get_terminal_top_left(t);
+ v2 cs = get_cell_size(t);
+ Rect cr = {.pos = {.x = tl.x, .y = tl.y - cs.h}, .size = cs};
TermView *tv = t->views + t->view_idx;
- {
- Rect cr = {.pos = {.x = tl.x, .y = tl.y - cs.h}, .size = cs};
- for (u32 r = 0; r < t->size.h; r++) {
- for (u32 c = 0; c < t->size.w; c++) {
- push_cell(rpb, t, tv->fb.rows[r][c], cr, t->fa.deltay);
- cr.pos.x += cs.w;
- }
- cr.pos.x = tl.x;
- cr.pos.y -= cs.h;
- }
- }
-
- /* NOTE: draw cursor */
- if (!(t->gl.mode & WIN_MODE_HIDECURSOR) && t->scroll_offset == 0) {
- iv2 curs = t->cursor.pos;
- Rect cr = {
- .pos = {.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)},
- .size = cs,
- };
- Cell cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x];
- cursor.style.attr ^= ATTR_INVERSE;
- push_cell(rpb, t, cursor, cr, t->fa.deltay);
+ /* NOTE: draw whole framebuffer */
+ for (u32 r = 0; r < t->size.h; r++) {
+ push_cell_row(t, tv->fb.rows[r], t->size.w, 0, cr, rpb);
+ cr.pos.y -= cs.h;
}
/* NOTE: draw selection if active */
@@ -353,29 +354,32 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
Range sel = t->selection.range;
iv2 curs = sel.start;
iv2 end = sel.end;
- Rect cr = {
- .pos = {.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)},
- .size = cs,
- };
+ cr.pos = (v2){.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)};
/* NOTE: do full rows first */
for (; curs.y < end.y; curs.y++) {
- for (; curs.x < t->size.w; curs.x++) {
- Cell cell = tv->fb.rows[curs.y][curs.x];
- cell.style.attr ^= ATTR_INVERSE;
- push_cell(rpb, t, cell, cr, t->fa.deltay);
- cr.pos.x += cs.w;
- }
+ u32 len = t->size.w - curs.x - 1;
+ push_cell_row(t, tv->fb.rows[curs.y] + curs.x, len, 1, cr, rpb);
curs.x = 0;
cr.pos.x = tl.x;
cr.pos.y -= cs.h;
}
/* NOTE: do the last row */
- for (; curs.x <= end.x; curs.x++) {
- Cell cell = tv->fb.rows[curs.y][curs.x];
- cell.style.attr ^= ATTR_INVERSE;
- push_cell(rpb, t, cell, cr, t->fa.deltay);
- cr.pos.x += cs.w;
+ push_cell_row(t, tv->fb.rows[curs.y] + curs.x, end.x - curs.x + 1, 1, cr, rpb);
+ }
+
+ /* NOTE: draw cursor */
+ if (!(t->gl.mode & WIN_MODE_HIDECURSOR) && t->scroll_offset == 0) {
+ iv2 curs = t->cursor.pos;
+ cr.pos = (v2){.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)};
+ Cell cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x];
+ if (cursor.style.attr & ATTR_WDUMMY) {
+ cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x - 1];
+ ASSERT(cursor.style.attr & ATTR_WIDE);
}
+ if (cursor.style.attr & ATTR_WIDE)
+ cr.size.w *= 2;
+ cursor.style.attr ^= ATTR_INVERSE;
+ push_cell(rpb, t, cursor, cr, t->fa.deltay);
}
}
@@ -419,6 +423,10 @@ update_selection(Term *t)
return;
iv2 new_p = mouse_to_cell_space(t, mouse);
+ if (t->views[t->view_idx].fb.rows[new_p.y][new_p.x].style.attr & ATTR_WDUMMY) {
+ ASSERT(t->views[t->view_idx].fb.rows[new_p.y][new_p.x - 1].style.attr & ATTR_WIDE);
+ new_p.x--;
+ }
if (sel->state != SS_WORDS) {
sel->range.start = sel->anchor.start;
sel->range.end = new_p;
@@ -463,10 +471,14 @@ KEYBIND_FN(copy)
u32 last_non_space_idx = 0;
for (; curs.y < end.y; curs.y++) {
for (; curs.x < t->size.w && buf_curs != buf_size; curs.x++) {
- /* TODO: handle the utf8 case */
- u32 cp = tv->fb.rows[curs.y][curs.x].cp;
- if (!ISSPACE(cp)) last_non_space_idx = buf_curs;
- buf[buf_curs++] = cp;
+ Cell c = tv->fb.rows[curs.y][curs.x];
+ if (c.style.attr & ATTR_WDUMMY)
+ continue;
+ if (!ISSPACE(c.cp))
+ last_non_space_idx = buf_curs;
+ s8 enc = utf8_encode(c.cp);
+ for (size i = 0; i < enc.len && buf_curs != buf_size; i++)
+ buf[buf_curs++] = enc.data[i];
}
buf[last_non_space_idx + 1] = '\n';
buf_curs = last_non_space_idx + 2;
@@ -474,8 +486,16 @@ KEYBIND_FN(copy)
}
/* NOTE: do the last row */
- for (; curs.x <= end.x && buf_curs != buf_size; curs.x++)
- buf[buf_curs++] = (char)tv->fb.rows[curs.y][curs.x].cp;
+ for (; curs.x <= end.x && buf_curs != buf_size; curs.x++) {
+ Cell c = tv->fb.rows[curs.y][curs.x];
+ if (c.style.attr & ATTR_WDUMMY)
+ continue;
+ if (!ISSPACE(c.cp))
+ last_non_space_idx = buf_curs;
+ s8 enc = utf8_encode(c.cp);
+ for (size i = 0; i < enc.len && buf_curs != buf_size; i++)
+ buf[buf_curs++] = enc.data[i];
+ }
CLAMP(buf_curs, 0, buf_size - 1);
buf[buf_curs] = 0;
@@ -667,6 +687,10 @@ mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod)
t->selection.state++;
iv2 cell = mouse_to_cell_space(t, t->selection.mouse_start);
+ if (t->views[t->view_idx].fb.rows[cell.y][cell.x].style.attr & ATTR_WDUMMY) {
+ ASSERT(t->views[t->view_idx].fb.rows[cell.y][cell.x - 1].style.attr & ATTR_WIDE);
+ cell.x--;
+ }
if (t->selection.state == SS_WORDS) {
t->selection.anchor = get_word_around_cell(t, cell);