From 32dfc949090f6f2b5675bdf62f3cb55942d1052c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Aug 2019 12:37:05 +0530 Subject: [PATCH] Implement a hack to (mostly) preserve tabs when cat a file with them and then copying the text or passing screen contents to another program It's a simple enough hack that it seems worth doing. If it causes any issues, can always be reverted. Fixes #1829 --- docs/changelog.rst | 7 +++++++ kitty/fonts.c | 1 + kitty/line.c | 47 +++++++++++++++++++++++++++++++++---------- kitty/screen.c | 18 +++++++++++++++++ kitty_tests/screen.py | 9 +++------ 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0e5c28366..1937ff169 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,13 @@ Changelog |kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator. To update |kitty|, :doc:`follow the instructions `. +0.14.5 [future] +--------------------- + +- Implement a hack to (mostly) preserve tabs when cat a file with them and then + copying the text or passing screen contents to another program (:iss:`1829`) + + 0.14.4 [2019-08-31] --------------------- diff --git a/kitty/fonts.c b/kitty/fonts.c index b88449004..8374fc8dd 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -550,6 +550,7 @@ START_ALLOW_CASE_RANGE switch(cpu_cell->ch) { case 0: case ' ': + case '\t': return BLANK_FONT; case 0x2500 ... 0x2570: case 0x2574 ... 0x259f: diff --git a/kitty/line.c b/kitty/line.c index 2cb544399..8ca62de9a 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -187,15 +187,19 @@ size_t cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf) { size_t n = 1; buf[0] = cell->ch ? cell->ch : ' '; - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) buf[n++] = codepoint_for_mark(cell->cc_idx[i]); - } + if (buf[0] != '\t') { + for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { + if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) buf[n++] = codepoint_for_mark(cell->cc_idx[i]); + } + } else buf[0] = ' '; return n; } size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type zero_char) { - size_t n = encode_utf8(cell->ch ? cell->ch : zero_char, buf); + char_type ch = cell->ch ? cell->ch : zero_char; + if (ch == '\t') { include_cc = false; } + size_t n = encode_utf8(ch, buf); if (include_cc) { for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n); } @@ -205,10 +209,15 @@ cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type zero_char) { size_t cell_as_utf8_for_fallback(CPUCell *cell, char *buf) { - size_t n = encode_utf8(cell->ch ? cell->ch : ' ', buf); - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) { - n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n); + char_type ch = cell->ch ? cell->ch : ' '; + bool include_cc = true; + if (ch == '\t') { ch = ' '; include_cc = false; } + size_t n = encode_utf8(ch, buf); + if (include_cc) { + for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { + if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) { + n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n); + } } } buf[n] = 0; @@ -228,7 +237,16 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc if (ch == 0) { if (previous_width == 2) { previous_width = 0; continue; }; } - n += cell_as_unicode(self->cpu_cells + i, include_cc, buf + n, ' '); + if (ch == '\t') { + buf[n++] = '\t'; + unsigned num_cells_to_skip_for_tab = self->cpu_cells[i].cc_idx[0]; + while (num_cells_to_skip_for_tab && i + 1 < limit && self->cpu_cells[i+1].ch == ' ') { + i++; + num_cells_to_skip_for_tab--; + } + } else { + n += cell_as_unicode(self->cpu_cells + i, include_cc, buf + n, ' '); + } previous_width = self->gpu_cells[i].attrs & WIDTH_MASK; } return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n); @@ -288,8 +306,15 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool *truncated, const } *prev_cell = cell; WRITE_CH(ch); - for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) { - WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c])); + if (ch == '\t') { + unsigned num_cells_to_skip_for_tab = self->cpu_cells[pos].cc_idx[0]; + while (num_cells_to_skip_for_tab && pos + 1 < limit && self->cpu_cells[pos+1].ch == ' ') { + num_cells_to_skip_for_tab--; pos++; + } + } else { + for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) { + WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c])); + } } previous_width = cell->attrs & WIDTH_MASK; } diff --git a/kitty/screen.c b/kitty/screen.c index 2a50edb59..7a888973c 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -737,6 +737,24 @@ screen_tab(Screen *self) { } if (!found) found = self->columns - 1; if (found != self->cursor->x) { + if (self->cursor->x < self->columns) { + linebuf_init_line(self->linebuf, self->cursor->y); + combining_type diff = found - self->cursor->x; + CPUCell *cpu_cell = self->linebuf->line->cpu_cells + self->cursor->x; + bool ok = true; + for (combining_type i = 0; i < diff; i++) { + CPUCell *c = cpu_cell + i; + if (c->ch != ' ' && c->ch != 0) { ok = false; break; } + } + if (ok) { + for (combining_type i = 0; i < diff; i++) { + CPUCell *c = cpu_cell + i; + c->ch = ' '; zero_at_ptr_count(c->cc_idx, arraysz(c->cc_idx)); + } + cpu_cell->ch = '\t'; + cpu_cell->cc_idx[0] = diff; + } + } self->cursor->x = found; } } diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 94c1890ea..ec60e5561 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -293,11 +293,7 @@ class TestScreen(BaseTest): s.tab() s.draw('*') s.cursor_position(2, 2) - for col in range(2, s.columns - 1, 6): - for i in range(5): - s.draw(' ') - s.draw('*') - self.ae(str(s.line(0)), str(s.line(1))) + self.ae(str(s.line(0)), '\t*'*13) def test_margins(self): # Taken from vttest/main.c @@ -338,7 +334,8 @@ class TestScreen(BaseTest): s.cursor_position(region, s.columns), s.draw(ch.lower()) for l in range(2, region + 2): c = chr(ord('I') + l - 2) - self.ae(c + ' ' * (s.columns - 2) + c.lower(), str(s.line(l))) + before = '\t' if l % 4 == 0 else ' ' + self.ae(c + ' ' * (s.columns - 3) + before + c.lower(), str(s.line(l))) s.reset_mode(DECOM) # Test that moving cursor outside the margins works as expected s = self.create_screen(10, 10)