Skip to content

Commit 0cc45b9

Browse files
Tell the body about a tall footer and the loop about an unseen window, and carry held deletes to the viewer (#930)
* Hold a delete whose file a move wrote in a queue the viewer owns An owning viewer's accept-all took its moves and deletes in queue order, and EnqueueTracked replaced by key only, so a delete raised after a move onto the same verified file followed that move in the batch: the received file was moved into place and then deleted, and neither was left. The tray's tracker had the same shape and these are its rules. A move that arrives withdraws the delete pending on its target. A move that is carried out, on its own or in a batch, marks the delete pending on the file it wrote (QueueEntry.Written), and the entry says why. A bulk accept leaves a delete that is marked, or whose file a move still pending is going to write, and says so after its counts. Accepting the delete on its own still carries it out, and a run raising it again lets go of the hold. The watch over owned files reads the delete's file again once the move has written it, so the entry built from that read keeps the mark. MoveOntoDeleteTests holds each of those. Run before the fix, the accept-all and the group accept left no file at the target, a move did not withdraw the delete, and a second accept-all deleted what the first had accepted. * Say why a delete is held on a full listing and in the wire's accept-all answer A delete held because a move wrote its file, or is still to, was marked in the tray's menu and debug view only. A viewer showing the tray's queue showed it like any other, and the answer to its accept-all counted it as kept without saying why. A full listing now carries the reason on a `held` line beside the delete's own (ViewerResponseDelete.Held). A line of its own, since `delete` is read by its field count: a reader that predates it skips the line, and an owner that predates it sends none, which reads as nothing held. Both owners say it, the tray from Tracker.HeldReason and an owning viewer from ViewerSession.HeldReason. An attached viewer puts it on the entry as its status, so the row carries the mark a failure does and its tip says why. The tray's answer to a wire accept-all ends with the sentence its own balloon uses when a delete of the batch was kept for a move. TrackedDelete.Written is the one thing a listing carries that changes on a tracked object that stays the same object, so its two changes are counted into ITrackedFiles.Version with the restores. Without that the tray answered "unchanged" to a viewer still showing a hold a re-run had let go of, which AnAttachedViewerSaysWhyTheTrayHoldsADelete failed on with the count taken out. Checked by round trips in ViewerProtocolTests, including a listing with no hold line, one whose hold precedes its delete and one whose hold names no delete; by AttachedViewerTests for the entry, which stays the same entry from pump to pump; and by TrayViewerSyncTest over a socket for both arrangements. * Leave a held delete out of a group accept sent from an attached viewer "Accept all in" a group from a viewer showing someone else's queue sends one Accept per key, and an owner carries out an accept by key as asked, held or not, since that is how a reviewer says a file is redundant. So a group over a move and the delete pending on its target moved the received file into place and then deleted it, which the owner's own accept-all does not do. The listing now says which deletes the owner holds, so the group accept does not send those keys and says so in its message. Chosen over a wire verb for a group accept because the owner already tells the viewer all it needs, the rule about deletes waiting on the group's snapshots already lives on this side, and an owner old enough not to report holds would not know a new verb either: it reports none and is sent every key, as before. The holds are read from the latest listing when the deletes' turn comes. A delete waiting on one of the group's own moves is held before that move is accepted and after it, so a listing from either side of the accept says so. Checked over a socket for both arrangements in TrayViewerSyncTest, and in AttachedViewerTests against an owner that says a delete is held and one that does not. With the skip taken out the file the move wrote was gone after the group accept, from a tray and from an owning viewer. * Take the rows a tall footer covers off the rows a native head reports The managed side slices the body for the rows a head says its window has, less eight lines for everything that is not a row. Both native heads reported the window's height in rows whatever their footer came to. Eight lines are more than a title, the headers and one row of buttons take, so a footer of two or three rows fitted in what was over. A taller one did not: a paged document's buttons are four rows in a Linux window under about 450 pixels wide, and the last rows the managed side sliced were laid out under them. On macOS, past three rows of buttons, or two and a status line, the last one or two were not drawn. DeviewInput.rows is now no more than the body has room for with those eight lines added back, counted from the last frame laid out for the window: by MeasureGrid from where the table put its first row and where the body ends, and by Renderer.grid(for:) from the capacity the last window draw worked out, when that was at the size asked about. Where the footer fits, which is every window there was a baseline or a habit for, the answer is the one it always was. No struct moved, but what a field means did, so DEVIEW_VERSION is 12: the committed binaries are refused until build-native has rebuilt them. No managed change beyond the version. The WinForms head already reports the rows its canvas can draw. Its capture, which builds a screen before its footer is laid out, is as it was. Checked in an ubuntu:24.04 container set up as the unix job is. The 22 scenes and window tests PixelTests had reproduce byte for byte against the new shim. ATallFooterTakesRowsFromTheBody shows the window, asks it for its rows with a footer of one row and of five, and takes a picture of the second screen built for what was reported: 41 rows and 39, the last line sliced the one above the buttons. Against a shim with the limit taken out it fails, told 41 for both. The shim builds with no warnings. The Swift is unverified: it cannot be built from Windows, and CI's macOS job only captures, which never measures a window. * Tell the loop when nobody can see the window, and leave a minimised Linux window alone The loop keeps three things going beside a window for whoever is reading it: OwnerLink lists the queue's owner, TrackedWatch looks at the pending files again, and DocumentWatch reads and draws the documents on screen. Each slows or stops for a hidden window, and the loop only knew a window was hidden when it had hidden it itself. A window in the taskbar or the Dock, or wholly behind another on macOS, was listed, watched and drawn for as one on screen. The WinForms and macOS heads already slowed their own frame wait for those, which is as far as a head could go. A head now says so with every poll: DeviewInput.unseen, and ViewerInput.Unseen from it. The loop slows the three watchers while its own hide or the head's word says nobody is looking, the two kept apart since they come and go apart. It is a state, as the grid is, so it makes no frame one with input in it. - Linux: hidden or minimised. A window behind another is not known to be, since X11 says nothing of it that GLFW passes on. A minimised window is also no longer built and drawn when its screen changes, as a hidden one is not: coming back from the taskbar is an arrival that marks it stale, as being shown is. - macOS: what its pump already asks before waiting longer, which is ordered out, miniaturised, or with nothing of it showing. - Windows: minimised. A window behind another is not asked about. The field goes at the end of DeviewInput, in the bump to 12 the commit before made. Left: the macOS pump still cannot be interrupted, so a window nobody can see comes forward up to a tenth of a second late for a patch arriving over the socket. Checked by two tests of ViewerProgram.Loop over a window that says, poll by poll, whether it can be seen, the first of which fails with the loop's answer taken out, and by a WinForms test that minimises a form on a desktop of its own, which fails against a head that says nothing. In the container, a real window under Xvfb and openbox, minimised with xdotool and brought back with wmctrl, presented sixty changed screens each time and counted by a GL query: before this it drew 60 of 60 minimised and now draws none, says unseen while minimised and while hidden and not otherwise, and draws again once restored. That check is not in the suite, since CI has no window manager. PixelTests pass against the new shim, every baseline as it was. The Swift is one line and unverified. * Keep a dragged picture from pulling the other pane's into its own range An enlarged picture's centre is one point for both panes, and each pane moves it in as far as its own picture needs to keep its space full. A drag started from the centre the dragged pane had drawn about and was kept inside that pane's range. With two pictures of different shapes the ranges differ, so the first move of a drag in the pane that can go less far brought the other pane's picture in from wherever beyond that range it had been taken, on an axis both can move on. A drag now moves the centre the frame asked for, and keeps it inside what the pane showing less of its picture can show, which is the wider of the two ranges since both are about the middle. The same arithmetic in all three heads: UpdatePan in the shim, PictureSpace.dragged on macOS and PicturePlacement.Dragged in the WinForms head, each handed how the other pane's picture was placed when the button went down. A drag in the pane that can go less far, from a centre beyond its own range, moves the other pane's picture alone until the centre is back inside it. The WinForms head needed more than the native ones: it had not been given their earlier fix either, so on an axis its picture could not move on at all it reported the middle, and a sideways drag took the other pane back to its middle row. An axis the dragged picture cannot move on is now left as the model had it there too. No struct changes: what DeviewInput.panX and panY are kept inside does, which the header says under the bump to 12 this round already made. Checked on Windows by three tests of PicturePlacement and one through the canvas with a wide picture beside a tall one, all four of which fail against the old arithmetic. In the container, a real window under Xvfb with the same pair, the centre asked for as far down as there is, and the left picture dragged sideways by xdotool: the shim before this reported 0.7099 down, the left pane's own limit, and this one 0.7824, the right pane's, where it already was. Dragged to a corner from either pane both now stop at 0.2066 across and 0.7824 down, where the right pane stopped at 0.276 across and the left at 0.7099 down. From the middle every drag reports what it did. That check is not in the suite, since nothing there can move a pointer. PixelTests pass against the new shim with every baseline as it was. The Swift is unverified. * Close what this round fixed in todo.md, and describe what changed The held delete items and the three that needed the ABI to move go, replaced by what each fix left: a hold let go by a re-raise, a listing taken while a move is in flight, a covered window that only macOS knows of, and what a person on a Mac has to confirm. claude.md gains ABI 12, the drag's arithmetic, and the hold on the wire and in an owning viewer. * Rebuild native renderer binaries (#931) Co-authored-by: SimonCropp <122666+SimonCropp@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: SimonCropp <122666+SimonCropp@users.noreply.github.com>
1 parent fa5a11e commit 0cc45b9

41 files changed

Lines changed: 1627 additions & 97 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎claude.md‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,11 @@ the comment there about not caching "nothing staged" asks for.
273273
tenth of a second for a window that is ordered out, miniaturised or covered, which the managed
274274
side is not told. A wheel is told from a trackpad by `hasPreciseScrollingDeltas`, and a
275275
control-click is taken in `mouseDown`, since the view has no `NSMenu` for AppKit to ask for.
276-
A drag reports the frame's own centre on an axis its picture cannot move on, as the Linux head
277-
does: only when the space is what cut the picture short can it move. A repeat of a held key is
276+
A drag moves the centre the frame asked for and clamps it to the wider of the two panes'
277+
ranges (`PictureSpace.dragged(by:other:)`), with the same arithmetic in the shim and in
278+
WinForms (`PicturePlacement.Dragged`): clamped to the dragged pane's own range, a drag pulled
279+
the other pane's picture into it. `grid(for:)` reports no more rows than the last window draw
280+
left the body under a tall footer. A repeat of a held key is
278281
dropped for accept, accept all and discard. A picture both panes name keeps a scaled copy for
279282
each pane (`Picture.spare`), since the panes can be a point apart in width and one copy was
280283
made again for each in turn without end.
@@ -320,7 +323,12 @@ the comment there about not caching "nothing staged" asks for.
320323
three quarters of a picture's size up, and mipmaps made only for a picture drawn smaller than
321324
that, on the decoder thread when first needed. A picture past `GL_MAX_TEXTURE_SIZE` is brought
322325
down to one that fits as it is read. `PixelTests` has two tests that show the window and count
323-
draws through a GL query, `AWindowLeftAloneIsNotDrawn` and `AHiddenWindowIsNotDrawn`.
326+
draws through a GL query, `AWindowLeftAloneIsNotDrawn` and `AHiddenWindowIsNotDrawn`, and a
327+
third that shows it to ask for its rows, `ATallFooterTakesRowsFromTheBody`. A minimised window
328+
is left alone as a hidden one is. `MeasureGrid` reports no more rows than the body had room for
329+
in the last window frame plus the model's eight chrome lines (`chromeRows`, kept in step with
330+
`ScreenBuilder.Chrome` here and in `Renderer.swift`), so a footer taller than its allowance
331+
takes rows from the body and not from under it.
324332
- Group headers fold. `SessionState.Collapsed` holds `QueueItem.GroupKey`s and `QueueProjection`
325333
skips their members, so the marker rides in the label and no head or ABI field knows about it.
326334
Whether an entry is hidden is always read back out of `VisibleEntries`, never recomputed — the
@@ -595,7 +603,11 @@ the comment there about not caching "nothing staged" asks for.
595603
refuses a library whose version is not an exact match, so a bump and a binaries rebuild land
596604
together: change `native/`, run `build-native`, merge the PR it opens. Between the two, the
597605
`native` CI job — the one that loads the committed binaries — reports the mismatch, which is the
598-
check working.
606+
check working. Version 12 is two things: `DeviewInput.rows` capped by the body's room, and
607+
`DeviewInput.unseen`, a state like the grid and not an event, which is a head saying nobody can
608+
see its window (hidden or minimised on Linux; ordered out, miniaturised or occluded on macOS;
609+
minimised on Windows, through `ViewerInput.Unseen`). `ViewerProgram.Loop` keeps its own `hidden`
610+
and the head's `unseen` apart and slows `OwnerLink`, `TrackedWatch` and `DocumentWatch` on either.
599611
- Built binaries are **committed** to `src/DiffEngineViewer.{Linux,Mac}/runtimes/{rid}/native/`, so a plain
600612
`dotnet build` produces a shippable package and contributors never need CMake. Regenerate them
601613
with the `build-native` GitHub workflow, which opens a PR.
@@ -703,12 +715,21 @@ the comment there about not caching "nothing staged" asks for.
703715
- A move that writes a file marks the delete pending on it (`TrackedDelete.Written`), however
704716
the move was accepted, and no accept-all carries a marked delete out until a run raises it
705717
again; accepting it on its own still does. `Tracker.HeldReason` is what the menu and the debug
706-
view show. "Discard (n)" runs wholly on a worker. A scan that fails three times running is one
718+
view show, and it rides a full listing as a `held: key|reason` line of its own
719+
(`ViewerResponseDelete.Held`), since the `delete` line is parsed by field count and an older
720+
reader skips a line it does not know. An attached viewer shows it as the entry's status and
721+
leaves held deletes out of "Accept all in" a group (`OwnerLink.AcceptGroup`), and the wire's
722+
accept-all answer ends with `Tracker.DeletesKept`. An owning viewer goes by the same rules for
723+
its own files: a move arriving withdraws the delete on its target (`EnqueueTracked`), a move
724+
carried out marks it (`QueueEntry.Written`), a batch keeps a marked or awaited delete
725+
(`ViewerSession.HeldReason`), a single accept still deletes, and `TrackedEntry.DeleteAgain`
726+
carries the mark across the watch's re-read. "Discard (n)" runs wholly on a worker. A scan that fails three times running is one
707727
balloon for the run. Every tray keeps the `SessionEndWindow` now, and `Program.SessionEnding`
708728
stages the queue where it is held here and then removes the version marker.
709729
- `ITrackedFiles.Version` is what `ListingTag` asks about the tracked files: the identity of the
710730
tracked objects, so `TrackedMove` and `TrackedDelete` must stay immutable in everything a
711-
listing carries. The scan removes a move by key and value, so one staged again since it looked
731+
listing carries. `TrackedDelete.Written` is the exception, and its changes are counted into the
732+
version as the restores are. The scan removes a move by key and value, so one staged again since it looked
712733
is left. `Tracker.Clear` hands the queue's discard to a worker, as a single discard does. A hot
713734
key's action is caught in `KeyRegister`, because a throw from a message filter comes out of
714735
`Application.Run()`.

‎native/include/deview.h‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ typedef struct DeviewPane {
140140
*
141141
* The managed side does not know how many pixels a pane has, so it may ask for a centre that
142142
* would leave part of the space empty. A renderer moves the centre in as far as it takes to
143-
* keep the space full, and that clamped centre is the one a drag starts from.
143+
* keep the space full. That is for drawing only. A drag starts from the centre asked for,
144+
* moved in only as far as the pane that can go further would move it: see DeviewInput.panX.
144145
*/
145146
float imageZoom;
146147
float imageCenterX;
@@ -294,6 +295,15 @@ typedef struct DeviewInput {
294295
* The window size in character cells, not pixels. Measured here from the font that was
295296
* actually loaded, because this side is the only one that knows it. Reporting pixels and
296297
* having the managed side divide by a constant is what left the viewer with no DPI handling.
298+
*
299+
* rows is the window's height in rows, and no more than the body has room for with the eight
300+
* lines the managed side keeps for everything that is not a row (ScreenBuilder.Chrome) added
301+
* back. Those eight lines are more than a title, the headers and a footer of one row take, and
302+
* a footer that fits in what is over costs nothing. One that does not - buttons that wrap onto
303+
* a third and fourth row in a narrow window - is laid out over the bottom of the body, so the
304+
* rows it covers are taken off here, and the managed side slices a body that ends above it.
305+
* Counted from the last frame laid out for the window, the only place a footer's height is
306+
* known.
297307
*/
298308
int32_t columns;
299309
int32_t rows;
@@ -327,8 +337,11 @@ typedef struct DeviewInput {
327337
int32_t zoomDelta;
328338

329339
/*
330-
* Where a drag has left an enlarged picture: the point now at the middle of what shows, as
331-
* fractions of the picture's width and height, already kept inside what the space can show.
340+
* Where a drag has left an enlarged picture: the centre both panes are to draw about, as
341+
* fractions of the picture's width and height. It is the centre the frame asked for that the
342+
* drag moves, and it is kept inside what the pane showing less of its picture can show: the
343+
* two pictures need not be the same shape, and kept to the dragged pane's own range a drag
344+
* brought the other pane's picture in from wherever beyond it that one had been taken.
332345
* On an axis the dragged picture cannot move on, because all of it shows, it is the centre
333346
* the frame was drawn with, unchanged: the other pane's picture may be able to move there.
334347
* panX is -1 on the frames with no such drag, which is almost all of them.
@@ -346,6 +359,19 @@ typedef struct DeviewInput {
346359
* says which pane it is for in DeviewScreen.menuPane.
347360
*/
348361
int32_t rightClickedPane;
362+
363+
/*
364+
* 1 while nobody can see the window, and 0 otherwise: minimised, hidden by deview_set_hidden,
365+
* or with nothing of it showing where the window system can say so, which AppKit can and X11
366+
* cannot. A state, as the grid is, and not an event: reported by every poll for as long as
367+
* it is so.
368+
*
369+
* The managed side keeps things going beside the window for whoever is reading it - it reads
370+
* the files behind the rows again, lists the queue's owner, draws a document's pages - and
371+
* knew to slow those only for a window it had hidden itself. A window in the taskbar or the
372+
* Dock, or behind another, was kept up as one being read.
373+
*/
374+
int32_t unseen;
349375
} DeviewInput;
350376

351377
/*
@@ -402,8 +428,14 @@ typedef struct DeviewPlacement {
402428
* widened array element again, in the same bump because they shipped together.
403429
* And DeviewInput reports a right-click over a pane, which DeviewScreen answers with a menu
404430
* that names the pane rather than a queue row.
431+
* 12: DeviewInput.rows is fewer than the window's height in rows where the footer is taller than
432+
* the managed side allows for, by the rows of the body that footer covers. No struct moved:
433+
* what a field means did, and a library from before reports rows that a tall footer hides.
434+
* DeviewInput also reports a window nobody can see, at its end, in the same bump because
435+
* they shipped together. And panX and panY are kept inside what either pane can show, where
436+
* they were kept inside what the dragged one can.
405437
*/
406-
#define DEVIEW_VERSION 11
438+
#define DEVIEW_VERSION 12
407439

408440
/*
409441
* The Swift implementation imports this header for the struct layouts, because Swift does not

0 commit comments

Comments
 (0)