diff --git a/grid.c b/grid.c index d2905a93..4ccb27ac 100644 --- a/grid.c +++ b/grid.c @@ -183,6 +183,13 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, } \ } while(0) +#define print_spacer() \ + do { \ + new_row->cells[new_col_idx].wc = CELL_MULT_COL_SPACER; \ + new_row->cells[new_col_idx].attrs = old_cell->attrs; \ + new_row->cells[new_col_idx].attrs.clean = 1; \ + } while (0) + /* * Keep track of empty cells. If the old line ends with a * string of empty cells, we don't need to, nor do we want to, @@ -222,12 +229,19 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, /* Multi-column characters are never cut in half */ assert(c + width <= old_cols); - for (int i = 0; i < empty_count + width; i++) { + for (int i = 0; i < empty_count + 1; i++) { const struct cell *old_cell = &old_row->cells[c - empty_count + i]; + if (old_cell->wc == CELL_MULT_COL_SPACER) + continue; + /* Out of columns on current row in new grid? */ - if (new_col_idx + max(1, wcwidth(old_cell->wc)) > new_cols) + if (new_col_idx + max(1, wcwidth(old_cell->wc)) > new_cols) { + /* Pad to end-of-line with spacers, then line-wrap */ + for (;new_col_idx < new_cols; new_col_idx++) + print_spacer(); line_wrap(); + } assert(new_row != NULL); assert(new_col_idx >= 0); @@ -247,6 +261,14 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, } } new_col_idx++; + + /* For multi-column characters, insert spacers in the + * subsequent cells */ + for (size_t i = 0; i < width - 1; i++) { + assert(new_col_idx < new_cols); + print_spacer(); + new_col_idx++; + } } c += width - 1; @@ -258,6 +280,7 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, line_wrap(); } +#undef print_spacer #undef line_wrap } diff --git a/render.c b/render.c index 99ca03e3..a049ca05 100644 --- a/render.c +++ b/render.c @@ -408,10 +408,10 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (cell->wc != 0) { wchar_t base = cell->wc; - if (base >= COMB_CHARS_LO && - base < (COMB_CHARS_LO + term->composed_count)) + if (base >= CELL_COMB_CHARS_LO && + base < (CELL_COMB_CHARS_LO + term->composed_count)) { - composed = &term->composed[base - COMB_CHARS_LO]; + composed = &term->composed[base - CELL_COMB_CHARS_LO]; base = composed->base; } @@ -438,7 +438,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus) draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); - if (cell->wc == 0 || cell->attrs.conceal) + if (cell->wc == 0 || cell->wc == CELL_MULT_COL_SPACER || cell->attrs.conceal) goto draw_cursor; pixman_image_t *clr_pix = pixman_image_create_solid_fill(&fg); diff --git a/selection.c b/selection.c index cd3780a7..f460607a 100644 --- a/selection.c +++ b/selection.c @@ -134,9 +134,9 @@ foreach_selected_normal( c <= (r == end_row ? end_col : term->cols - 1); c++) { + if (row->cells[c].wc == CELL_MULT_COL_SPACER) + continue; cb(term, row, &row->cells[c], c, data); - c += max(1, wcwidth(row->cells[c].wc)) - 1; - assert(c < term->cols); } start_col = 0; @@ -168,9 +168,9 @@ foreach_selected_block( assert(row != NULL); for (int c = top_left.col; c <= bottom_right.col; c++) { + if (row->cells[c].wc == CELL_MULT_COL_SPACER) + continue; cb(term, row, &row->cells[c], c, data); - c += max(1, wcwidth(row->cells[c].wc)) - 1; - assert(c < term->cols); } } } @@ -273,22 +273,7 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, /* New row - determine if we should insert a newline or not */ if (term->selection.kind == SELECTION_NORMAL) { - int width = max(1, wcwidth(cell->wc)); - - if (width > 1) { - /* Heuristict to handle force-wrapped multi-column - * characters */ - - /* - * TODO: maybe we should print a placeholder value to - * the empty cells at the end of the line when - * force-wrapping? Then extract() could simply skip - * those cells - */ - ctx->empty_count -= min(width, ctx->empty_count); - } - - else if (ctx->last_row->linebreak || + if (ctx->last_row->linebreak || ctx->empty_count > 0 || cell->wc == 0) { @@ -321,8 +306,9 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, assert(ctx->idx + 1 <= ctx->size); - if (cell->wc >= COMB_CHARS_LO && cell->wc < (COMB_CHARS_LO + term->composed_count)) { - const struct composed *composed = &term->composed[cell->wc - COMB_CHARS_LO]; + if (cell->wc >= CELL_COMB_CHARS_LO && + cell->wc < (CELL_COMB_CHARS_LO + term->composed_count)) { + const struct composed *composed = &term->composed[cell->wc - CELL_COMB_CHARS_LO]; ctx->buf[ctx->idx++] = composed->base; diff --git a/terminal.c b/terminal.c index eda311da..40bfbc90 100644 --- a/terminal.c +++ b/terminal.c @@ -2387,33 +2387,39 @@ print_insert(struct terminal *term, int width) row->cells[i].attrs.clean = 0; } +static void +print_spacer(struct terminal *term, int col) +{ + struct row *row = term->grid->cur_row; + struct cell *cell = &row->cells[col]; + + cell->wc = CELL_MULT_COL_SPACER; + cell->attrs = term->vt.attrs; + cell->attrs.clean = 0; +} + void term_print(struct terminal *term, wchar_t wc, int width) { if (unlikely(width <= 0)) return; - if (unlikely(width > 1) && + print_linewrap(term); + print_insert(term, width); + + if (unlikely(width > 1) && likely(term->auto_margin) && term->grid->cursor.point.col + width > term->cols) { /* Multi-column character that doesn't fit on current line - - * force a line wrap */ + * pad with spacers */ + for (size_t i = term->grid->cursor.point.col; i < term->cols; i++) + print_spacer(term, i); + + /* And force a line-wrap */ term->grid->cursor.lcf = 1; - - /* - * TODO: should we insert place holder values in the remaining - * cells? This would allow e.g. text extraction to simply - * skip these, instead of trying to recognize a sequence of - * empty cells at the end of the line followed by a - * multi-column character... - * - * Might also make text reflow easier, or even more correct. - */ + print_linewrap(term); } - print_linewrap(term); - print_insert(term, width); - sixel_overwrite_at_cursor(term, width); /* *Must* get current cell *after* linewrap+insert */ @@ -2429,10 +2435,7 @@ term_print(struct terminal *term, wchar_t wc, int width) /* Advance cursor the 'additional' columns while dirty:ing the cells */ for (int i = 1; i < width && term->grid->cursor.point.col < term->cols - 1; i++) { term->grid->cursor.point.col++; - - struct cell *cell = &row->cells[term->grid->cursor.point.col]; - cell->wc = 0; - cell->attrs.clean = 0; + print_spacer(term, term->grid->cursor.point.col); } /* Advance cursor */ diff --git a/terminal.h b/terminal.h index 4d1cceff..68259160 100644 --- a/terminal.h +++ b/terminal.h @@ -48,6 +48,10 @@ struct attributes { }; static_assert(sizeof(struct attributes) == 8, "bad size"); +#define CELL_COMB_CHARS_LO 0x40000000ul +#define CELL_COMB_CHARS_HI 0x400ffffful +#define CELL_MULT_COL_SPACER 0x40100000ul + struct cell { wchar_t wc; struct attributes attrs; @@ -216,8 +220,6 @@ struct terminal { struct grid alt; struct grid *grid; - #define COMB_CHARS_LO 0x40000000ul - #define COMB_CHARS_HI 0x400ffffful size_t composed_count; struct composed *composed; diff --git a/vt.c b/vt.c index 8d9d6cfa..618611c6 100644 --- a/vt.c +++ b/vt.c @@ -550,9 +550,9 @@ action_utf8_print(struct terminal *term, wchar_t wc) } const struct composed *composed = - (base >= COMB_CHARS_LO && - base < (COMB_CHARS_LO + term->composed_count)) - ? &term->composed[base - COMB_CHARS_LO] + (base >= CELL_COMB_CHARS_LO && + base < (CELL_COMB_CHARS_LO + term->composed_count)) + ? &term->composed[base - CELL_COMB_CHARS_LO] : NULL; if (composed != NULL) @@ -635,7 +635,7 @@ action_utf8_print(struct terminal *term, wchar_t wc) if (cc->combining[wanted_count - 1] != wc) continue; - term_print(term, COMB_CHARS_LO + i, base_width); + term_print(term, CELL_COMB_CHARS_LO + i, base_width); return; } @@ -648,12 +648,12 @@ action_utf8_print(struct terminal *term, wchar_t wc) new_cc.combining[i] = composed->combining[i]; new_cc.combining[wanted_count - 1] = wc; - if (term->composed_count < COMB_CHARS_HI) { + if (term->composed_count < CELL_COMB_CHARS_HI) { term->composed_count++; term->composed = realloc(term->composed, term->composed_count * sizeof(term->composed[0])); term->composed[term->composed_count - 1] = new_cc; - term_print(term, COMB_CHARS_LO + term->composed_count - 1, base_width); + term_print(term, CELL_COMB_CHARS_LO + term->composed_count - 1, base_width); return; } else { /* We reached our maximum number of allowed composed