3

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 Answer 1

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))

(defmacro my-scroll-and-clamp--with-evil-visual-mode-hack (&rest body)
  "Execute BODY with the point not restricted to line limits.

This is needed so the point is not forced to line bounds
even when in evil visual line mode."
  `(let ((mark-found nil))
     (when (and (fboundp 'evil-visual-state-p)
                (funcall 'evil-visual-state-p)
                (fboundp 'evil-visual-type)
                (eq (funcall 'evil-visual-type) 'line)
                (boundp 'evil-visual-point))
       (let ((mark (symbol-value 'evil-visual-point)))
         (when (markerp mark)
           (setq mark-found mark))))
     (unwind-protect
         (progn
           (when mark-found
             (goto-char (marker-position mark-found)))
           ,@body)
       (when mark-found
         (set-marker mark-found (point))))))


;;;###autoload
(defun my-scroll-and-clamp-up-command ()
  (interactive)
  (my-scroll-and-clamp--with-evil-visual-mode-hack
   (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))

;;;###autoload
(defun my-scroll-and-clamp-down-command ()
  (interactive)
  (my-scroll-and-clamp--with-evil-visual-mode-hack
   (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)
8
  • This is not triggered when I use mouse wheel, can it be also bind to it?
    – alper
    Commented Jul 27, 2020 at 11:57
  • Yes you could bind to mouse wheel, you might want to scroll fewer lines in that case though.
    – ideasman42
    Commented Jul 27, 2020 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
    Commented Jul 27, 2020 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
    Commented Jul 28, 2020 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
    Commented Aug 17, 2021 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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