summaryrefslogtreecommitdiff
path: root/docs/diff-pager-proposals.md
blob: f75361df6dd427ff498c27b0cd1849b75cf78ad5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Diffs in a pager: what delta does, what pardes could

Proposals, 2026-10-01, none decided. They come from reading delta,
diff-so-fancy and difftastic (all in `~/05-genizah/`) against pardes as of
`3815a0bf`, the commit that made `pardes -` a terminal's `PAGER` and
`GIT_PAGER`. The rule kept throughout: the pane's text stays the plain
text git printed, and whatever is added is something a look or an exec
can act on.

## How the others read a diff

**delta** reads stdin a line at a time and keeps each line twice: the raw
bytes and a copy with the ANSI stripped (`delta.rs:235`). Every match runs
on the stripped copy, so git's colour does not change the parse. The raw
copy is kept for two cases where colour is the information: a hunk line
whose colour is not git's plain red or green (`--color-moved`), and every
line under `--word-diff` or `--color-words`, which pass through as git
coloured them (`hunk.rs:136-171`). A state machine (`delta.rs:20-38`) moves
on `commit`, `diff`, `---`/`+++`, `@@`, and the `-`/`+`/space prefixes, with
further states for merge conflicts, submodules, `git blame`, `git grep` and
`git show REV:path`. Which git command it is reading it learns from its
parent process's command line (`utils/process.rs`), down to scanning the
pids next to its own. Within a line, it pairs each removed line with the
first added line within 0.6 edit distance, tokenises both on `\w+` and
aligns them (`edits.rs`, `align.rs`). Its OSC 8 links name only the new
side's `file:line`. `--navigate` writes `Δ` and `•` before file and hunk
headers and puts a search for them into a copy of less's history file, so
that `n` finds them.

**diff-so-fancy** matches git's colour codes where they appear, colouring
for itself where there are none. It rewrites the diff for show: file
headers become a box, `@@ -a,b +c,d @@` becomes `@ file:N @`, and the
`+`/`-` markers go, leaving old against new to colour. Within a line it
uses git's diff-highlight, which pairs lines only in a block with as many
added as removed lines, and marks what lies between a common prefix and a
common suffix. Behind `OSC1717=V1` it emits a per-line escape carrying the
line's kind, its old and new numbers and its path. It is the only one of
the three whose output gives every line its place in both files.

**difftastic** reads no diff. Git runs it as the diff program
(`GIT_EXTERNAL_DIFF`) and hands it both whole files. It parses each with
tree-sitter and finds the cheapest set of changes with Dijkstra, falling
back to a line diff past 1 MB, 3M graph vertices or any parse error. What
it prints is not a patch: change is shown only by colour, and its JSON is
unstable and counts lines from 0.

## What pardes does now

`pardes -` reads stdin, strips terminal escapes (`stripEscapes`,
`main.zig`), and puts the text in a `<cwd>/+Pager` pane. Git starts its
pager at the top of the work tree even when run from a subdirectory
(checked), so `<cwd>` is already the base git's repository-relative paths
need.

A pane that is a diff is read by `src/diff.zig`. It is already at delta's
level, or past it:

- A hunk's end comes from its `@@` counts, as patch reads it, so a removed
  `--- x` line is never taken for a header. delta only counts this way for
  a plain `diff -u`.
- `syntax.highlightDiff` colours each hunk's old side and new side each as
  one piece in the file's own language, so a string or a comment that
  spans lines colours as it does in the file. delta runs one highlighter
  through the removed lines and then the added ones.
- git's other prefixes (`c/`, `i/`, `w/`, `--no-prefix`) are understood, and
  so are rows a terminal wrapped.
- A look on any line goes to the place it names (`look.zig:1573`): a file
  header to the file, an `@@` or a hunk line to `path:N`. delta's links
  are the nearest thing it has.

## Gaps

