From 94b549f93e8cc4372e5ab7dc9c832219f337819c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Jun 2021 22:48:20 +0200 Subject: [PATCH] vt: emit a tab character if all cells between cursor and tab stop are empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TAB (\t) move the cursor to the next tab stop. That’s it, according to the specification. However, many terminal emulators try to keep tabs in the grid, to be able to e.g. copy them. That is, copying a text chunk containing tabs should result in tabs being pasted, not spaces. In order to do that, we need to print a tab character to the grid. To improve text reflow of tabs, we also print spaces to the subsequent cells, up until (but not including) the next tab stop. However, we can only do this if all the cells between the cursor and the next tab stop are empty, since (obviously), we cannot overwrite pre-existing characters. Finally, while some fonts render tabs as spaces (i.e. an empty glyph), some use a glyph representing “unprintable” characters, or similar. Thus, we need to exclude cells with tab characters when rendering. --- render.c | 2 +- vt.c | 43 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index 22deaded..68ddf5a6 100644 --- a/render.c +++ b/render.c @@ -620,7 +620,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->wc >= CELL_SPACER || + if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == L'\t' || (unlikely(cell->attrs.conceal) && !is_selected)) { goto draw_cursor; diff --git a/vt.c b/vt.c index d0d215f1..fe7ec52a 100644 --- a/vt.c +++ b/vt.c @@ -162,20 +162,57 @@ action_execute(struct terminal *term, uint8_t c) case '\t': { /* HT - horizontal tab */ + int start_col = term->grid->cursor.point.col; int new_col = term->cols - 1; + tll_foreach(term->tab_stops, it) { - if (it->item > term->grid->cursor.point.col) { + if (it->item > start_col) { new_col = it->item; break; } } - xassert(new_col >= term->grid->cursor.point.col); + xassert(new_col >= start_col); + xassert(new_col < term->cols); + + + bool emit_tab_char = true; + struct row *row = term->grid->cur_row; + + /* Check if all cells from here until the next tab stop are empty */ + for (const struct cell *cell = &row->cells[start_col]; + cell < &row->cells[new_col]; + cell++) + { + if (!(cell->wc == 0 || cell->wc == L' ')) { + emit_tab_char = false; + break; + } + } + + /* + * Emit a tab in current cell, and write spaces to the + * subsequent cells, all the way until the next tab stop. + */ + if (emit_tab_char) { + row->dirty = true; + + row->cells[start_col].wc = '\t'; + row->cells[start_col].attrs.clean = 0; + + for (struct cell *cell = &row->cells[start_col + 1]; + cell < &row->cells[new_col]; + cell++) + { + cell->wc = L' '; + cell->attrs.clean = 0; + } + } /* According to the specification, HT _should_ cancel LCF. But * XTerm, and nearly all other emulators, don't. So we follow * suit */ bool lcf = term->grid->cursor.lcf; - term_cursor_right(term, new_col - term->grid->cursor.point.col); + term_cursor_right(term, new_col - start_col); term->grid->cursor.lcf = lcf; break; }