From a6bce0b221b6a331ce7be68fb2d24808fc895da5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 May 2018 14:33:09 +0530 Subject: [PATCH] diff: More work on showing images --- kittens/diff/main.py | 48 ++++++++++++++++++++++++++++++++++++++++-- kittens/diff/render.py | 36 ++++++++++++++++++++----------- kittens/tui/images.py | 30 ++++++++++++++++++-------- 3 files changed, 91 insertions(+), 23 deletions(-) diff --git a/kittens/diff/main.py b/kittens/diff/main.py index adff6b190..1551715fd 100644 --- a/kittens/diff/main.py +++ b/kittens/diff/main.py @@ -155,15 +155,59 @@ class DiffHandler(Handler): def draw_lines(self, num, offset=0): offset += self.scroll_pos + image_involved = False + limit = len(self.diff_lines) for i in range(num): lpos = offset + i - if lpos >= len(self.diff_lines): + if lpos >= limit: text = '' else: - text = self.diff_lines[lpos].text + line = self.diff_lines[lpos] + text = line.text + if line.image_data is not None: + image_involved = True self.write('\r' + text + '\x1b[0m') if i < num - 1: self.write('\n') + if image_involved: + self.place_images() + self.cmd.set_cursor_position(0, self.num_lines - 1) + + def place_images(self): + offset = self.scroll_pos + limit = len(self.diff_lines) + in_image = False + for row in range(self.num_lines): + lpos = offset + row + if lpos >= limit: + break + line = self.diff_lines[lpos] + if in_image: + if line.image_data is None: + in_image = False + continue + if line.image_data is not None: + left_placement, right_placement = line.image_data + if left_placement is not None: + self.place_image(row, left_placement, True) + in_image = True + if right_placement is not None: + self.place_image(row, right_placement, False) + in_image = True + + def place_image(self, row, placement, is_left): + xpos = (0 if is_left else (self.screen_size.cols // 2)) + placement.image.margin_size + image_height_in_rows = placement.image.rows + topmost_visible_row = placement.row + num_visible_rows = image_height_in_rows - topmost_visible_row + visible_frac = min(num_visible_rows / image_height_in_rows, 1) + if visible_frac > 0: + self.cmd.set_cursor_position(xpos, row) + height = int(visible_frac * placement.image.height) + top = placement.image.height - height + self.image_manager.hide_image(placement.image.image_id) + self.image_manager.show_image(placement.image.image_id, src_rect=( + 0, top, placement.image.width, height)) def draw_screen(self): self.enforce_cursor_state() diff --git a/kittens/diff/render.py b/kittens/diff/render.py index 0754b98ad..af86e852f 100644 --- a/kittens/diff/render.py +++ b/kittens/diff/render.py @@ -371,40 +371,47 @@ def rename_lines(path, other_path, args, columns, margin_size): class Image: - def __init__(self, image_id, width, height): + def __init__(self, image_id, width, height, margin_size): self.image_id = image_id self.width, self.height = width, height ss = screen_size() self.rows = int(ceil(self.height / ss.cell_height)) self.columns = int(ceil(self.width / ss.cell_width)) + self.margin_size = margin_size + + +class ImagePlacement: + + def __init__(self, image, row): + self.image = image + self.row = row def render_image(path, is_left, available_cols, margin_size, image_manager): lnum = 0 - m = ' ' * margin_size - image_data = None margin_fmt = removed_margin_format if is_left else added_margin_format + m = margin_fmt(' ' * margin_size) fmt = removed_format if is_left else added_format def yield_split(text): nonlocal lnum for i, line in enumerate(split_to_size(text, available_cols)): - yield margin_fmt(m) + fmt(place_in(line, available_cols)), Reference(path, LineRef(lnum, i)), image_data + yield m + fmt(place_in(line, available_cols)), Reference(path, LineRef(lnum, i)), None lnum += 1 try: - image_id, width, height = image_manager.send_image(path, available_cols - 2, screen_size().rows - 1) + image_id, width, height = image_manager.send_image(path, available_cols - margin_size, screen_size().rows - 1) except Exception as e: yield from yield_split(_('Failed to render image, with error:')) - yield from yield_split(str(e)) + yield from yield_split(' '.join(str(e).splitlines())) return meta = _('Dimensions: {0}x{1} pixels Size: {2}').format( width, height, human_readable(len(data_for_path(path)))) yield from yield_split(meta) - bg_line = margin_fmt(m) + fmt(' ' * available_cols) - img = Image(image_id, width, height) + bg_line = m + fmt(' ' * available_cols) + img = Image(image_id, width, height, margin_size) for r in range(img.rows): - yield bg_line, Reference(path, LineRef(lnum)), (img, r) + yield bg_line, Reference(path, LineRef(lnum)), ImagePlacement(img, r) lnum += 1 @@ -417,12 +424,17 @@ def image_lines(left_path, right_path, columns, margin_size, image_manager): right_lines = render_image(right_path, False, available_cols, margin_size, image_manager) filler = ' ' * (available_cols + margin_size) for left, right in zip_longest(left_lines, right_lines): + left_placement = right_placement = None if left is None: left = filler - right, ref, image_data = right - else: + right, ref, right_placement = right + elif right is None: right = filler - left, ref, image_data = left + left, ref, left_placement = left + else: + right, ref, right_placement = right + left, ref, left_placement = left + image_data = (left_placement, right_placement) if left_placement or right_placement else None yield Line(left + right, ref, image_data=image_data) diff --git a/kittens/tui/images.py b/kittens/tui/images.py index aae7068db..aa1775659 100644 --- a/kittens/tui/images.py +++ b/kittens/tui/images.py @@ -111,6 +111,7 @@ class ImageManager: self.handler = handler self.filesystem_ok = None self.image_data = {} + self.failed_images = {} self.converted_images = {} self.sent_images = {} self.image_id_to_image_data = {} @@ -120,7 +121,7 @@ class ImageManager: @property def next_image_id(self): - return next(self.image_id_counter) + return next(self.image_id_counter) + 2 def __enter__(self): import tempfile @@ -138,7 +139,7 @@ class ImageManager: def delete_all_sent_images(self): for img_id in self.transmission_status: - self.handler.write(serialize_gr_command({'a': 'D', 'i': img_id})) + self.handler.write(serialize_gr_command({'a': 'd', 'i': img_id})) self.transmission_status.clear() def handle_response(self, apc): @@ -161,8 +162,8 @@ class ImageManager: else: in_flight = self.placements_in_flight[image_id] if in_flight: - pl = in_flight.pop(0) - if payload.startswith('ENOENT:') and pl['retry_count'] == 0: + pl = in_flight.popleft() + if payload.startswith('ENOENT:'): try: self.resend_image(image_id, pl) except Exception: @@ -171,7 +172,6 @@ class ImageManager: self.placements_in_flight.pop(image_id, None) def resend_image(self, image_id, pl): - pl['retry_count'] += 1 image_data = self.image_id_to_image_data[image_id] skey = self.image_id_to_converted_data[image_id] self.transmit_image(image_data, image_id, *skey) @@ -179,8 +179,14 @@ class ImageManager: def send_image(self, path, max_cols=None, max_rows=None, scale_up=False): path = os.path.abspath(path) + if path in self.failed_images: + raise self.failed_images[path] if path not in self.image_data: - self.image_data[path] = identify(path) + try: + self.image_data[path] = identify(path) + except Exception as e: + self.failed_images[path] = e + raise m = self.image_data[path] ss = screen_size() if max_cols is None: @@ -192,7 +198,11 @@ class ImageManager: key = path, available_width, available_height skey = self.converted_images.get(key) if skey is None: - self.converted_images[key] = skey = self.convert_image(path, available_width, available_height, m, scale_up) + try: + self.converted_images[key] = skey = self.convert_image(path, available_width, available_height, m, scale_up) + except Exception as e: + self.failed_images[path] = e + raise final_width, final_height = skey[1:] if final_width == 0: return 0, 0, 0 @@ -208,9 +218,11 @@ class ImageManager: def hide_image(self, image_id): self.handler.write(serialize_gr_command({'a': 'd', 'i': image_id})) - def show_image(self, image_id): + def show_image(self, image_id, src_rect=None): cmd = {'a': 'p', 'i': image_id} - self.placements_in_flight[image_id].append({'cmd': cmd, 'retry_count': 0}) + if src_rect is not None: + cmd['x'], cmd['y'], cmd['w'], cmd['h'] = map(int, src_rect) + self.placements_in_flight[image_id].append({'cmd': cmd}) self.handler.write(serialize_gr_command(cmd)) def convert_image(self, path, available_width, available_height, image_data, scale_up=False):