1. **A +Pager pane is never a diff.** Whether a pane is coloured and looked
   at as a diff is decided by its name ending `.diff` or `.patch`
   (`panes.zig:451`); only a finished command pane is checked by content
   (`looksLikeDiff`, `File.zig:1545`). So `git log -p` in a terminal pages
   to plain text, with no colour and no look on its lines. (From reading
   the code, not yet run.)
2. **Some of what git says is only colour, and stripping loses it.** With
   `git diff --color-words`, `foo(1)` changed to `bar(1)` arrives as
   `let x = foo(1);bar(1);` (checked). `--color-moved` loses its moved
   blocks the same way.
3. **No changed words within a line**, which is most of what people use
   delta for.
4. **Nothing for commits or blame.** A `git grep` line, `path:N:text`, is a
   look already.

## Proposals

### A. Read +Pager as a diff when it is one

Either `colorAlgo` checks a +Pager pane's content with `looksLikeDiff`, or
`pardes -` names the pane `+Pager.diff` when what it read is a diff. The
name has the merit that a 9P client, or a reader of the tag, sees what the
pane is. Either is a few lines, and everything in `diff.zig`, the
painter and the look comes along. Leaning to the content check, so that a
name always means where the text came from.

### B. Turn colour that carries meaning into git's own text

While stripping, a red or green run inside a hunk line under word diff
becomes `[-foo(1)-]` or `{+bar(1)+}`: exactly what `git diff
--word-diff=plain` prints. The pane stays plain text, searchable and
editable, and says what the colour said. `--color-moved` has no text form
in git; it can be kept as a style layer beside the text, dropped at the
first edit, or let go.

### C. Changed words within a line, drawn only

Pair removed lines with added ones and align their tokens as delta does
(edit distance, then an alignment table), and set an emph bit in the
style bytes beside `diff_added`/`diff_removed`. The text is untouched. A
change made only of whitespace, an indent or a gap, is not marked (the
patch carried in our own delta build, `local-patches` in the genizah).
Later, the tokens could be tree-sitter's leaves in place of `\w+`, which
gets much of difftastic's quality without its graph search.

### D. Moving through a diff by address, not by marks

delta's marks exist because less cannot search by structure. In pardes
`:/^@@/` and `:/^diff /` are text, and a look of them moves. They can sit
in a diff pane's tag. Or an `Index` exec writes a +Index pane with one
line per file or hunk, each a look:

    @p12:345 src/look.zig +12 -3

### E. A diff you can act on

This is what a pager that only prints cannot do.

- **Stage, Unstage, Revert** in the tag: the hunk under the cursor, with
  its file header and its `@@` counts recounted, written to
  `git apply --cached`, `git apply --cached -R`, or `git apply -R`.
- **Apply** for the whole pane: edit the diff, then apply it, which is
  `git add -p`'s `e` for a whole buffer.
- **The old side.** A look on a removed line goes today to the new line now
  standing where it was. The `index e1f909f..0136052` line names the old
  blob, so a look on a removed line can open `git cat-file -p e1f909f` at
  its old line instead.
- **Commits.** A look on `commit abc1234`, or on a blame line's hash, runs
  `git show abc1234` into another +Pager, as acme's plumber would.

These can be helpers over 9P (a script reads the pane's body and dot,
runs git, writes back), in keeping with the roadmap's helper step, rather
than code in the core.

### F. Further off

- **pardes as git's diff program**, where difftastic sits: it is handed
  both whole files and still prints an ordinary unified diff, aligned by
  syntax tree. Not worth it until C with tree-sitter tokens falls short.
- **Reading OSC 1717**, so that a diff from delta or diff-so-fancy, whose
  text is too rewritten to parse again, can still be looked at line by
  line.

## Open questions

1. A: check the content, or name the pane `.diff`?
2. B: word colour to `[-…-]{+…+}` text, a style layer beside the text, or
   both?
3. E: in the core, or as helpers over 9P?

Suggested order: A, then the text half of B, then E's commit and old-side
looks, then C.