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
13 changes: 13 additions & 0 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ apart.
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.
- Text selection is a view, and makes the same bargain images do. A drag arrives as both of its
ends at once, in rows of the whole side rather than of the visible slice: a head knows the scroll
top it drew the press with, so only it can resolve one that spans a wheel notch, and reporting
the pair every held frame rather than press/move/release events is what makes a whole
press-drag-release inside one frame arrive whole. `SessionState.Selection` names the entry it was
dragged in, so a stale one stops existing (`LiveSelection`) rather than needing a clear on every
transition, one of which would eventually be missed. `Row.Selection` is the run to highlight,
which the three pixel heads draw and `AsciiRenderer` cannot - a character grid has no way to
invert part of a line without changing its width - so what the model universally states about a
selection goes in the **status line**, where every renderer draws it, and the highlight is the
enrichment on top. Copying is `IViewerWindow.SetClipboard` rather than a `ViewerActions` member,
because a clipboard belongs to a toolkit the way a window does, and it is answered before the
owner link: the text is already in this process.
- 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
14 changes: 13 additions & 1 deletion docs/mdsource/viewer.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,20 @@ Nothing is written to disk for inline review. The patch travels over stdin, or o
| `Shift+A` | Accept all |
| `d` | Discard |
| `v` | Cycle the variants of a conflicted snapshot (also the Variant button) |
| `Ctrl+A` | Select all of one pane |
| `Ctrl+C` | Copy the selection |
| `q` `Esc` | Close |


## Selecting and copying

Drag across either pane to select text, and `Ctrl+C` to copy it. `Ctrl+A` selects one whole pane: the one something is already selected in, or the received side when nothing is. On macOS the Edit menu carries both, so `Cmd+C` and `Cmd+A` work there too.

A selection belongs to one pane. Dragging out of it keeps extending within the side the drag started in rather than crossing into the other, because the two sides are different documents. It survives scrolling, so a range taller than the window is a drag plus a wheel. The status line says how much is selected, and a click with no drag behind it clears it.

What lands on the clipboard is what is on screen: tabs already expanded to the four spaces the panes draw them as, and no line numbers or change markers. Filler rows are left out — the blank lines that keep the two panes aligned where one side has no line are padding rather than content, so pasting a selection back gives the file's lines and nothing else.


## Multiple pending snapshots

A test run that fails several inline snapshots produces one window, not several. Whichever process binds the loopback port holds the queue; everything else hands its patch to that one. The window lists everything pending and offers **Accept all**.
Expand All @@ -103,12 +114,13 @@ Every row of the pending column answers a right-click:
* An inline snapshot offers **Accept**, **Discard** and **Open source file**, plus **Show next variant** when frameworks disagree about it.
* A move offers **Accept move**, **Discard** and **Open target directory**; a delete offers **Accept delete**, **Discard** and **Open directory**.
* A solution header offers **Accept all in ...** and **Discard all in ...** for that solution only, and a test sub-header the same for that test's changes. Bulk accepts skip conflicted snapshots, the way accept-all does.
* Every entry also offers **Copy selection** when there is one, and a **Copy** item per pane, named after that pane, which copies the whole side. A side with nothing in it — the expected side of a brand new snapshot, or what is left after a delete — gets no item rather than one that copies nothing.

Right-clicking an entry selects it first, so the menu acts on what is highlighted. Opening a file manager is always local — the files are on this machine, wherever the queue lives.

On Windows and macOS this is the real OS menu, so it also takes the arrow keys, Enter, Escape and type-to-select, flips rather than clips near the edge of a screen, and is readable by a screen reader. A click that dismisses it is consumed doing so, which is why right-clicking a different row while a menu is open takes two clicks. On Linux it is drawn by the viewer, and any other click or key closes it.

macOS also carries a menu bar, listing the same commands as the keys below.
macOS also carries a menu bar: a Snapshot menu listing the same commands as the keys above, and an Edit menu with Copy and Select All. Those two are the only items carrying a key equivalent, because the rest are plain letters and a menu would match them before the window ever saw them.


## Grouping
Expand Down
14 changes: 13 additions & 1 deletion docs/viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,20 @@ Nothing is written to disk for inline review. The patch travels over stdin, or o
| `Shift+A` | Accept all |
| `d` | Discard |
| `v` | Cycle the variants of a conflicted snapshot (also the Variant button) |
| `Ctrl+A` | Select all of one pane |
| `Ctrl+C` | Copy the selection |
| `q` `Esc` | Close |


## Selecting and copying

Drag across either pane to select text, and `Ctrl+C` to copy it. `Ctrl+A` selects one whole pane: the one something is already selected in, or the received side when nothing is. On macOS the Edit menu carries both, so `Cmd+C` and `Cmd+A` work there too.

A selection belongs to one pane. Dragging out of it keeps extending within the side the drag started in rather than crossing into the other, because the two sides are different documents. It survives scrolling, so a range taller than the window is a drag plus a wheel. The status line says how much is selected, and a click with no drag behind it clears it.

What lands on the clipboard is what is on screen: tabs already expanded to the four spaces the panes draw them as, and no line numbers or change markers. Filler rows are left out — the blank lines that keep the two panes aligned where one side has no line are padding rather than content, so pasting a selection back gives the file's lines and nothing else.


## Multiple pending snapshots

