Trevor Pogue
Trevor Pogue

Reputation: 176

emacs - scroll past top of buffer?

Emacs (and all other text editors) by default show blank space below the bottom lines of a buffer. I want emacs to be able to also scroll above/show blank space like this above the top lines in a buffer as well so that the top lines can be viewed in the center of the screen for small files.

Upvotes: 5

Views: 641

Answers (2)

Trevor Pogue
Trevor Pogue

Reputation: 176

With guidance from Thomas's answer here I have created a minor mode for this which is now available on MELPA:

TopSpace - Recenter line 1 with scrollable upper margin/padding

topspace

Upvotes: 3

Thomas
Thomas

Reputation: 17412

I've started a small minor-mode to accomplish this. However, as first versions go, it's probably buggy, doesn't handle all edge cases (such as e.g., narrowing), and isn't particularly efficient.

Thus, I'd be happy to accept improvements: feel free to directly edit this answer if you can make this code better or to extend it.

(defvar vertical-center-num-buffers 0
  "The number of buffers in which `vertical-center-mode' is activated.")

(define-minor-mode vertical-center-mode
  "This minor mode displays the contents of a buffer vertically
centered with respect to the window height. This, of course, only
makes sense for buffers whose content is shorter than the window
height."
  nil
  " vc"
  nil

  ;; is the mode being turned on or off?
  (if vertical-center-mode
  ;; on
  (progn
    ;; keep track of the number of lines in the buffer
    (setq-local vertical-center-num-lines (count-lines (point-min) (point-max)))
    ;; use an overlay to display empty lines at the beginning of the buffer
    (setq-local vertical-center-overlay (make-overlay (point-min) (point-max)))
    ;; initial call to the function that centers the buffer contents
    (vertical-center--lines-changed 0)

    ;; react to changes to the buffer or the window
    (add-hook 'kill-buffer-hook 'vertical-center--kill-buffer)
    (add-hook 'window-size-change-functions 'vertical-center--window-size-changed)
    (when (= vertical-center-num-buffers 0)
      (add-hook 'before-change-functions 'vertical-center--before-change)
      (add-hook 'after-change-functions 'vertical-center--after-change))

    ;; this is just to play nice and remove the above hook
    ;; functions when they're no longer needed. Let's keep our
    ;; fingers crossed that we'll always stay in sync.
    (setq vertical-center-num-buffers (1+ vertical-center-num-buffers)))

;; off
;; delete/unset data structures when the mode is turned off
(delete-overlay vertical-center-overlay)
(makunbound 'vertical-center-num-lines)
(makunbound 'vertical-center-overlay)
(setq vertical-center-num-buffers (1- vertical-center-num-buffers))

;; remove hook functions when they're no longer needed
(when (= vertical-center-num-buffers 0)
  (remove-hook 'kill-buffer-hook 'vertical-center--kill-buffer)
  (remove-hook 'window-size-change-functions 'vertical-center--window-size-changed)
  (remove-hook 'before-change-functions 'vertical-center--before-change)
  (remove-hook 'after-change-functions 'vertical-center--after-change))))

;; handle killing of buffers
(defun vertical-center--kill-buffer ()
  (when vertical-center-mode
(setq vertical-center-num-buffers (1- vertical-center-num-buffers))))    

;; react to changes in the window height
(defun vertical-center--window-size-changed (arg)
  (vertical-center--lines-changed 0))

;; handle deletions of buffer text
(defun vertical-center--before-change (beginning end)
  (when (boundp 'vertical-center-num-lines)
(let ((num-lines 0))
  (while (< beginning end)
    (when (= (char-after beginning) ?\n)
      (setq num-lines (1- num-lines)))
    (setq beginning (1+ beginning)))
  (when (< num-lines 0)
    (vertical-center--lines-changed num-lines)))))

;; handle insertions into the buffer
(defun vertical-center--after-change (beginning end previous-length)
  (when (boundp 'vertical-center-num-lines)
(let ((num-lines 0))
  (while (< beginning end)
    (when (= (char-after beginning) ?\n)
      (setq num-lines (1+ num-lines)))
    (setq beginning (1+ beginning)))
  (when (> num-lines 0)
    (vertical-center--lines-changed num-lines)))))

;; update the display when either the buffer content or the window
;; height has changed
(defun vertical-center--lines-changed (num-lines)
  (setq vertical-center-num-lines (+ vertical-center-num-lines num-lines))
  (let ((top-margin (/ (- (window-height) vertical-center-num-lines) 2)))
    ;; set the top margin
    (overlay-put vertical-center-overlay 'before-string 
             (when (> top-margin 0)
               (make-string top-margin ?\n)))))

Save the above code in a file named "vertical-center.el" in a directory of your choice, and then add the following lines to your .emacs file:

(setq load-path (append load-path "<directory>"))
(autoload 'vertical-center-mode "vertical-center")

Here, <directory> should be the path to the directory in which you saved the "vertical-center.el" file.

After restarting Emacs, you can now activate or deactivate the mode by typing M-x vertical-center-mode.

Upvotes: 1

Related Questions