diff --git a/kittens/diff/main.py b/kittens/diff/main.py index 9a5b02d24..1649bcba4 100644 --- a/kittens/diff/main.py +++ b/kittens/diff/main.py @@ -80,7 +80,7 @@ class DiffHandler(Handler): else: text = self.diff_lines[lpos].text self.write(text) - self.write('\n\r') + self.write('\x1b[0m\n\r') def on_key(self, key_event): if self.state is INITIALIZING: diff --git a/kittens/diff/render.py b/kittens/diff/render.py index d5114e680..013f632dd 100644 --- a/kittens/diff/render.py +++ b/kittens/diff/render.py @@ -5,7 +5,7 @@ import re from gettext import gettext as _ -from kitty.fast_data_types import wcswidth +from kitty.fast_data_types import truncate_point_for_length from .collect import data_for_path, path_name_map from .config import formats @@ -60,13 +60,16 @@ def sanitize(text): def fit_in(text, count): - w = wcswidth(text) - if w <= count: + p = truncate_point_for_length(text, count) + if p >= len(text): return text - text = text[:count-1] - while wcswidth(text) > count - 1: - text = text[:-1] - return text + '…' + if count > 1: + p = truncate_point_for_length(text, count - 1) + return text[:p] + '…' + + +def place_in(text, sz): + return fit_in(text, sz).ljust(sz) def format_func(which): @@ -82,10 +85,6 @@ title_format = format_func('title') margin_format = format_func('margin') -def place_in(text, sz): - return fit_in(text, sz).ljust(sz) - - def title_lines(left_path, right_path, args, columns, margin_size): name = fit_in(sanitize(path_name_map[left_path]), columns - 2 * margin_size) yield title_format((' ' + name).ljust(columns)) @@ -99,13 +98,23 @@ def binary_lines(path, other_path, columns, margin_size): def fl(path): text = template.format(human_readable(len(data_for_path(path)))) text = place_in(text, columns // 2 - margin_size) - return margin_format(' ' * margin_size) + text_format(text) + return margin_format(' ' * margin_size) + text_format(text) + '\x1b[0m' return fl(path) + fl(other_path) +def split_to_size(line, width): + while line: + p = truncate_point_for_length(line, width) + yield line[:p] + line = line[p:] + + def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size): - return iter(()) + available_cols = columns // 2 - margin_size + for hunk_num, hunk in enumerate(hunks): + for line_num, (left, right) in enumerate(zip(hunk.left_lines, hunk.right_lines)): + pass def render_diff(collection, diff_map, args, columns): diff --git a/kitty/screen.c b/kitty/screen.c index 6688ab8e7..f291f1098 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1516,6 +1516,52 @@ screen_wcswidth(PyObject UNUSED *self, PyObject *str) { return PyLong_FromUnsignedLong(ans); } + +static PyObject* +screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) { + PyObject *str; unsigned int num_cells; + if (!PyArg_ParseTuple(args, "OI", &str, &num_cells)) return NULL; + if (PyUnicode_READY(str) != 0) return NULL; + int kind = PyUnicode_KIND(str); + void *data = PyUnicode_DATA(str); + Py_ssize_t len = PyUnicode_GET_LENGTH(str), i; + char_type prev_ch = 0; + int prev_width = 0; + bool in_sgr = false; + unsigned long width_so_far = 0; + for (i = 0; i < len && width_so_far < num_cells; i++) { + char_type ch = PyUnicode_READ(kind, data, i); + if (in_sgr) { + if (ch == 'm') in_sgr = false; + continue; + } + if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { in_sgr = true; continue; } + if (ch == 0xfe0f) { + if (is_emoji_presentation_base(prev_ch) && prev_width == 1) { + width_so_far += 1; + prev_width = 2; + } else prev_width = 0; + } else { + int w = wcwidth_std(ch); + switch(w) { + case -1: + case 0: + prev_width = 0; break; + case 2: + prev_width = 2; break; + default: + prev_width = 1; break; + } + if (width_so_far + prev_width > num_cells) { break; } + width_so_far += prev_width; + } + prev_ch = ch; + + } + return PyLong_FromUnsignedLong(i); +} + + static PyObject* line(Screen *self, PyObject *val) { unsigned long y = PyLong_AsUnsignedLong(val); @@ -1987,6 +2033,7 @@ PyTypeObject Screen_Type = { static PyMethodDef module_methods[] = { {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""}, + {"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index de7f46d2f..12a8420f2 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -5,7 +5,7 @@ from kitty.config import build_ansi_color_table, defaults from kitty.fast_data_types import ( REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf, - parse_input_from_terminal, wcswidth, wcwidth + parse_input_from_terminal, wcswidth, wcwidth, truncate_point_for_length ) from kitty.rgb import to_color from kitty.utils import sanitize_title @@ -338,7 +338,15 @@ class TestDataTypes(BaseTest): self.ae(tuple(map(w, 'a1\0コニチ ✔')), (1, 1, 0, 2, 2, 2, 1, 1)) self.ae(wcswidth('\u2716\u2716\ufe0f\U0001f337'), 5) self.ae(wcswidth('\033a\033[2mb'), 2) + tpl = truncate_point_for_length + self.ae(tpl('abc', 4), 3) + self.ae(tpl('abc', 2), 2) + self.ae(tpl('abc', 0), 0) + self.ae(tpl('a\U0001f337', 2), 1) + self.ae(tpl('a\U0001f337', 3), 2) + self.ae(tpl('a\U0001f337b', 4), 3) self.ae(sanitize_title('a\0\01 \t\n\f\rb'), 'a b') + self.ae(tpl('a\x1b[31mbc', 2), 7) def tp(*data, leftover='', text='', csi='', apc='', ibp=False): text_r, csi_r, apc_r, rest = [], [], [], []