;;; centered-buffer-mode.el --- Minor mode for centering buffers. ;; Copyright (C) 2012-2016 Sylvain Benner & Contributors ;; ;; Author: Eugene "JAremko" Yaremenko ;; Keywords: centering buffer minor-mode ;; Created: 1 July 2016 ;; Version: 1.01 ;; Package-Requires: ((emacs "24.4")) ;; URL: https://github.com/syl20bnr/spacemacs ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'face-remap) (defgroup spacemacs-centered-buffer-mode nil "Minor mode to center buffer in its window." :group 'convenience) (defcustom spacemacs-centered-buffer-mode-min-fringe-width 50 "Minimal fringe width." :type 'integer :group 'spacemacs-centered-buffer-mode) (defcustom spacemacs-centered-buffer-mode-safety-gap-width 20 "Add extra width to the modified buffer to make sure that differed modifications won't cause an overflow." :type 'integer :group 'spacemacs-centered-buffer-mode) (defcustom spacemacs-centered-buffer-mode-fringe-color "black" "Color of the fringes." :type 'color :group 'spacemacs-centered-buffer-mode) (defvar-local spacemacs--centered-buffer-mode-origin-buffer nil) (defvar-local spacemacs--centered-buffer-mode-indirect-buffer nil) (defvar-local spacemacs--centered-buffer-mode-text-pixel-size nil) (defvar spacemacs-centered-buffer-mode-default-fringe-color (face-background 'fringe)) (defvar spacemacs--centered-buffer-mode-indirect-buffers (list)) (define-minor-mode spacemacs-centered-buffer-mode "Minor mode to center buffer in its window." :init-value nil :group 'spacemacs-centered-buffer-mode (if spacemacs-centered-buffer-mode (if (not (window-dedicated-p)) (spacemacs//centered-buffer-mode-enable-branch (called-interactively-p 'any)) (setq spacemacs-centered-buffer-mode nil) (when (called-interactively-p 'any) (message "Can't center in dedicated window!"))) (spacemacs//centered-buffer-mode-disable-branch))) (defun spacemacs//centered-buffer-mode-enable-branch (interact) "Used it `spacemacs-centered-buffer-mode'. Assume to be called interactively when INTERACT has non nil value." ;; Mode will be applied to the indirect buffer. (setq spacemacs-centered-buffer-mode nil) ;; Don't run if the mode is enabled(we are in the indirect buffer). (unless spacemacs--centered-buffer-mode-origin-buffer (let* ((window (selected-window)) (origin-buffer (window-buffer window)) (indirect-buffer (if (buffer-live-p spacemacs--centered-buffer-mode-indirect-buffer) spacemacs--centered-buffer-mode-indirect-buffer (setq spacemacs--centered-buffer-mode-indirect-buffer (make-indirect-buffer origin-buffer (format "%s(centered)" origin-buffer) t))))) (spacemacs//centered-buffer-mode-buffer-fringr-color-toggle origin-buffer t) (setq spacemacs--centered-buffer-mode-indirect-buffer indirect-buffer spacemacs-centered-buffer-mode-default-fringe-color (face-background 'fringe)) (when (derived-mode-p 'org-mode) (setq-local org-startup-folded nil) (outline-show-all)) (switch-to-buffer indirect-buffer nil t) (with-mode-disabled page-break-lines-mode (let* ((fringe-w (spacemacs//centered-buffer-calc-fringe window))) (if (> fringe-w spacemacs-centered-buffer-mode-min-fringe-width) (progn ;; Fix visual glitch. (spacemacs/toggle-line-numbers) (spacemacs/toggle-line-numbers) ;; (setq spacemacs--centered-buffer-mode-indirect-buffers (append (list indirect-buffer) spacemacs--centered-buffer-mode-indirect-buffers) spacemacs--centered-buffer-mode-origin-buffer origin-buffer spacemacs-centered-buffer-mode t indicate-empty-lines nil fringes-outside-margins t left-fringe-width fringe-w right-fringe-width fringe-w ;; looks better with some margin. left-margin-width (if (or (not left-margin-width) (= left-margin-width 0)) 1 left-margin-width) spacemacs--centered-buffer-mode-text-pixel-size (car (window-text-pixel-size window))) (face-remap-add-relative 'fringe :background spacemacs-centered-buffer-mode-fringe-color) (set-window-buffer window indirect-buffer) (advice-add 'previous-buffer :before #'spacemacs//centered-buffer-mode-prev-next-buffer-advice) (advice-add 'next-buffer :before #'spacemacs//centered-buffer-mode-prev-next-buffer-advice) (add-hook 'after-change-functions #'spacemacs//centered-buffer-after-change-function nil t) (add-hook 'buffer-list-update-hook 'spacemacs//centered-buffer-buffer-list-update-fringes) (add-hook 'window-configuration-change-hook 'spacemacs//centered-buffer-buffer-list-update-fringes)) (setq spacemacs--centered-buffer-mode-origin-buffer nil) (set-buffer origin-buffer) (spacemacs//centered-buffer-mode-buffer-fringr-color-toggle origin-buffer nil) (kill-buffer indirect-buffer) (setq spacemacs--centered-buffer-mode-indirect-buffer nil) (when interact (message "Not enough space to center the buffer!")))))))) (defun spacemacs//centered-buffer-mode-prev-next-buffer-advice () "Disables `spacemacs-centered-buffer-mode' when `spacemacs/previous-buffer' or `spacemacs/next-buffer' is called." (when (bound-and-true-p spacemacs-centered-buffer-mode) (spacemacs-centered-buffer-mode -1))) (defun spacemacs//centered-buffer-mode-disable-branch () "Used in `spacemacs-centered-buffer-mode'." ;; Don't run if the mode is disabled(we are not in the indirect buffer). (when spacemacs--centered-buffer-mode-origin-buffer (let* ((window (selected-window)) (origin-buffer spacemacs--centered-buffer-mode-origin-buffer) (indirect-buffer (window-buffer window))) (setq spacemacs--centered-buffer-mode-origin-buffer nil) (switch-to-buffer origin-buffer nil t) (spacemacs//centered-buffer-mode-buffer-fringr-color-toggle origin-buffer nil) (setq spacemacs--centered-buffer-mode-indirect-buffer nil) (when (buffer-live-p indirect-buffer) (dolist (window (get-buffer-window-list indirect-buffer 2)) (set-window-buffer window origin-buffer)) (kill-buffer indirect-buffer))))) (defun spacemacs//centered-buffer-mode-buffer-fringr-color-toggle (buffer flag) "Change fringe color of the BUFFER if FLAG has non-nil value. Revert changes Otherwise." (with-current-buffer buffer (if flag (face-remap-add-relative 'fringe :background spacemacs-centered-buffer-mode-fringe-color) (face-remap-reset-base 'fringe) (face-remap-add-relative 'fringe :background spacemacs-centered-buffer-mode-default-fringe-color)))) (defun spacemacs//centered-buffer-calc-fringe (&optional window text-pixel-size) "Calculate fringe width for `spacemacs-centered-buffer-mode'. Uses text-pixel-size if provided, otherwise calculates it with `window-pixel-width'." (-(/ (- (window-pixel-width window) (or text-pixel-size (car (window-text-pixel-size window)))) 2) (if (bound-and-true-p org-indent-mode) 40 spacemacs-centered-buffer-mode-safety-gap-width))) (defun spacemacs//centered-buffer-buffer-update-window-fringes (window) "Update fringe width of WINDOW if it displays `centered-buffer-mode' buffer." (when (and (buffer-local-value 'spacemacs--centered-buffer-mode-origin-buffer (window-buffer window)) ;; Might be needed because ;; (spacemacs-centered-buffer-mode -1) kills buffers. (buffer-live-p (window-buffer window))) (let ((fringe-w (spacemacs//centered-buffer-calc-fringe window spacemacs--centered-buffer-mode-text-pixel-size))) (if (> fringe-w spacemacs-centered-buffer-mode-min-fringe-width) (set-window-fringes window fringe-w fringe-w t) (spacemacs-centered-buffer-mode -1) (when spacemacs--centered-buffer-mode-indirect-buffers (spacemacs//centered-buffer-prune-indirect-buffer-list)))))) (defun spacemacs//centered-buffer-buffer-list-update-fringes () "Update fringe width of all `centered-buffer-mode' fringes." (dolist (frame (frame-list)) (when (frame-live-p frame) (dolist (window (window-list frame 2)) (spacemacs//centered-buffer-buffer-update-window-fringes window))))) (defun spacemacs//centered-buffer-after-change-function (begin end length) "Reduce `centered-buffer-mode' fringe width in case of buffer content overflow." (dolist (window (get-buffer-window-list (current-buffer) 2 t)) (save-excursion (let* ((min-pos (progn (goto-char begin) (point-at-bol))) (max-pos (progn (goto-char end) (point-at-eol))) (updated-segment-max-width (car (window-text-pixel-size window min-pos max-pos)))) (when (> updated-segment-max-width spacemacs--centered-buffer-mode-text-pixel-size) (setq spacemacs--centered-buffer-mode-text-pixel-size updated-segment-max-width) (spacemacs//centered-buffer-buffer-update-window-fringes window)))))) (defun spacemacs//centered-buffer-prune-indirect-buffer-list () "Remove indirect buffer from the `spacemacs--centered-buffer-mode-indirect-buffers' if the buffer hasn't at least one live window. Disables `centered-buffer-mode' hooks and advices if `spacemacs--centered-buffer-mode-indirect-buffers' has no elements left to minimize the performance hit when the mode isn't used." (dolist (buffer spacemacs--centered-buffer-mode-indirect-buffers) (unless (or (and (buffer-live-p buffer) (window-live-p (get-buffer-window buffer t))) (not spacemacs--centered-buffer-mode-indirect-buffers)) (let ((origin-buffer (buffer-local-value 'spacemacs--centered-buffer-mode-origin-buffer buffer))) (when (ignore-errors (kill-buffer buffer)) (setq spacemacs--centered-buffer-mode-indirect-buffers (delete buffer spacemacs--centered-buffer-mode-indirect-buffers)) (when (buffer-live-p origin-buffer) (spacemacs//centered-buffer-mode-buffer-fringr-color-toggle origin-buffer nil) (with-current-buffer origin-buffer (setq spacemacs--centered-buffer-mode-indirect-buffers nil))))))) ;; Remove hooks and advices when they are not needed anymore. (unless spacemacs--centered-buffer-mode-indirect-buffers (advice-remove 'previous-buffer #'spacemacs//centered-buffer-mode-prev-next-buffer-advice) (advice-remove 'next-buffer #'spacemacs//centered-buffer-mode-prev-next-buffer-advice) (remove-hook 'after-change-functions #'spacemacs//centered-buffer-after-change-function) (remove-hook 'buffer-list-update-hook 'spacemacs//centered-buffer-buffer-list-update-fringes) (remove-hook 'window-configuration-change-hook 'spacemacs//centered-buffer-buffer-list-update-fringes))) (defmacro with-mode-disabled (mode &rest body) "Evaluate BODY with MODE disabled." (declare (indent 1) (debug t)) `(if (not (bound-and-true-p ,mode)) (progn ,@body) (,mode -1) ,@body (,mode +1))) (provide 'centered-buffer-mode) ;;; centered-buffer-mode.el ends here