vt: emit a tab character if all cells between cursor and tab stop are empty

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.
This commit is contained in:
Daniel Eklöf 2021-06-05 22:48:20 +02:00
parent e77b7d7111
commit 94b549f93e
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 41 additions and 4 deletions

View file

@ -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;

43
vt.c
View file

@ -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;
}