Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ keeps offering a snapshot that is already in the source.
Whether an entry is hidden is always read back out of `VisibleEntries`, never recomputed — the
rules about when a header exists at all live in one place and must stay there. A fold is a view:
`AcceptAll` still sweeps what it hides, which `CollapseTests` pins.
- Images (`Images/`, extensions in `DiffEngine/Viewer/ImageExtensions.cs`, linked into the viewer so
the tool registration and the renderer cannot disagree) are a side, not a mode. `FileSide.Read`
decides text or picture **by extension**, because the expected side of a new snapshot has no bytes
to sniff, and `ImageRows` produces the same aligned `Row` lists `DiffRows` does — one per property,
coloured against the other side. So every head compares images today with no ABI change. Whether
the two are the same file belongs to the pair rather than to a side, so it is the status line.
`Pane.Image` is an **enrichment**: all three heads paint the picture under those rows, each with
its toolkit's own decoder (GDI+, ImageIO, raylib), so *which formats draw* is per platform while
*what the comparison says* is not. Nothing about a comparison may become expressible only through
the picture, or the text snapshots stop describing what a head without that decoder shows. All
three fit from `ImagePane.Width/Height` — the file header's numbers, not the decoder's — one blank
line under the pane's rows, so the placement rule lives once. Headers are sniffed by hand
(`ImageHeader`) rather than by System.Drawing, which does not exist on macOS or Linux.
- Queue tooltips are composed once in `QueueProjection`, not per head, and are **null when they
would only repeat the row**. Labels are already the shortest distinguishing form, so the tip is
what the label left off — path, test, frameworks, failure text. `QueueTooltipTests` snapshots the
Expand Down Expand Up @@ -149,7 +162,11 @@ keeps offering a snapshot that is already in the source.
any of that, because the binaries are committed.
- `native/src/deview.cpp` is a renderer for the `Screen` model, not an ImGui binding: eight exports
taking one flat blittable frame description. The ABI is `native/include/deview.h`; bump
`DEVIEW_VERSION` whenever the structs change **or a field changes meaning**.
`DEVIEW_VERSION` whenever the structs change **or a field changes meaning**. The managed side
refuses a library whose version is not an exact match, so a bump and a binaries rebuild land
together: change `native/`, run `build-native`, merge the PR it opens. Between the two, the
`native` CI job — the one that loads the committed binaries — reports the mismatch, which is the
check working.
- Built binaries are **committed** to `src/DiffEngineViewer.{Linux,Mac}/runtimes/{rid}/native/`, so a plain
`dotnet build` produces a shippable package and contributors never need CMake. Regenerate them
with the `build-native` GitHub workflow, which opens a PR.
Expand Down
3 changes: 3 additions & 0 deletions docs/diff-tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ DiffTools.UseOrder(DiffTool.DeltaWalker);
* Use shell execute: False
* Create no window: True
* Environment variable for custom install location: `DiffEngine_DiffEngineViewer`
* Supported binaries: .bmp, .gif, .ico, .jpeg, .jpg, .png, .webp

#### Tool order:

Expand All @@ -287,6 +288,8 @@ DiffTools.UseOrder(DiffTool.DiffEngineViewer);
* Also available standalone as `DiffEngineViewer.Windows`, `.Mac` or `.Linux`
* Renders natively per platform: WinForms on Windows, AppKit and Core Text on
macOS, Dear ImGui through raylib on Linux
* Compares images by format, dimensions and content, and draws them, with
whichever formats each platform's own decoder reads

#### Windows settings:

Expand Down
41 changes: 39 additions & 2 deletions docs/mdsource/viewer.source.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# DiffEngineViewer

