cell: pack more efficiently and store glyph as a wchar

The 'attributes' struct is now 8 bytes and naturally packed (used to
be 9 bytes, artificially packed).

'cell' struct is now 12 bytes, naturally packed (used to be 13 bytes,
artificially packed).

Furthermore, the glyph is stored as a wchar instead of a char*. This
makes it easier (faster) to do glyph lookup when rendering.
This commit is contained in:
Daniel Eklöf 2019-08-02 18:19:07 +02:00
parent ab92abbd21
commit 4d7993b36f
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
9 changed files with 146 additions and 129 deletions

38
csi.c
View file

@ -50,8 +50,8 @@ static void
sgr_reset(struct terminal *term) sgr_reset(struct terminal *term)
{ {
memset(&term->vt.attrs, 0, sizeof(term->vt.attrs)); memset(&term->vt.attrs, 0, sizeof(term->vt.attrs));
term->vt.attrs.foreground = term->colors.fg; term->vt.attrs.fg = term->colors.fg;
term->vt.attrs.background = term->colors.bg; term->vt.attrs.bg = term->colors.bg;
} }
static const char * static const char *
@ -129,7 +129,8 @@ csi_sgr(struct terminal *term)
case 35: case 35:
case 36: case 36:
case 37: case 37:
term->vt.attrs.foreground = 1 << 30 | term->colors.regular[param - 30]; term->vt.attrs.have_fg = 1;
term->vt.attrs.fg = term->colors.regular[param - 30];
break; break;
case 38: { case 38: {
@ -144,7 +145,8 @@ csi_sgr(struct terminal *term)
color = term->colors.bright[idx - 8]; color = term->colors.bright[idx - 8];
else else
color = colors256[idx]; color = colors256[idx];
term->vt.attrs.foreground = 1 << 30 | color; term->vt.attrs.have_fg = 1;
term->vt.attrs.fg = color;
i += 2; i += 2;
} }
@ -155,7 +157,8 @@ csi_sgr(struct terminal *term)
uint8_t r = term->vt.params.v[i + 2].value; uint8_t r = term->vt.params.v[i + 2].value;
uint8_t g = term->vt.params.v[i + 3].value; uint8_t g = term->vt.params.v[i + 3].value;
uint8_t b = term->vt.params.v[i + 4].value; uint8_t b = term->vt.params.v[i + 4].value;
term->vt.attrs.foreground = 1 << 30 | r << 16 | g << 8 | b; term->vt.attrs.have_fg = 1;
term->vt.attrs.fg = r << 16 | g << 8 | b;
i += 4; i += 4;
} }
@ -171,7 +174,8 @@ csi_sgr(struct terminal *term)
/* 6 - CS tolerance */ /* 6 - CS tolerance */
/* 7 - color space associated with tolerance */ /* 7 - color space associated with tolerance */
term->vt.attrs.foreground = 1 << 30 | r << 16 | g << 8 | b; term->vt.attrs.have_fg = 1;
term->vt.attrs.fg = r << 16 | g << 8 | b;
} else } else
UNHANDLED_SGR(); UNHANDLED_SGR();
} }
@ -183,7 +187,7 @@ csi_sgr(struct terminal *term)
} }
case 39: case 39:
term->vt.attrs.foreground = 0; term->vt.attrs.have_fg = 0;
break; break;
/* Regular background colors */ /* Regular background colors */
@ -195,7 +199,8 @@ csi_sgr(struct terminal *term)
case 45: case 45:
case 46: case 46:
case 47: case 47:
term->vt.attrs.background = 1 << 30 | term->colors.regular[param - 40]; term->vt.attrs.have_bg = 1;
term->vt.attrs.bg = term->colors.regular[param - 40];
break; break;
case 48: { case 48: {
@ -211,7 +216,8 @@ csi_sgr(struct terminal *term)
color = term->colors.bright[idx - 8]; color = term->colors.bright[idx - 8];
else else
color = colors256[idx]; color = colors256[idx];
term->vt.attrs.background = 1 << 30 | color; term->vt.attrs.have_bg = 1;
term->vt.attrs.bg = color;
i += 2; i += 2;
} }
@ -221,7 +227,8 @@ csi_sgr(struct terminal *term)
uint8_t r = term->vt.params.v[i + 2].value; uint8_t r = term->vt.params.v[i + 2].value;
uint8_t g = term->vt.params.v[i + 3].value; uint8_t g = term->vt.params.v[i + 3].value;
uint8_t b = term->vt.params.v[i + 4].value; uint8_t b = term->vt.params.v[i + 4].value;
term->vt.attrs.background = 1 << 30 | r << 16 | g << 8 | b; term->vt.attrs.have_bg = 1;
term->vt.attrs.bg = r << 16 | g << 8 | b;
i += 4; i += 4;
} }
@ -238,7 +245,8 @@ csi_sgr(struct terminal *term)
/* 6 - CS tolerance */ /* 6 - CS tolerance */
/* 7 - color space associated with tolerance */ /* 7 - color space associated with tolerance */
term->vt.attrs.background = 1 << 30 | r << 16 | g << 8 | b; term->vt.attrs.have_bg = 1;
term->vt.attrs.bg = r << 16 | g << 8 | b;
} else } else
UNHANDLED_SGR(); UNHANDLED_SGR();
} }
@ -249,7 +257,7 @@ csi_sgr(struct terminal *term)
break; break;
} }
case 49: case 49:
term->vt.attrs.background = 0; term->vt.attrs.have_bg = 0;
break; break;
/* Bright foreground colors */ /* Bright foreground colors */
@ -261,7 +269,8 @@ csi_sgr(struct terminal *term)
case 95: case 95:
case 96: case 96:
case 97: case 97:
term->vt.attrs.foreground = 1 << 30 | term->colors.bright[param - 90]; term->vt.attrs.have_fg = 1;
term->vt.attrs.fg = term->colors.bright[param - 90];
break; break;
/* Regular background colors */ /* Regular background colors */
@ -273,7 +282,8 @@ csi_sgr(struct terminal *term)
case 105: case 105:
case 106: case 106:
case 107: case 107:
term->vt.attrs.background = 1 << 30 | term->colors.bright[param - 100]; term->vt.attrs.have_bg = 1;
term->vt.attrs.bg = term->colors.bright[param - 100];
break; break;
default: default:

