diff --git a/kittens/diff/main.py b/kittens/diff/main.py index de56be48e..9e021fa04 100644 --- a/kittens/diff/main.py +++ b/kittens/diff/main.py @@ -4,6 +4,7 @@ import os import sys +from collections import defaultdict from functools import partial from gettext import gettext as _ @@ -17,7 +18,7 @@ from ..tui.loop import Loop from .collect import create_collection, data_for_path, set_highlight_data from .config import init_config from .patch import Differ -from .render import render_diff +from .render import LineRef, render_diff try: from .highlight import initialize_highlighter, highlight_collection @@ -49,18 +50,47 @@ class DiffHandler(Handler): self.report_traceback_on_exit = None self.args = args self.scroll_pos = self.max_scroll_pos = 0 + self.current_context_count = self.args.context self.highlighting_done = False + self.restore_position = None def create_collection(self): self.start_job('collect', create_collection, self.left, self.right) def generate_diff(self): - self.start_job('diff', generate_diff, self.collection, self.args.context) + self.start_job('diff', generate_diff, self.collection, self.current_context_count) def render_diff(self): self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols)) + self.ref_path_map = defaultdict(list) + for i, l in enumerate(self.diff_lines): + self.ref_path_map[l.ref.path].append((i, l.ref)) self.max_scroll_pos = len(self.diff_lines) - self.num_lines + @property + def current_position(self): + return self.diff_lines[min(len(self.diff_lines) - 1, self.scroll_pos)].ref + + @current_position.setter + def current_position(self, ref): + num = None + if isinstance(ref.extra, LineRef): + sln = ref.extra.src_line_number + for i, q in self.ref_path_map[ref.path]: + if isinstance(q.extra, LineRef): + if q.extra.src_line_number >= sln: + if q.extra.src_line_number == sln: + num = i + break + num = i + if num is None: + for i, q in self.ref_path_map[ref.path]: + num = i + break + + if num is not None: + self.scroll_pos = min(num, self.max_scroll_pos) + @property def num_lines(self): return self.screen_size.rows - 1 @@ -132,6 +162,15 @@ class DiffHandler(Handler): self.cmd.clear_to_eol() self.write(':') + def change_context_count(self, new_ctx): + new_ctx = max(0, new_ctx) + if new_ctx != self.current_context_count: + self.current_context_count = new_ctx + self.state = COLLECTED + self.generate_diff() + self.restore_position = self.current_position + self.draw_screen() + def on_text(self, text, in_bracketed_paste=False): if text == 'q': if self.state <= DIFFED: @@ -141,6 +180,15 @@ class DiffHandler(Handler): if text in 'jk': self.scroll_lines(1 if text == 'j' else -1) return + if text in 'a+-=': + new_ctx = self.current_context_count + if text == 'a': + new_ctx = 100000 + elif text == '=': + new_ctx = 3 + else: + new_ctx += (-1 if text == '-' else 1) * 5 + self.change_context_count(new_ctx) def on_key(self, key_event): if key_event.type is RELEASE: @@ -176,6 +224,7 @@ class DiffHandler(Handler): return if job_id == 'collect': self.collection = job_result['result'] + self.state = COLLECTED self.generate_diff() elif job_id == 'diff': diff_map = job_result['result'] @@ -186,6 +235,10 @@ class DiffHandler(Handler): self.state = DIFFED self.diff_map = diff_map self.render_diff() + self.scroll_pos = 0 + if self.restore_position is not None: + self.current_position = self.restore_position + self.restore_position = None self.draw_screen() if initialize_highlighter is not None and not self.highlighting_done: self.highlighting_done = True diff --git a/kittens/diff/render.py b/kittens/diff/render.py index 6049a9351..1dad3495b 100644 --- a/kittens/diff/render.py +++ b/kittens/diff/render.py @@ -15,32 +15,32 @@ from .config import formats from .diff_speedup import split_with_highlights as _split_with_highlights -class HunkRef: +class Ref: - __slots__ = ('hunk_num', 'line_num', 'chunk_num') + def __setattr__(self, name, value): + raise AttributeError("can't set attribute") - def __init__(self, hunk_num, chunk_num=None, line_num=None): - self.hunk_num = hunk_num - self.chunk_num = chunk_num - self.line_num = line_num + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, ', '.join( + '{}={}'.format(n, getattr(self, n)) for n in self.__slots__ if n != '_hash')) -class LineRef: +class LineRef(Ref): __slots__ = ('src_line_number', 'wrapped_line_idx') - def __init__(self, sln, wli): - self.src_line_number = sln - self.wrapped_line_idx = wli + def __init__(self, sln, wli=0): + object.__setattr__(self, 'src_line_number', sln) + object.__setattr__(self, 'wrapped_line_idx', wli) -class Reference: +class Reference(Ref): __slots__ = ('path', 'extra') def __init__(self, path, extra=None): - self.path = path - self.extra = extra + object.__setattr__(self, 'path', path) + object.__setattr__(self, 'extra', extra) class Line: @@ -243,41 +243,42 @@ def render_half_line(line_number, line, highlights, ltype, margin_size, availabl def lines_for_chunk(data, hunk_num, chunk, chunk_num): if chunk.is_context: for i in range(chunk.left_count): - left_line_number = chunk.left_start + i + left_line_number = line_ref = chunk.left_start + i right_line_number = chunk.right_start + i highlights = data.left_highlights_for_line(left_line_number) if highlights: lines = split_with_highlights(data.left_lines[left_line_number], data.available_cols, highlights) else: lines = split_to_size(data.left_lines[left_line_number], data.available_cols) - ref = Reference(data.left_path, HunkRef(hunk_num, chunk_num, i)) left_line_number = str(left_line_number + 1) right_line_number = str(right_line_number + 1) - for text in lines: + for wli, text in enumerate(lines): line = render_diff_line(left_line_number, text, 'context', data.margin_size, data.available_cols) if right_line_number == left_line_number: r = line else: r = render_diff_line(right_line_number, text, 'context', data.margin_size, data.available_cols) + ref = Reference(data.left_path, LineRef(line_ref, wli)) yield Line(line + r, ref) left_line_number = right_line_number = '' else: common = min(chunk.left_count, chunk.right_count) for i in range(max(chunk.left_count, chunk.right_count)): - ref = Reference(data.left_path, HunkRef(hunk_num, chunk_num, i)) ll, rl = [], [] if i < chunk.left_count: - rln = chunk.left_start + i + rln = ref_ln = chunk.left_start + i ll.extend(render_half_line( rln, data.left_lines[rln], data.left_highlights_for_line(rln), 'remove', data.margin_size, data.available_cols, None if chunk.centers is None else chunk.centers[i])) + ref_path = data.left_path if i < chunk.right_count: - rln = chunk.right_start + i + rln = ref_ln = chunk.right_start + i rl.extend(render_half_line( rln, data.right_lines[rln], data.right_highlights_for_line(rln), 'add', data.margin_size, data.available_cols, None if chunk.centers is None else chunk.centers[i])) + ref_path = data.right_path if i < common: extra = len(ll) - len(rl) if extra != 0: @@ -293,7 +294,8 @@ def lines_for_chunk(data, hunk_num, chunk, chunk_num): else: x, count = ll, len(rl) x.extend(repeat(data.filler_line, count)) - for left_line, right_line in zip(ll, rl): + for wli, (left_line, right_line) in enumerate(zip(ll, rl)): + ref = Reference(ref_path, LineRef(ref_ln, wli)) yield Line(left_line + right_line, ref) @@ -302,7 +304,7 @@ def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size): data = DiffData(left_path, right_path, available_cols, margin_size) for hunk_num, hunk in enumerate(hunks): - yield Line(hunk_title(hunk_num, hunk, margin_size, columns - margin_size), Reference(left_path, HunkRef(hunk_num))) + yield Line(hunk_title(hunk_num, hunk, margin_size, columns - margin_size), Reference(left_path, LineRef(hunk.left_start))) for cnum, chunk in enumerate(hunk.chunks): yield from lines_for_chunk(data, hunk_num, chunk, cnum)