When using page-down (internally scroll-up-command), sometimes the window ends up in a state where end end-of-file is at the top of the screen.

Is there a way to page-down, clamping window position so it doesn't end up scrolling past the document end?

The emacs buffer displays something like this:

;;; some-file.el ends here

Note that I'm not asking for a way to prevent the window moving past the document end entirely (although that could be nice), just a way to change the behavior of scroll-up-command


This can be done using scrolling commands written in elisp.

;; Scrolling that re-centers, keeping the cursor vertically centered.
;; Also clamps window top when scrolling down,
;; so the text doesn't scroll off-screen.

(defun my-scroll-and-clamp--forward-line (n)
  "Wrap `forward-line', supporting Emacs built-in goal column.
Argument N the number of lines, passed to `forward-line'."
  (let ((next-column
         (or goal-column
             (and (memq last-command
                        '(next-line previous-line line-move))
                  (if (consp temporary-goal-column)
                      (car temporary-goal-column)
    (unless next-column
      (setq temporary-goal-column (current-column))
      (setq next-column temporary-goal-column))
    (forward-line n)
    (move-to-column next-column))
  ;; Needed so `temporary-goal-column' is respected in the future.
  (setq this-command 'line-move))

(defun my-scroll-and-clamp-up-command ()
  (let ((height (window-height)))

    ;; Move point.
    (my-scroll-and-clamp--forward-line height)

    ;; Move window.
        (save-excursion ;; new point.
          (forward-line (- (/ height 2)))
        (save-excursion ;; max point.
          (goto-char (point-max))
          (forward-line (- (- height (+ 1 (* 2 scroll-margin)))))

(defun my-scroll-and-clamp-down-command ()
  (let* ((height (window-height)))

    ;; Move point.
    (my-scroll-and-clamp--forward-line (- height))
    (setq this-command 'line-move)

    ;; Move window.
      (save-excursion ;; new point.
        (forward-line (- (/ height 2)))

Example shortcut bindings:

(global-set-key (kbd "<next>") 'my-scroll-and-clamp-up-command)
(global-set-key (kbd "<prior>") 'my-scroll-and-clamp-down-command)
  • This is not triggered when I use mouse wheel, can it be also bind to it?
    – alper
    Jul 27 '20 at 11:57
  • Yes you could bind to mouse wheel, you might want to scroll fewer lines in that case though.
    – ideasman42
    Jul 27 '20 at 12:29
  • I binded as S-down-mouse-1 but seems like it did not take any affect :-( Is it also possible to add N-line margin while scrolling on your code. smooth scrolling have this as (setq smooth-scroll-margin 5) @ideasman42
    – alper
    Jul 27 '20 at 13:42
  • 1
    Not sure what your question is exactly, any issue binding is unlikely related to this function. This is a spesific scroll action, you can bind how you like - of course, for example passing height as a smaller/fixed value. Note that I wrote a package on melpa that does fast modal scrolling using the mouse, see: scroll-on-drag - gitlab.com/ideasman42/emacs-scroll-on-drag
    – ideasman42
    Jul 28 '20 at 9:28
  • I am having following warning: ‘next-line’ is for interactive use only; use ‘forward-line’ instead. should I change next-line with forward-line?
    – alper
    Aug 17 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.