1

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

1

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)
                    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 ()
  (interactive)
  (let ((height (window-height)))

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

    ;; Move window.
    (set-window-start
      (selected-window)
      (min
        (save-excursion ;; new point.
          (forward-line (- (/ height 2)))
          (point))
        (save-excursion ;; max point.
          (goto-char (point-max))
          (beginning-of-line)
          (forward-line (- (- height (+ 1 (* 2 scroll-margin)))))
          (point)))))
  (redisplay))

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

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

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

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)
| improve this answer | |
  • This is not triggered when I use mouse wheel, can it be also bind to it? – alper Jul 27 at 11:57
  • Yes you could bind to mouse wheel, you might want to scroll fewer lines in that case though. – ideasman42 Jul 27 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 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 at 9:28

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.