23
font.c
View file

@ -381,29 +381,23 @@ glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph)
.left = font->face->glyph->bitmap_left, .left = font->face->glyph->bitmap_left,
.top = font->face->glyph->bitmap_top, .top = font->face->glyph->bitmap_top,
.pixel_size_fixup = font->pixel_size_fixup, .pixel_size_fixup = font->pixel_size_fixup,
.valid = true,
}; };
return true; return true;
err: err:
*glyph = (struct glyph){
.valid = false,
};
return false; return false;
} }
const struct glyph * const struct glyph *
font_glyph_for_utf8(struct font *font, const char *utf8) font_glyph_for_wc(struct font *font, wchar_t wc)
{ {
mtx_lock(&font->lock); mtx_lock(&font->lock);
mbstate_t ps = {0};
wchar_t wc;
if (mbrtowc(&wc, utf8, 4, &ps) < 0) {
LOG_DBG("failed to convert utf-8 sequence %02x %02x %02x %02x to unicode",
(unsigned char)utf8[0], (unsigned char)utf8[1],
(unsigned char)utf8[2], (unsigned char)utf8[3]);
mtx_unlock(&font->lock);
return NULL;
}
assert(font->cache != NULL); assert(font->cache != NULL);
size_t hash_idx = hash_index(wc); size_t hash_idx = hash_index(wc);
hash_entry_t *hash_entry = font->cache[hash_idx]; hash_entry_t *hash_entry = font->cache[hash_idx];
@ -418,10 +412,7 @@ font_glyph_for_utf8(struct font *font, const char *utf8)
} }
struct glyph glyph; struct glyph glyph;
if (!glyph_for_wchar(font, wc, &glyph)) { bool got_glyph = glyph_for_wchar(font, wc, &glyph);
mtx_unlock(&font->lock);
return NULL;
}
if (hash_entry == NULL) { if (hash_entry == NULL) {
hash_entry = calloc(1, sizeof(*hash_entry)); hash_entry = calloc(1, sizeof(*hash_entry));
@ -434,7 +425,7 @@ font_glyph_for_utf8(struct font *font, const char *utf8)
tll_push_back(*hash_entry, glyph); tll_push_back(*hash_entry, glyph);
mtx_unlock(&font->lock); mtx_unlock(&font->lock);
return &tll_back(*hash_entry); return got_glyph ? &tll_back(*hash_entry) : NULL;
} }
void void

