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
This commit is contained in:
Kovid Goyal 2019-08-31 12:37:05 +05:30
parent 20f7118432
commit 32dfc94909
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 65 additions and 17 deletions

View File

@ -4,6 +4,13 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
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]
---------------------

View File

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

View File

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

View File

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

View File

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