DiffEngineViewer is a cross platform diff tool for text files and inline snapshots. It is the
reviewer for [inline snapshots](https://github.com/VerifyTests/Verify/blob/main/docs/inline-snapshots.md):
DiffEngineViewer is a cross platform diff tool for text files, images and inline snapshots. It is
the reviewer for [inline snapshots](https://github.com/VerifyTests/Verify/blob/main/docs/inline-snapshots.md):
it shows the received text against the expected text, and accepting rewrites the literal in the
source file.

Expand Down Expand Up @@ -168,6 +168,43 @@ A viewer that owns the queue itself never shows moves or deletes, because DiffEn
them to a running tray.


## Images

`.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp` and `.ico` are compared as pictures rather than as
text, wherever they turn up: a pair passed on the command line, or a move or delete the tray is
holding.

Each pane lists what its own side is — format, pixel dimensions and byte count — with a property
that matches the other side reading as unchanged and one that does not reading as modified, the same
colouring a line of text gets. Whether the two are the same picture belongs to the pair rather than
to either side, so it is stated in the status line: **images are identical**, **images differ**, or
**only \<file\> exists** when one side has nothing yet, which is the normal state of a brand new
image snapshot.

The extension decides, not the content. A `.png` holding something that is not one is still an
image side, and says its format was not recognized instead of rendering the bytes as text.

Each pane also draws the picture itself, one blank line under those rows: fitted to the space,
never enlarged past its own size, on a checkerboard so transparency reads as transparent. All three
heads place it identically, from the size the file's own header gave rather than from whatever
their decoder reported.

Which formats can be drawn is the platform's answer rather than the viewer's, because each head
uses the decoder its toolkit ships with:

| Head | Drawn |
| --- | --- |
| Windows (GDI+) | `.png` `.jpg` `.jpeg` `.gif` `.bmp` `.ico` |
| macOS (ImageIO) | all seven |
| Linux (raylib) | `.png` `.jpg` `.jpeg` `.gif` `.bmp` |

A format a head cannot decode draws nothing, and the comparison is still there in the rows above
it. That is why those rows are the description and the picture is an addition to it.

Accepting is the same act it is for text — copy the received file over the expected one, or forward
the move to the tray — so nothing about reviewing an image changes what accepting one does.


## With DiffEngineTray

The tray starts at login, so it normally binds the port first and holds the queue. The viewer then
Expand Down
41 changes: 39 additions & 2 deletions docs/viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ To change this file edit the source file and then run MarkdownSnippets.

# DiffEngineViewer

DiffEngineViewer is a cross platform diff tool for text files and inline snapshots. It is the
reviewer for [inline snapshots](https://github.com/VerifyTests/Verify/blob/main/docs/inline-snapshots.md):
DiffEngineViewer is a cross platform diff tool for text files, images and inline snapshots. It is
the reviewer for [inline snapshots](https://github.com/VerifyTests/Verify/blob/main/docs/inline-snapshots.md):
it shows the received text against the expected text, and accepting rewrites the literal in the
source file.

Expand Down Expand Up @@ -175,6 +175,43 @@ A viewer that owns the queue itself never shows moves or deletes, because DiffEn
them to a running tray.


## Images

`.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp` and `.ico` are compared as pictures rather than as
text, wherever they turn up: a pair passed on the command line, or a move or delete the tray is
holding.

Each pane lists what its own side is — format, pixel dimensions and byte count — with a property
that matches the other side reading as unchanged and one that does not reading as modified, the same
colouring a line of text gets. Whether the two are the same picture belongs to the pair rather than
to either side, so it is stated in the status line: **images are identical**, **images differ**, or
**only \<file\> exists** when one side has nothing yet, which is the normal state of a brand new
image snapshot.

The extension decides, not the content. A `.png` holding something that is not one is still an
image side, and says its format was not recognized instead of rendering the bytes as text.

Each pane also draws the picture itself, one blank line under those rows: fitted to the space,
never enlarged past its own size, on a checkerboard so transparency reads as transparent. All three
heads place it identically, from the size the file's own header gave rather than from whatever
their decoder reported.

Which formats can be drawn is the platform's answer rather than the viewer's, because each head
uses the decoder its toolkit ships with:

| Head | Drawn |
| --- | --- |
| Windows (GDI+) | `.png` `.jpg` `.jpeg` `.gif` `.bmp` `.ico` |
| macOS (ImageIO) | all seven |
| Linux (raylib) | `.png` `.jpg` `.jpeg` `.gif` `.bmp` |

A format a head cannot decode draws nothing, and the comparison is still there in the rows above
it. That is why those rows are the description and the picture is an addition to it.

Accepting is the same act it is for text — copy the received file over the expected one, or forward
the move to the tray — so nothing about reviewing an image changes what accepting one does.


## With DiffEngineTray

The tray starts at login, so it normally binds the port first and holds the queue. The viewer then
Expand Down
6 changes: 6 additions & 0 deletions native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ set(BUILD_GAMES OFF CACHE BOOL "" FORCE)
set(CUSTOMIZE_BUILD ON CACHE BOOL "" FORCE)
set(SUPPORT_MODULE_RAUDIO OFF CACHE BOOL "" FORCE)
set(SUPPORT_MODULE_RMODELS OFF CACHE BOOL "" FORCE)
# The formats the viewer compares as pictures and raylib can decode. WebP and ICO are also
# compared, and raylib has no decoder for either, so on this platform they draw as their property
# rows alone — which is what those rows are for.
set(SUPPORT_FILEFORMAT_PNG ON CACHE BOOL "" FORCE)
set(SUPPORT_FILEFORMAT_JPG ON CACHE BOOL "" FORCE)
set(SUPPORT_FILEFORMAT_BMP ON CACHE BOOL "" FORCE)
set(SUPPORT_FILEFORMAT_GIF ON CACHE BOOL "" FORCE)

FetchContent_Declare(
raylib
Expand Down
25 changes: 24 additions & 1 deletion native/include/deview.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ typedef struct DeviewPane {
int32_t rowCount;
int32_t scrollTop;
int32_t totalRows;

/*
* The picture this side is, to be drawn under its rows: a path on this machine, carried in the
* same string blob as everything else.
*
* The rows remain the description of an image comparison — format, dimensions and byte count,
* coloured against the other side — and every head draws those. This is an addition on top for
* a head that has a decoder, so a format the platform cannot decode draws nothing and still
* reads correctly. Nothing about a comparison may become expressible only through this.
*
* imagePathLength is 0 for a text side, and for an image side whose bytes could not be read or
* recognized. The managed side has already made that judgement, so a renderer never repeats it.
*
* The size is the one the file's own header gave, not whatever a decoder reports, so all three
* heads fit a picture into a pane from identical numbers.
*/
int32_t imagePathOffset;
int32_t imagePathLength;
int32_t imageWidth;
int32_t imageHeight;
} DeviewPane;

typedef struct DeviewButton {
Expand Down Expand Up @@ -191,8 +211,11 @@ typedef struct DeviewInput {
* and a context menu dismissed without a choice. Between them these are what a head needs to
* draw the queue tip, the pane scrollbar and the menu with the platform's own controls rather
* than its own rectangles.
* 6: DeviewPane carries the picture the side is, so an image comparison is drawn rather than only
* described. A widened array element, so an older library reads every pane after the first at
* the wrong offset — this is the bump that matters most to honour.
*/
#define DEVIEW_VERSION 5
#define DEVIEW_VERSION 6

/*
* The Swift implementation imports this header for the struct layouts, because Swift does not
Expand Down
Loading
Loading