3
font.h
View file

@ -22,6 +22,7 @@ struct glyph {
int top; int top;
double pixel_size_fixup; double pixel_size_fixup;
bool valid;
}; };
typedef tll(struct glyph) hash_entry_t; typedef tll(struct glyph) hash_entry_t;
@ -51,5 +52,5 @@ struct font {
}; };
bool font_from_name(font_list_t names, const char *attributes, struct font *result); bool font_from_name(font_list_t names, const char *attributes, struct font *result);
const struct glyph *font_glyph_for_utf8(struct font *font, const char *utf8); const struct glyph *font_glyph_for_wc(struct font *font, wchar_t wc);
void font_destroy(struct font *font); void font_destroy(struct font *font);

55
input.c
View file

@ -60,12 +60,6 @@ static void
keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
struct wl_surface *surface, struct wl_array *keys) struct wl_surface *surface, struct wl_array *keys)
{ {
LOG_DBG("enter");
#if 0
uint32_t *key;
wl_array_for_each(key, keys)
xkb_state_update_key(xkb_state, *key, 1);
#endif
struct terminal *term = data; struct terminal *term = data;
term->input_serial = serial; term->input_serial = serial;
term_focus_in(term); term_focus_in(term);
@ -76,7 +70,6 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
struct wl_surface *surface) struct wl_surface *surface)
{ {
struct terminal *term = data; struct terminal *term = data;
term_focus_out(term);
mtx_lock(&term->kbd.repeat.mutex); mtx_lock(&term->kbd.repeat.mutex);
if (term->kbd.repeat.cmd != REPEAT_EXIT) { if (term->kbd.repeat.cmd != REPEAT_EXIT) {
@ -84,6 +77,35 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
cnd_signal(&term->kbd.repeat.cond); cnd_signal(&term->kbd.repeat.cond);
} }
mtx_unlock(&term->kbd.repeat.mutex); mtx_unlock(&term->kbd.repeat.mutex);
term_focus_out(term);
}
static void
start_repeater(struct terminal *term, uint32_t key)
{
mtx_lock(&term->kbd.repeat.mutex);
if (!term->kbd.repeat.dont_re_repeat) {
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_START;
term->kbd.repeat.key = key;
cnd_signal(&term->kbd.repeat.cond);
}
}
mtx_unlock(&term->kbd.repeat.mutex);
}
static void
stop_repeater(struct terminal *term, uint32_t key)
{
mtx_lock(&term->kbd.repeat.mutex);
if (term->kbd.repeat.key == key) {
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_STOP;
cnd_signal(&term->kbd.repeat.cond);
}
}
mtx_unlock(&term->kbd.repeat.mutex);
} }
static void static void
@ -97,14 +119,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift; const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift;
if (state == XKB_KEY_UP) { if (state == XKB_KEY_UP) {
mtx_lock(&term->kbd.repeat.mutex); stop_repeater(term, key);
if (term->kbd.repeat.key == key) {
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_STOP;
cnd_signal(&term->kbd.repeat.cond);
}
}
mtx_unlock(&term->kbd.repeat.mutex);
return; return;
} }
@ -261,15 +276,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
} }
} }
mtx_lock(&term->kbd.repeat.mutex); start_repeater(term, key - 8);
if (!term->kbd.repeat.dont_re_repeat) {
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_START;
term->kbd.repeat.key = key - 8;
cnd_signal(&term->kbd.repeat.cond);
}
}
mtx_unlock(&term->kbd.repeat.mutex);
} }
static void static void

