Skip to main content

Keep scrollin' scrollin' scrollin'

I never was a big fan of the scrolling behavior in Emacs. Even though I’m used to its abrupt behavior, my eyes lose orientation from time to time.

There are some packages available which try to improve this like on-screen or highlight-context-line and modern Emacs comes with pixel-scroll-mode. Neither of them worked for me in one way or the other, so today I tried to come up with something myself.

The idea is similar to on-screen but you get a better sense of the scrolling direction and the moving parts on your screen. The bottom/top line gets highlighted shortly and after that the new position after the scroll. This way it looks like the line gets pulled up/down. Here you see me scrolling forward two times and back again:

scrollin

If you want to try it yourself, here is the code that you can tweak to your own liking:

(autoload 'View-scroll-half-page-forward "view")
(autoload 'View-scroll-half-page-backward "view")
(global-set-key (kbd "C-v") 'View-scroll-half-page-forward)
(global-set-key (kbd "M-v") 'View-scroll-half-page-backward)

(global-set-key (kbd "C-M-v")
  'my-View-scroll-half-page-forward-other-window)
(global-set-key (kbd "C-M-S-v")
  'my-View-scroll-half-page-backward-other-window)

(defun my-View-scroll-half-page-forward-other-window ()
  (interactive)
  (with-selected-window (next-window)
    (call-interactively 'View-scroll-half-page-forward)))

(defun my-View-scroll-half-page-backward-other-window ()
  (interactive)
  (with-selected-window (next-window)
    (call-interactively 'View-scroll-half-page-backward)))

(setq scroll-preserve-screen-position 'always)

(advice-add #'View-scroll-half-page-forward :around
            #'my-indicate-scroll-forward)

(advice-add #'View-scroll-half-page-backward :around
            #'my-indicate-scroll-backward)

(defun my-indicate-scroll-get-line (pos)
  (save-excursion
    (goto-char pos)
    (string-to-number (format-mode-line "%l"))))

(defun my-indicate-scroll (linep f args)
  (let ((linen (my-indicate-scroll-get-line linep))
        (pulse-delay 0.1))
    (save-excursion
      (goto-line linen)
      (pulse-momentary-highlight-one-line (point) 'highlight))
    (sit-for 0.1)
    (apply f args)))

(defun my-indicate-scroll-forward (f &rest args)
  (my-indicate-scroll (1- (window-end)) f args))

(defun my-indicate-scroll-backward (f &rest args)
  (my-indicate-scroll (window-start) f args))

Update: As /u/alexbranham points out you can extend this idea using transient maps. Using the version below you can keep scrolling in the current direction using v:

(defun my-indicate-scroll (linep f args)
  (let ((linen (my-indicate-scroll-get-line linep))
        (pulse-delay 0.1))
    (set-transient-map
     `(keymap (?v . ,real-this-command)))
    (save-excursion
      (goto-line linen)
      (pulse-momentary-highlight-one-line (point) 'highlight))
    (sit-for 0.1)
    (apply f args)))

I’m quite happy with this and I like the new visual guidance when I’m scrollin' scrollin' scrollin'.