Perfect Smooth Scroll for Emacs on Mac OS (Mitsuharu Yamamoto, or Railwaycat's port)

The most extensive Emacs port utilizing most available features unique to Mac OS is Mitsuharu's Emacs port. Emacs 29 introduces pixel-level smooth scroll (as opposed to line-level), a feature already implemented in Mitsuharu's port. Hoever, this new implementation is not used in Mitsuharu's Emacs 29 port.

It is unfortunate, since a certain very irritating bug is resolved in Emacs 29's implementation, namely scrolling over image fragments can be extremely jittery (jumpy). Sometimes you couldn't even scroll past certain images no matter how hard you tries.

The solution is given below, where I replace Mitsuharu's implementation of smooth scroll with the native implementation, while correctly interpreting the Mac specific scroll events.

Simply add this to your config to resolve all scroll-related issues.

(setq scroll-conservatively 101)

(global-set-key (kbd "<wheel-down>") #'pixel-scroll-precision)
(global-set-key (kbd "<wheel-up>") #'pixel-scroll-precision)

(pixel-scroll-precision-mode +1)

(with-eval-after-load 'pixel-scroll
  (defun pixel-scroll-precision (event)
    "Scroll the display vertically by pixels according to EVENT.
Move the display up or down by the pixel deltas in EVENT to
scroll the display according to the user's turning the mouse
    (interactive "e")
    (let ((window (mwheel-event-window event))
          (current-window (selected-window)))
      (when (framep window)
        (setq window (frame-selected-window window)))
      (if (and (nth 3 event))
          (let ((delta
                 (* -1
                    (let ((dy (plist-get (nth 3 event) :scrolling-delta-y))
                      (if pending-events
                          (setq unread-command-events (nconc (nreverse pending-events)
                      (round (- dy))))))
            (unless (zerop delta)
              (if (> (abs delta) (window-text-height window t))
                  (mwheel-scroll event nil)
                (with-selected-window window
                  (if (or (and pixel-scroll-precision-interpolate-mice
                               (eq (device-class last-event-frame
                          (and pixel-scroll-precision-large-scroll-height
                               (> (abs delta)
                               (let* ((kin-state (pixel-scroll-kinetic-state))
                                      (ring (aref kin-state 0))
                                      (time (aref kin-state 1)))
                                 (or (null time)
                                     (> (- (float-time) time) 1.0)
                                     (and (consp ring)
                                          (ring-empty-p ring))))))
                        (let ((kin-state (pixel-scroll-kinetic-state)))
                          (aset kin-state 0 (make-ring 30))
                          (aset kin-state 1 nil))
                        (pixel-scroll-precision-interpolate delta current-window))
                    (condition-case nil
                          (if (< delta 0)
	                      (pixel-scroll-precision-scroll-down (- delta))
                            (pixel-scroll-precision-scroll-up delta))
                          (pixel-scroll-accumulate-velocity delta))
                      ;; Do not ding at buffer limits.  Show a message instead.
                       (message (error-message-string '(beginning-of-buffer))))
                       (message (error-message-string '(end-of-buffer))))))))))
        (mwheel-scroll event nil)))))

An alternative is using the new package ultra-scroll-mac, which aims to solve this very issue in a more performant way.

Author: Max Marshall

Created: 2024-02-14 Wed 16:16