View file

@ -154,11 +154,11 @@ render_cell(struct terminal *term, cairo_t *cr,
bool block_cursor = has_cursor && term->cursor_style == CURSOR_BLOCK; bool block_cursor = has_cursor && term->cursor_style == CURSOR_BLOCK;
bool is_selected = coord_is_selected(term, col, row); bool is_selected = coord_is_selected(term, col, row);
uint32_t _fg = cell->attrs.foreground >> 30 uint32_t _fg = cell->attrs.have_fg
? cell->attrs.foreground ? cell->attrs.fg
: !term->reverse ? term->colors.fg : term->colors.bg; : !term->reverse ? term->colors.fg : term->colors.bg;
uint32_t _bg = cell->attrs.background >> 30 uint32_t _bg = cell->attrs.have_bg
? cell->attrs.background ? cell->attrs.bg
: !term->reverse ? term->colors.bg : term->colors.fg; : !term->reverse ? term->colors.bg : term->colors.fg;
/* If *one* is set, we reverse */ /* If *one* is set, we reverse */
@ -185,7 +185,7 @@ render_cell(struct terminal *term, cairo_t *cr,
} }
struct font *font = attrs_to_font(term, &cell->attrs); struct font *font = attrs_to_font(term, &cell->attrs);
const struct glyph *glyph = font_glyph_for_utf8(font, cell->c); const struct glyph *glyph = font_glyph_for_wc(font, cell->wc);
int cell_cols = glyph != NULL ? max(1, glyph->width) : 1; int cell_cols = glyph != NULL ? max(1, glyph->width) : 1;
@ -210,7 +210,7 @@ render_cell(struct terminal *term, cairo_t *cr,
arm_blink_timer(term); arm_blink_timer(term);
} }
if (cell->c[0] == '\0' || cell->attrs.conceal) if (cell->wc == 0 || cell->attrs.conceal)
return cell_cols; return cell_cols;
if (glyph != NULL) { if (glyph != NULL) {

View file

@ -5,6 +5,7 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <wctype.h>
#define LOG_MODULE "selection" #define LOG_MODULE "selection"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
@ -58,11 +59,12 @@ extract_selection(const struct terminal *term)
/* TODO: replace '\0' with spaces, then trim lines */ /* TODO: replace '\0' with spaces, then trim lines */
for (int col = start_col; col < term->cols; col++) { for (int col = start_col; col < term->cols; col++) {
const struct cell *cell = &row->cells[col]; const struct cell *cell = &row->cells[col];
if (cell->c[0] == '\0') if (cell->wc == 0)
continue; continue;
size_t len = strnlen(cell->c, 4); mbstate_t ps = {0};
memcpy(&buf[idx], cell->c, len); size_t len = wcrtomb(&buf[idx], cell->wc, &ps);
assert(len >= 0); /* All wchars were valid multibyte strings to begin with */
idx += len; idx += len;
} }
@ -76,11 +78,12 @@ extract_selection(const struct terminal *term)
const struct row *row = grid_row_in_view(term->grid, end->row - term->grid->view); const struct row *row = grid_row_in_view(term->grid, end->row - term->grid->view);
for (int col = start_col; row != NULL && col <= end->col; col++) { for (int col = start_col; row != NULL && col <= end->col; col++) {
const struct cell *cell = &row->cells[col]; const struct cell *cell = &row->cells[col];
if (cell->c[0] == '\0') if (cell->wc == 0)
continue; continue;
size_t len = strnlen(cell->c, 4); mbstate_t ps = {0};
memcpy(&buf[idx], cell->c, len); size_t len = wcrtomb(&buf[idx], cell->wc, &ps);
assert(len >= 0); /* All wchars were valid multibyte strings to begin with */
idx += len; idx += len;
} }
} }
@ -214,19 +217,19 @@ selection_cancel(struct terminal *term)
} }
static bool static bool
isword(int c) isword(wint_t c)
{ {
switch (c) { switch (c) {
default: return !isspace(c); default: return !iswspace(c);
case '{': case '}': case L'{': case L'}':
case '[': case ']': case L'[': case L']':
case '(': case ')': case L'(': case L')':
case '`': case L'`':
case '\'': case L'\'':
case '"': case L'"':
case ',': case '.': case L',': case L'.':
case ':': case ';': case L':': case L';':
return false; return false;
} }
} }
@ -243,9 +246,9 @@ selection_mark_word(struct terminal *term, int col, int row, uint32_t serial)
struct coord end = {col, row}; struct coord end = {col, row};
const struct row *r = grid_row_in_view(term->grid, start.row); const struct row *r = grid_row_in_view(term->grid, start.row);
unsigned char c = r->cells[start.col].c[0]; wchar_t c = r->cells[start.col].wc;
if (!(c == '\0' || !isword(c))) { if (!(c == 0 || !isword(c))) {
while (true) { while (true) {
int next_col = start.col - 1; int next_col = start.col - 1;
int next_row = start.row; int next_row = start.row;
@ -259,8 +262,8 @@ selection_mark_word(struct terminal *term, int col, int row, uint32_t serial)
const struct row *row = grid_row_in_view(term->grid, next_row); const struct row *row = grid_row_in_view(term->grid, next_row);
unsigned char c = row->cells[next_col].c[0]; c = row->cells[next_col].wc;
if (c == '\0' || !isword(c)) if (c == 0 || !isword(c))
break; break;
start.col = next_col; start.col = next_col;
@ -269,9 +272,9 @@ selection_mark_word(struct terminal *term, int col, int row, uint32_t serial)
} }
r = grid_row_in_view(term->grid, end.row); r = grid_row_in_view(term->grid, end.row);
c = r->cells[end.col].c[0]; c = r->cells[end.col].wc;
if (!(c == '\0' || !isword(c))) { if (!(c == 0 || !isword(c))) {
while (true) { while (true) {
int next_col = end.col + 1; int next_col = end.col + 1;
int next_row = end.row; int next_row = end.row;
@ -285,7 +288,7 @@ selection_mark_word(struct terminal *term, int col, int row, uint32_t serial)
const struct row *row = grid_row_in_view(term->grid, next_row); const struct row *row = grid_row_in_view(term->grid, next_row);
unsigned char c = row->cells[next_col].c[0]; c = row->cells[next_col].wc;
if (c == '\0' || !isword(c)) if (c == '\0' || !isword(c))
break; break;

View file

@ -163,11 +163,12 @@ erase_cell_range(struct terminal *term, struct row *row, int start, int end)
assert(start < term->cols); assert(start < term->cols);
assert(end < term->cols); assert(end < term->cols);
if (unlikely(term->vt.attrs.background >> 30)) { if (unlikely(term->vt.attrs.have_bg)) {
for (int col = start; col <= end; col++) { for (int col = start; col <= end; col++) {
row->cells[col].c[0] = '\0'; row->cells[col].wc = 0;
row->cells[col].attrs.clean = 0; row->cells[col].attrs.clean = 0;
row->cells[col].attrs.background = term->vt.attrs.background; row->cells[col].attrs.have_bg = 1;
row->cells[col].attrs.bg = term->vt.attrs.bg;
} }
} else { } else {
memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0])); memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0]));

View file

@ -3,6 +3,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <wchar.h>
#include <threads.h> #include <threads.h>
#include <semaphore.h> #include <semaphore.h>
@ -50,28 +51,34 @@ struct rgb { float r, g, b; };
/* /*
* Note: we want the cells to be as small as possible. Larger cells * Note: we want the cells to be as small as possible. Larger cells
* means fewer scrollback lines (or performance drops due to cache * means fewer scrollback lines (or performance drops due to cache
* misses) */ * misses)
*
* Note that the members are laid out optimized for x86
*/
struct attributes { struct attributes {
uint8_t bold:1; uint32_t bold:1;
uint8_t dim:1; uint32_t dim:1;
uint8_t italic:1; uint32_t italic:1;
uint8_t underline:1; uint32_t underline:1;
uint8_t strikethrough:1; uint32_t strikethrough:1;
uint8_t blink:1; uint32_t blink:1;
uint8_t conceal:1; uint32_t conceal:1;
uint8_t reverse:1; uint32_t reverse:1;
uint32_t fg:24;
uint32_t clean:1; uint32_t clean:1;
uint32_t foreground:31; uint32_t have_fg:1;
uint32_t have_bg:1;
uint32_t reserved:1; uint32_t reserved:5;
uint32_t background:31; uint32_t bg:24;
} __attribute__((packed)); };
static_assert(sizeof(struct attributes) == 8, "bad size");
struct cell { struct cell {
wchar_t wc;
struct attributes attrs; struct attributes attrs;
char c[4]; };
} __attribute__((packed)); static_assert(sizeof(struct cell) == 12, "bad size");
struct scroll_region { struct scroll_region {
int start; int start;

49
vt.c
View file

@ -728,33 +728,29 @@ action_print_utf8(struct terminal *term)
print_insert(term); print_insert(term);
//LOG_DBG("print: UTF8: %.*s", (int)term->vt.utf8.idx, term->vt.utf8.data); mbstate_t ps = {0};
memcpy(cell->c, term->vt.utf8.data, term->vt.utf8.idx); if (mbrtowc(&cell->wc, (const char *)term->vt.utf8.data, term->vt.utf8.idx, &ps) < 0)
cell->c[term->vt.utf8.idx] = '\0'; cell->wc = 0;
term->vt.utf8.idx = 0; term->vt.utf8.idx = 0;
cell->attrs = term->vt.attrs; cell->attrs = term->vt.attrs;
/* Hack: zero- and double-width characters */ int width = wcwidth(cell->wc);
mbstate_t ps = {0}; if (width <= 0) {
wchar_t wc; /* Skip post_print() below - i.e. don't advance cursor */
if (mbrtowc(&wc, cell->c, 4, &ps) >= 0) { return;
int width = wcwidth(wc); }
if (width <= 0) {
/* Skip post_print() below - i.e. don't advance cursor */
return;
}
/* Advance cursor the 'additional' columns (last step is done /* Advance cursor the 'additional' columns (last step is done
* by post_print()) */ * by post_print()) */
for (int i = 1; i < width && term->cursor.col < term->cols - 1; i++) { for (int i = 1; i < width && term->cursor.col < term->cols - 1; i++) {
term_cursor_right(term, 1); term_cursor_right(term, 1);
assert(term->cursor.col < term->cols); assert(term->cursor.col < term->cols);
struct cell *cell = &row->cells[term->cursor.col]; struct cell *cell = &row->cells[term->cursor.col];
cell->c[0] = '\0'; cell->wc = 0;
cell->attrs.clean = 0; cell->attrs.clean = 0;
}
} }
post_print(term); post_print(term);
@ -788,16 +784,17 @@ action_print(struct terminal *term, uint8_t c)
c >= 0x41 && c <= 0x7e) c >= 0x41 && c <= 0x7e)
{ {
const char *glyph = vt100_0[c - 0x41]; const char *glyph = vt100_0[c - 0x41];
if (glyph != NULL) if (glyph != NULL) {
strncpy(cell->c, glyph, sizeof(cell->c)); mbstate_t ps = {0};
if (mbrtowc(&cell->wc, glyph, strlen(glyph), &ps) < 0)
cell->wc = 0;
}
} else { } else {
//LOG_DBG("print: ASCII: %c", c); //LOG_DBG("print: ASCII: %c", c);
cell->c[0] = c; cell->wc = c;
cell->c[1] = '\0';
} }
cell->attrs = term->vt.attrs; cell->attrs = term->vt.attrs;
post_print(term); post_print(term);
} }