A test run that fails several inline snapshots produces one window, not several. Whichever process binds the loopback port holds the queue; everything else hands its patch to that one. The window lists everything pending and offers **Accept all**.
Expand All @@ -110,12 +121,13 @@ Every row of the pending column answers a right-click:
* An inline snapshot offers **Accept**, **Discard** and **Open source file**, plus **Show next variant** when frameworks disagree about it.
* A move offers **Accept move**, **Discard** and **Open target directory**; a delete offers **Accept delete**, **Discard** and **Open directory**.
* A solution header offers **Accept all in ...** and **Discard all in ...** for that solution only, and a test sub-header the same for that test's changes. Bulk accepts skip conflicted snapshots, the way accept-all does.
* Every entry also offers **Copy selection** when there is one, and a **Copy** item per pane, named after that pane, which copies the whole side. A side with nothing in it — the expected side of a brand new snapshot, or what is left after a delete — gets no item rather than one that copies nothing.

Right-clicking an entry selects it first, so the menu acts on what is highlighted. Opening a file manager is always local — the files are on this machine, wherever the queue lives.

On Windows and macOS this is the real OS menu, so it also takes the arrow keys, Enter, Escape and type-to-select, flips rather than clips near the edge of a screen, and is readable by a screen reader. A click that dismisses it is consumed doing so, which is why right-clicking a different row while a menu is open takes two clicks. On Linux it is drawn by the viewer, and any other click or key closes it.

macOS also carries a menu bar, listing the same commands as the keys below.
macOS also carries a menu bar: a Snapshot menu listing the same commands as the keys above, and an Edit menu with Copy and Select All. Those two are the only items carrying a key equivalent, because the rest are plain letters and a menu would match them before the window ever saw them.


## Grouping
Expand Down
53 changes: 51 additions & 2 deletions native/include/deview.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ typedef struct DeviewRow {
int32_t lineNumber;
int32_t textOffset;
int32_t textLength;

/*
* What of this row the reader has selected, in characters of the text above rather than in
* pixels: the managed side flattens tabs before it counts, so a column here multiplied by the
* cell width is where the highlight goes.
*
* selectLength is 0 on a row with nothing selected, which is every row of almost every frame.
* The managed side has already resolved which side the drag is in and clipped the range to the
* visible slice, so a renderer only fills a rectangle and never decides what is in it.
*/
int32_t selectStart;
int32_t selectLength;
} DeviewRow;

typedef struct DeviewPane {
Expand Down Expand Up @@ -168,7 +180,11 @@ enum DeviewKey {
DEVIEW_KEY_DISCARD = 12,
DEVIEW_KEY_ACCEPT_ALL = 13,
DEVIEW_KEY_QUIT = 14,
DEVIEW_KEY_NEXT_VARIANT = 15
DEVIEW_KEY_NEXT_VARIANT = 15,
/* Ctrl+C, and Cmd+C on macOS. */
DEVIEW_KEY_COPY = 16,
/* Ctrl+A, which is why plain A must be reported as accept only when no modifier is held. */
DEVIEW_KEY_SELECT_ALL = 17
};

typedef struct DeviewInput {
Expand Down Expand Up @@ -203,6 +219,26 @@ typedef struct DeviewInput {
*/
int32_t columns;
int32_t rows;

/*
* A drag selecting pane text: which pane (0 left, 1 right, -1 for the frames with no button
* held over one), and both of its ends.
*
* Reported together for as long as the button is held and then simply not reported, rather
* than as press, move and release events. The managed side already holds the selection by
* then, so a release has nothing to add, and a whole press-drag-release landing inside one
* frame still arrives whole.
*
* Rows are rows of the whole side, not of the visible slice: this side drew the press with a
* scrollTop it still remembers, so only this side can resolve a drag that spans a wheel notch.
* Columns are characters of the row's text. Neither is clamped here — the managed side owns
* the text and does it there.
*/
int32_t dragSide;
int32_t dragAnchorRow;
int32_t dragAnchorColumn;
int32_t dragFocusRow;
int32_t dragFocusColumn;
} DeviewInput;

/*
Expand All @@ -221,8 +257,15 @@ typedef struct DeviewInput {
* 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.
* 7: DeviewScreen carries the pending count, which is not the length of the queue it also carries:
* that is the visible slice, so a head deriving one from the other said "Pending (16)" beside
* "inline 1 of 30".
* 8: DeviewRow carries the selected run of its text and DeviewInput reports a drag across a pane,
* which between them are text selection. DeviewRow is a widened array element, so this is the
* same kind of bump 6 was. deview_set_clipboard is added beside them, because the selection is
* only worth having if it can be copied and each toolkit owns its own clipboard.
*/
#define DEVIEW_VERSION 7
#define DEVIEW_VERSION 8

/*
* The Swift implementation imports this header for the struct layouts, because Swift does not
Expand Down Expand Up @@ -266,6 +309,12 @@ DEVIEW_API int32_t deview_capture(

DEVIEW_API void deview_set_hidden(int32_t hidden);

/*
* Puts UTF-8 text on the system clipboard. The toolkit's own clipboard rather than the managed
* side shelling out to a copy program, which is a thing every desktop has a different one of.
*/
DEVIEW_API void deview_set_clipboard(const char* text);

DEVIEW_API void deview_focus(void);

DEVIEW_API void deview_shutdown(void);
Expand Down
Loading
Loading