Repository: timbrel/gitsavvy
Issue: Open file from diff in word-diff mode throws

This is a regression of the accurate jumping. Honestly I hardly knew about the word diff mode, and it's not part of my workflow but the accurate jumping expects a unified diff otherwise it will raise bc it depends on the line mode char +- on each line.

There is no trivial fix I guess bc for accurate jumping we need to differentiate between available and removed chars.

Probably three options.

1. As a hotfix just disable the open functionality.
2. Do inaccurate jumping as before.
3. Just parse it 🤪 as well.


Changed files:
core/commands/diff.py

diff --git a/core/commands/diff.py b/core/commands/diff.py
index 9b3dab58..4f6ce813 100644
--- a/core/commands/diff.py
+++ b/core/commands/diff.py
@@ -685,8 +685,14 @@ class GsDiffOpenFileAtHunkCommand(TextCommand, GitCommand):
                     seen.add(filename)
                     yield item
 
+        word_diff_mode = bool(self.view.settings().get('git_savvy.diff_view.show_word_diff'))
         diff = parse_diff_in_view(self.view)
-        jump_positions = filter_(self.jump_position_to_file(diff, pt) for pt in cursor_pts)
+        algo = (
+            self.jump_position_to_file_for_word_diff_mode
+            if word_diff_mode
+            else self.jump_position_to_file
+        )
+        jump_positions = filter_(algo(diff, pt) for pt in cursor_pts)
         for jp in first_per_file(jump_positions):
             self.load_file_at_line(*jp)
 
@@ -738,6 +744,71 @@ class GsDiffOpenFileAtHunkCommand(TextCommand, GitCommand):
 
         return filename, row, col
 
+    def jump_position_to_file_for_word_diff_mode(self, diff, pt):
+        # type: (ParsedDiff, int) -> Optional[Tuple[str, int, int]]
+        head_and_hunk_offsets = head_and_hunk_for_pt(diff, pt)
+        if not head_and_hunk_offsets:
+            return None
+
+        view = self.view
+        header_region, hunk_region = head_and_hunk_offsets
+        header = extract_content(view, header_region)
+        hunk = extract_content(view, hunk_region)
+        hunk_start, _ = hunk_region
+
+        # The hunk contains itself a header line e.g. "@@ -686,8 +686,14 @@ ...",
+        # so the actual content of the hunk starts at the line after that.
+        content_start = view.full_line(hunk_start).end()
+
+        # Select all "deletion" regions in the hunk up to the cursor (pt)
+        removed_regions_before_pt = [
+            # In case the cursor is *in* a region, shorten it up to
+            # the cursor.
+            sublime.Region(region.begin(), min(region.end(), pt))
+            for region in view.get_regions('git-savvy-removed-bold')
+            if content_start <= region.begin() < pt
+        ]
+
+        # Select all completely removed lines, but exclude lines
+        # if the cursor is exactly at the end-of-line char.
+        removed_lines_before_pt = sum(
+            region == view.line(region.begin()) and region.end() != pt
+            for region in removed_regions_before_pt
+        )
+        line_start = view.line(pt).begin()
+        removed_chars_before_pt = sum(
+            region.size()
+            for region in removed_regions_before_pt
+            if line_start <= region.begin() < pt
+        )
+
+        # Compute the *relative* row in that hunk
+        head_row, _ = view.rowcol(content_start)
+        pt_row, col = view.rowcol(pt)
+        rel_row = pt_row - head_row
+        # If the cursor is in the hunk header, assume instead it is
+        # at `(0, 0)` position in the hunk content.
+        if rel_row < 0:
+            rel_row, col = 0, 0
+
+        # Extract the starting line at "b" encoded in the hunk header t.i. for
+        # "@@ -685,8 +686,14 @@ ..." extract the "686".
+        head, *tail = hunk.rstrip().split('\\n')
+        match = HUNKS_LINES_RE.search(head)
+        if not match:
+            return None
+
+        b = int(match.group(2))
+        row = b + rel_row
+
+        filename = extract_filename_from_header(header)
+        if not filename:
+            return None
+
+        row = row - removed_lines_before_pt
+        col = col + 1 - removed_chars_before_pt
+        return filename, row, col
+
 
 def relative_rowcol_in_hunk(view, hunk_start, pt):
     # type: (sublime.View, Point, Point) -> RowCol