Something went wrong. Try again.
Juju is a git/jj interface for the Helix editor
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137;; Copyright (C) 2026 Tom Waddington;;;; This program is free software: you can redistribute it and/or modify;; it under the terms of the GNU Affero General Public License as published by;; the Free Software Foundation, either version 3 of the License, or;; (at your option) any later version.
;;; diff-split.scm - pairing a hunk's lines for side-by-side display;;;;;; A unified hunk lists deletions and additions one after another; a;;; side-by-side view wants them beside each other. This turns one `hunk` into a;;; list of two-sided rows: a context line fills both sides, a run of deletions;;; is zipped index-wise with the run of additions that follows it, and whatever;;; is left over in the longer run becomes one-sided rows.;;;;;; Both line numbers are threaded through, since each side counts in its own;;; file: the old number advances on context and deletions, the new one on;;; context and additions.;;;;;; Each side also carries the deletion's or addition's index within;;; `hunk-lines`, which is what `build-apply-patch`'s include? predicate keys;;; on, so a selected side-by-side row still resolves to exact patch lines.;;; Context lines carry #f there: they are not stageable.;;;;;; Rows and sides are hashes rather than structs: they are display scaffolding,;;; not model values, and the accessors below keep call sites readable. Pure.
(require "model.scm")
(provide split-hunk-rows split-row-kind split-row-left split-row-right split-side-line split-side-text split-side-index)
;;@doc;; The side-by-side rows for `h`, in display order. Each row is a hash:;; 'kind 'context | 'change | 'del | 'add | 'meta;; 'left the old-file side, or #f when the row has none;; 'right the new-file side, or #f;; and each side is a hash of 'line (its number in that file, #f on a 'meta;; row), 'text, and 'index (its position in `hunk-lines`, #f for context).(define (split-hunk-rows h) (walk-lines (hunk-lines h) (car (hunk-old-range h)) (car (hunk-new-range h)) 0 '()))
(define (make-side line text index) (hash 'line line 'text text 'index index))
(define (make-split-row kind left right) (hash 'kind kind 'left left 'right right))
(define (split-row-kind r) (hash-ref r 'kind))(define (split-row-left r) (hash-ref r 'left))(define (split-row-right r) (hash-ref r 'right))(define (split-side-line s) (hash-ref s 'line))(define (split-side-text s) (hash-ref s 'text))(define (split-side-index s) (hash-ref s 'index))
;; `acc` accumulates rows reversed; `j` is the next index into `hunk-lines`.(define (walk-lines lines old new j acc) (cond [(null? lines) (reverse acc)] [(eq? (diff-line-kind (car lines)) 'context) (walk-lines (cdr lines) (+ old 1) (+ new 1) (+ j 1) (cons (context-row (car lines) old new) acc))] ;; "\ No newline at end of file" belongs to neither file and breaks any run ;; it lands in; git only ever emits it at the very end of a hunk. [(eq? (diff-line-kind (car lines)) 'meta) (walk-lines (cdr lines) old new (+ j 1) (cons (meta-row (car lines)) acc))] [else (walk-change lines old new j acc)]))
;; At a deletion or addition: take the run of deletions, then the run of;; additions directly after it, and zip the two into rows.(define (walk-change lines old new j acc) (let* ([dels (take-run lines 'del j)] [adds (take-run (run-rest dels) 'add (run-next-index dels))] [rows (zip-runs (run-entries dels) (run-entries adds) old new '())]) (walk-lines (run-rest adds) (+ old (length (run-entries dels))) (+ new (length (run-entries adds))) (run-next-index adds) (prepend-reversed rows acc))))
;; The leading run of `kind` lines: a hash of 'entries (a list of;; (cons diff-line index) in order), 'rest (the lines after the run) and;; 'next-index.(define (make-run entries rest next-index) (hash 'entries entries 'rest rest 'next-index next-index))
(define (run-entries r) (hash-ref r 'entries))(define (run-rest r) (hash-ref r 'rest))(define (run-next-index r) (hash-ref r 'next-index))
(define (take-run lines kind j) (collect-run lines kind j '()))
(define (collect-run lines kind j acc) (if (and (pair? lines) (eq? (diff-line-kind (car lines)) kind)) (collect-run (cdr lines) kind (+ j 1) (cons (cons (car lines) j) acc)) (make-run (reverse acc) lines j)))
;; Pair deletions with additions position by position; the surplus of the;; longer run becomes one-sided rows.(define (zip-runs dels adds old new acc) (cond [(and (null? dels) (null? adds)) (reverse acc)] [(null? adds) (zip-runs (cdr dels) adds (+ old 1) new (cons (del-row (car dels) old) acc))] [(null? dels) (zip-runs dels (cdr adds) old (+ new 1) (cons (add-row (car adds) new) acc))] [else (zip-runs (cdr dels) (cdr adds) (+ old 1) (+ new 1) (cons (change-row (car dels) (car adds) old new) acc))]))
(define (prepend-reversed rows acc) (if (null? rows) acc (prepend-reversed (cdr rows) (cons (car rows) acc))))
(define (context-row dl old new) (make-split-row 'context (make-side old (diff-line-text dl) #f) (make-side new (diff-line-text dl) #f)))
(define (meta-row dl) (make-split-row 'meta (make-side #f (diff-line-text dl) #f) #f))
(define (del-row entry old) (make-split-row 'del (entry-side entry old) #f))
(define (add-row entry new) (make-split-row 'add #f (entry-side entry new)))
(define (change-row del-entry add-entry old new) (make-split-row 'change (entry-side del-entry old) (entry-side add-entry new)))
(define (entry-side entry line) (make-side line (diff-line-text (car entry)) (cdr entry)))