Skip to content

Fix: Scroll up onto a Ghostel image preview from the rows below it - #527

Merged
tninja merged 2 commits into
tninja:mainfrom
Silex:fix/ghostel-scroll-up-past-preview
Oct 3, 2026
Merged

tninja merged 2 commits into
tninja:mainfrom
Silex:fix/ghostel-scroll-up-past-preview

Conversation

@Silex

@Silex Silex commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

In Ghostel sessions, a window scrolled up to the row below an image preview couldn't go further, neither by line scroll nor by wheel.

Emacs can't always measure backwards across the preview's overlay, so pixel-scroll-precision-scroll-up signals beginning-of-buffer and the window stays where it is. Now when an upward scroll would cross a preview's lower edge, the window is put on the preview's link row with the vscroll hiding all but the scrolled pixels. That's the state pixel-scroll-precision and ultra-scroll leave a window in themselves, so they carry on from there.

Two leftovers: a line scroll landing exactly on the row below a preview moves 36 px in that step, and leaving a preview at its top moves up to 6 px more than asked. Also the wheel events in the tests were synthetic, not from a real device.

A window scrolled up to the row below a local image preview could not
go further.  Emacs cannot always measure backwards across a preview:
from that row `pixel-scroll-precision-scroll-up' signals
`beginning-of-buffer', and both `pixel-scroll-precision' and
`ultra-scroll' leave the window where it is.  Line scrolling logged the
error on every key press, the wheel did nothing, and everything above
the preview stayed out of reach.  With a preview wider than the window
the measurement works, but the row alignment then reset the vscroll and
the whole image passed in one step.

Step onto the preview by hand.  When an upward scroll would cross a
preview's lower edge, start the window on the preview's link row and
hide all but the scrolled pixels with the vscroll.  That is how
`pixel-scroll-precision' and `ultra-scroll' themselves leave a window
inside a tall line, so both carry on from there.  Line scrolling, the
plain wheel and the precision wheel all take this path, and the row
alignment leaves a link row alone while it hides part of its image.

A window that starts right below a preview draws the preview's trailing
newline as an empty row; count it as the preview's last line so the
text does not jump by a row on the way in.

Scrolling up pushes the last rows out of the window.  Move point onto a
row still shown whole, or redisplay scrolls back down to show point and
undoes the scroll.  Record the scroll once the preview is in view too,
so that Ghostel does not anchor the window back to live output.

@tninja tninja left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look.

Comment thread ai-code-ghostel-image-preview.el Outdated
`pixel-scroll-precision' can fail to cross a preview's lower edge
upwards, and then leave the window on the row below the preview."
(let* ((delta-pair (and (consp event) (nth 4 event)))
(delta (and (consp delta-pair) (cdr delta-pair))))

@tninja tninja Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix! On emacs-mac, trackpad pixel deltas are in the event plist at (nth 3 event) as :scrolling-delta-y, rather than (nth 4 event). Could you handle that format too?

Suggested change
(delta (and (consp delta-pair) (cdr delta-pair))))
(let* ((delta-pair (and (consp event) (nth 4 event)))
(delta (or (and (consp delta-pair) (cdr delta-pair))
(and (listp (nth 3 event))
(plist-get (nth 3 event) :scrolling-delta-y)))))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4a220ee. I did not take the suggestion as is: (nth 3 event) is read outside the consp guard and signals for a non-list event, which two existing tests pass. Both reads are guarded now.

Two things this does not cover. A wheeled mouse on emacs-mac reports :delta-y in fractional lines and no :scrolling-delta-y, so it still goes to ultra-scroll-mac. And --clamp-up-event reads only (nth 4 event) too, from before this PR, so it does nothing on emacs-mac.

I have no emacs-mac here. The event shape comes from ultra-scroll-mac's source, so could you check it on your trackpad?

emacs-mac wheel events carry no pixel delta pair.  They report trackpad
pixels as `:scrolling-delta-y' in a property list that takes the place
of the line count.  The upward step onto a preview read only the pair,
so on emacs-mac it never ran and the event went to `ultra-scroll-mac',
which cannot cross the preview's lower edge.

Read the property list when the pair is missing.  Both reads stay
behind the list check, as the command is also called with events that
are not lists.
@Silex

Silex commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Two emacs-mac gaps are left after 4a220ee. I cannot test either one here, so I would like to know what you want for each.

  1. Wheeled mouse: it reports :delta-y in fractional lines and no :scrolling-delta-y. With mac-mouse-wheel-smooth-scroll on, ultra-scroll-mac turns that into pixels with ultra-scroll-mac-multiplier and scrolls by itself, so the upward step onto a preview does not run. I expect the window still stops on the row below the preview in that case. A fix would do the same conversion before the engine gets the event.
  2. --clamp-up-event: since Fix: Scroll line by line over Ghostel image previews #525 it reads and rewrites only (nth 4 event). On emacs-mac it returns the event unchanged, so an upward scroll larger than the hidden part of a preview is not clamped to the preview's top. A fix has to write the clamped value back into the plist.

For each: (a) fix it in this PR, (b) a separate PR after this one, or (c) leave it. Both are read from the ultra-scroll-mac source, not observed, so if you can try them on emacs-mac first, that would tell us whether they are worth fixing.

@tninja

tninja commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Thanks a lot for the quick follow-up in 4a220ee and for the careful write-up of the two remaining emacs-mac gaps. Reading the ultra-scroll-mac source to trace them was really helpful.

On my side, I ran the full test_ai-code-ghostel-image-preview.el suite on this branch with Emacs 31.1 (NS build), and all 36 tests pass. I don't have emacs-mac set up here (it is , so I couldn't check the trackpad and wheeled-mouse cases on a real device.

For both gaps, I'd go with (b/c): leave them out of this PR and come back to them later. This PR already fixes the main issue, so I'm happy to keep its scope as it is.

Thanks again for the thorough work and testing on this one!

@tninja
tninja merged commit 2402d4c into tninja:main Oct 3, 2026
3 checks passed
@Silex
Silex deleted the fix/ghostel-scroll-up-past-preview branch October 4, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants