;;; center-content.el --- Center buffer content -*- lexical-binding: t; -*- ;;; Commentary: ;; This package provides a minor mode `center-content-mode' that centers the ;; content of the current buffer both horizontally and vertically. ;;; Code: (require 'face-remap) (defgroup center-content () "Center buffer content." :group 'convenience :prefix "center-content-") (defcustom center-content-horizontal t "Enable horizontal centering." :type 'boolean :group 'center-content) (defcustom center-content-vertical t "Enable vertical centering." :type 'boolean :group 'center-content) (defcustom center-content-vertical-by-overlay nil "Enable vertical by using overlays. Using overlays for vertical centering may give some undesirable effects on the first line. By default, vertical centering is achieved by using setting `header-line-format'." :type 'boolean :group 'center-content) (defcustom center-content-hide-mode-line nil "Hide mode-line when centering." :type 'boolean :group 'center-content) (defvar-local center-content--horizontal-overlay nil "Overlay used to add left margin for horizontal centering.") (defvar-local center-content--vertical-overlay nil "Overlay used to add top margin for vertical centering.") (defvar-local center-content--original-header-line-format header-line-format "Stores the original `header-line-format` to restore upon disabling.") (defvar-local center-content--original-mode-line-format mode-line-format "Stores the original `mode-line-format` to restore upon disabling.") (defvar-local center-content--header-line-face-remap-cookie nil "Cookie for the face remapping of `header-line`.") (defun center-content--content-pixel-size () "Return the size of the content of the visible text in pixels." (window-text-pixel-size (selected-window) (window-start) (window-end nil t))) (defun center-content--calculate-left-margin (content-width) "Calculate the left margin relative to CONTENT-WIDTH." (let ((window-width (window-pixel-width)) (column-width (string-pixel-width " "))) (/ (max 0 (- window-width content-width)) (* 2 column-width)))) (defun center-content--calculate-top-margin (content-height) "Calculate the top margin relative to CONTENT-HEIGHT." (let ((window-height (window-pixel-height)) (line-height (default-line-height))) (/ (max 0 (- window-height content-height)) (* 2 line-height)))) (defun center-content--horizontal (content-width) "Add overlay for horizontal centering relative to CONTENT-WIDTH." (let* ((left-margin (center-content--calculate-left-margin content-width)) (overlay (make-overlay (point-min) (point-max))) (space (propertize " " 'display `(space :width ,left-margin)))) (overlay-put overlay 'line-prefix space) (setq center-content--horizontal-overlay overlay))) (defun center-content--vertical (content-height) "Add overlay for vertical centering relative to CONTENT-HEIGHT." (let* ((top-margin (center-content--calculate-top-margin content-height))) ;; Save and remap the header-line face to match the default face (setq center-content--header-line-face-remap-cookie (face-remap-add-relative 'header-line 'default)) (setq header-line-format (propertize " " 'display `(height ,top-margin))))) (defun center-content--vertical-by-overlay (content-height) "Add overlay for vertical centering relative to CONTENT-HEIGHT." (let* ((top-margin (center-content--calculate-top-margin content-height)) (overlay (make-overlay (window-start) (window-start) nil t t)) (space (propertize " " 'display `(space :width 0 :height ,top-margin)))) (overlay-put overlay 'intangible t) (overlay-put overlay 'before-string space) (setq center-content--vertical-overlay overlay))) (defun center-content--enable () "Enable horizontal and vertical centering of content." (setq center-content--original-header-line-format header-line-format) (setq center-content--original-mode-line-format mode-line-format) (let* ((content-size (center-content--content-pixel-size))) (when center-content-horizontal (center-content--horizontal (car content-size))) (when center-content-vertical (if center-content-vertical-by-overlay (center-content--vertical-by-overlay (cdr content-size)) (center-content--vertical (cdr content-size)))) (when center-content-hide-mode-line (setq mode-line-format nil)))) (defun center-content--disable () "Disable centering of content." ;; Remove horizontal centering overlay (when (overlayp center-content--horizontal-overlay) (delete-overlay center-content--horizontal-overlay) (setq center-content--horizontal-overlay nil)) ;; Remove vertical centering overlay (when (overlayp center-content--vertical-overlay) (delete-overlay center-content--vertical-overlay) (setq center-content--vertical-overlay nil)) ;; Restore the header-line face remapping (when center-content--header-line-face-remap-cookie (face-remap-remove-relative center-content--header-line-face-remap-cookie) (setq center-content--header-line-face-remap-cookie nil)) ;; Restore the header-line-format (setq header-line-format center-content--original-header-line-format) ;; Restore the original mode-line-format (setq mode-line-format center-content--original-mode-line-format)) (defun center-content-update () "Updates the centering if `center-content-mode' is enabled." (interactive) (when (bound-and-true-p center-content-mode) (center-content--disable) (center-content--enable))) ;;;###autoload (define-minor-mode center-content-mode "Toggle horizontal and vertical centering of buffer content. When enabled, the content of the buffer is centered both horizontally and vertically within the window." :init-value nil :global nil :lighter " Center" (if center-content-mode (center-content--enable) (center-content--disable))) (provide 'center-content) ;;; center-content.el ends here