├── perfect-margin.gif ├── README.md └── perfect-margin.el /perfect-margin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpwang/perfect-margin/HEAD/perfect-margin.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | perfect-margin 2 | ============== 3 | 4 | [![MELPA](https://melpa.org/packages/perfect-margin-badge.svg)](https://melpa.org/#/perfect-margin) 5 | 6 | Perfect-margin is a global minor mode to auto center windows. 7 | 8 | Support using these following modes at the same time: 9 | 10 | - linum-mode 11 | - display-line-numbers 12 | - [minimap](https://elpa.gnu.org/packages/minimap.html) position both `'left` `'right` 13 | - [treemacs](https://github.com/Alexander-Miller/treemacs) position `'left` 14 | - [org-side-tree](https://github.com/localauthor/org-side-tree) position both `'left` `'right` 15 | 16 | You can use **treemacs** on the left side and **minimap** on the right side. 17 | 18 | minimap window on the right-side has been adviced to be fixed-width. 19 | 20 | A picture is more than one thousand words, here is how it looks like. 21 | 22 | ![perfect-margin](https://raw.githubusercontent.com/mpwang/perfect-margin/master/perfect-margin.gif) 23 | 24 | _theme: doom-dracula from beautiful [doom-themes](https://github.com/hlissner/emacs-doom-themes)_ 25 | 26 | [minimap](http://elpa.gnu.org/packages/minimap.html) from MELPA 27 | 28 | # Usage 29 | 30 | Put perfect-margin under your Emacs load path, and add this to your init.el 31 | 32 | ```lisp 33 | (require 'perfect-margin) 34 | ``` 35 | 36 | 37 | Use `M-x perfect-margin-mode` to turn on/off perfect-margin. 38 | 39 | To make it permanent add this to your init.el after require. 40 | ```lisp 41 | (perfect-margin-mode 1) 42 | ``` 43 | 44 | ## Important Note 45 | when using together with minimap or linum, make sure you place config for perfect-margin `AFTER` minimap and linum. 46 | 47 | for **doom-emacs** users please add this to your `config.el` file when using minimap on the **right** side (which is the default value), otherwise the minimap might overlap with the mode line. 48 | 49 | ``` lisp 50 | (after! doom-modeline 51 | (setq mode-line-right-align-edge 'right-fringe)) 52 | ``` 53 | 54 | # Customization 55 | 56 | Via `M-x customize-group` and enter perfect-margin. 57 | 58 | Change `perfect-margin-visible-width` and `Apply and Save`. That's it. 59 | 60 | *Or* you can change the visible window width by setup `perfect-margin-visible-width` on the init.el. 61 | 62 | ```lisp 63 | (setq perfect-margin-visible-width 128) 64 | ``` 65 | 66 | set `perfect-margin-visible-width` to `-1` to use the `fill-column` value for visible width. 67 | 68 | By default both left and right margins are set, but in most cases setting only the left margin is sufficient as it allows for more display room on the right. Enable this option to only set the left margin of windows. 69 | 70 | ``` lisp 71 | (setq perfect-margin-only-set-left-margin t) 72 | ``` 73 | 74 | ## Customize what window to ignore setting margins 75 | 76 | perfect-margin by default ignore setting margins for minibuffer window and any window whos name starts with "*". 77 | Many of them are for special purpose. 78 | 79 | You can change this behavior by setting `perfect-margin-ignore-regexps` and `perfect-margin-ignore-filters`. 80 | 81 | This behavior might be too conservative, you can 82 | - set `perfect-margin-ignore-filters` to `nil` to auto-center minibuffer windows 83 | - set `perfect-margin-ignore-regexps` to `nil` to auto-center special windows like the HELM windows 84 | - or simply set both variables to `nil` to let perfect-margin auto-center all windows no matter what. 85 | 86 | ```lisp 87 | (setq perfect-margin-ignore-filters nil) 88 | (setq perfect-margin-ignore-regexps nil) 89 | ``` 90 | 91 | Default value for these two variables are listed below. 92 | 93 | ```lisp 94 | (defcustom perfect-margin-ignore-regexps 95 | '("^minibuf" "^[[:space:]]*\\*") 96 | "List of strings to determine if window is ignored. 97 | 98 | Each string is used as regular expression to match the window buffer name." 99 | :group 'perfect-margin) 100 | 101 | (defcustom perfect-margin-ignore-filters 102 | '(window-minibuffer-p) 103 | "List of functions to determine if window is ignored. 104 | 105 | Each function is called with window as its sole arguemnt, 106 | returning a non-nil value indicate to ignore the window." 107 | :group 'perfect-margin) 108 | ``` 109 | 110 | ## Customize to set margins for minibuffer 111 | 112 | It would be nice to set the margins for centain buffers, while ignoring other speical buffers, such as 113 | - using with minibuffer completion packages like **Vertico** 114 | - using with **which-key** 115 | 116 | You can use this `perfect-margin-force-regexps` setting 117 | 118 | ```lisp 119 | ;; Center completion minibuffer 120 | (add-to-list 'perfect-margin-force-regexps "*Minibuf") 121 | (add-to-list 'perfect-margin-force-regexps "*which-key") 122 | ``` 123 | 124 | This regex will ignore buffers like `*Completions*` or `*Messages*`, but allow `*Minibuf-1*`, `*Minibuf-2*`, etc. 125 | 126 | ## Customize window fringes 127 | 128 | perfect-margin by default does not manipulate window fringes. 129 | 130 | You can set `perfect-margin-hide-fringes` to `t` to tell perfect-margin to set both left and right fringe of all windows to zero, this 131 | might be useful in some corner cases where other packages which also manipulate fringes are enabled. 132 | 133 | 134 | # Additional binding on margin area 135 | 136 | You can place this in your init.el to make mouse wheel scroll on margin area just like it scroll on the visible window. 137 | 138 | ```lisp 139 | (dolist (margin '(" " " ")) 140 | (global-set-key (kbd (concat margin "")) 'ignore) 141 | (global-set-key (kbd (concat margin "")) 'ignore) 142 | (dolist (multiple '("" "double-" "triple-")) 143 | (global-set-key (kbd (concat margin "<" multiple "wheel-up>")) 'mwheel-scroll) 144 | (global-set-key (kbd (concat margin "<" multiple "wheel-down>")) 'mwheel-scroll))) 145 | ``` 146 | 147 | # for *use-package* user 148 | 149 | ```lisp 150 | (use-package perfect-margin 151 | :custom 152 | (perfect-margin-visible-width 128) 153 | :config 154 | ;; enable perfect-mode 155 | (perfect-margin-mode t) 156 | ;; auto-center minibuffer windows 157 | (setq perfect-margin-ignore-filters nil) 158 | ;; auto-center special windows 159 | (setq perfect-margin-ignore-regexps nil) 160 | ;; add additinal bding on margin area 161 | (dolist (margin '(" " " ")) 162 | (global-set-key (kbd (concat margin "")) 'ignore) 163 | (global-set-key (kbd (concat margin "")) 'ignore) 164 | (dolist (multiple '("" "double-" "triple-")) 165 | (global-set-key (kbd (concat margin "<" multiple "wheel-up>")) 'mwheel-scroll) 166 | (global-set-key (kbd (concat margin "<" multiple "wheel-down>")) 'mwheel-scroll)))) 167 | ``` 168 | 169 | # for *doom-emacs* user 170 | 171 | `package.el` 172 | 173 | ``` lisp 174 | (package! perfect-margin) 175 | ``` 176 | 177 | `config.el` 178 | 179 | ``` lisp 180 | (use-package! perfect-margin 181 | :config 182 | (after! doom-modeline 183 | (setq mode-line-right-align-edge 'right-fringe)) 184 | (after! minimap 185 | ;; if you use (vc-gutter +pretty) 186 | ;; and theme is causing "Invalid face attribute :foreground nil" 187 | ;; (setq minimap-highlight-line nil) 188 | (setq minimap-width-fraction 0.08)) 189 | ;; (setq perfect-margin-only-set-left-margin t) 190 | (perfect-margin-mode t)) 191 | ``` 192 | 193 | 194 | # Emacs Rocks and happy hacking! 195 | -------------------------------------------------------------------------------- /perfect-margin.el: -------------------------------------------------------------------------------- 1 | ;;; perfect-margin.el --- Auto center windows, works with line numbers 2 | ;; Copyright (C) 2014 Randall Wang 3 | 4 | ;; Author: Randall Wang 5 | ;; Created: 19 Nov 2014 6 | ;; Version: 0.1 7 | ;; URL: https://github.com/mpwang/perfect-margin 8 | ;; Keywords: convenience, frames 9 | ;; Package-Requires: ((emacs "25.1")) 10 | 11 | ;; This file is *NOT* part of GNU Emacs. 12 | 13 | ;; This program is free software; you can redistribute it and/or 14 | ;; modify it under the terms of the GNU General Public License 15 | ;; as published by the Free Software Foundation; either version 2 16 | ;; of the License, or (at your option) any later version. 17 | ;; 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANT ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | ;; 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | ;; 28 | ;; # Usage 29 | ;; 30 | ;; Put perfect-margin under your Emacs load path, and add this to your init.el 31 | ;; 32 | ;; (require 'perfect-margin) 33 | ;; 34 | ;; Use `M-x perfect-margin-mode` to turn on/off perfect-margin. 35 | ;; 36 | ;; To make it permanent add this to your init.el after require. 37 | ;; 38 | ;; (perfect-margin-mode 1) 39 | ;; 40 | ;; Note: when using together with minimap or linum/display-line-numbers, 41 | ;; make sure you place config for perfect-margin *AFTER* minimap and linum. 42 | ;; 43 | ;; # Customization 44 | ;; 45 | ;; Via `M-x customize-group` and enter perfect-margin. 46 | ;; 47 | ;; Change `perfect-margin-visible-width` and `Apply and Save`. That's it. 48 | ;; 49 | ;; *Or* you can change the visible window width by setup `perfect-margin-visible-width` on the init.el. 50 | ;; 51 | ;; (setq perfect-margin-visible-width 128) 52 | ;; 53 | ;; By default both left and right margins are set, enable this option to only set the left margin of windows. 54 | ;; 55 | ;; (setq perfect-margin-only-set-left-margin t) 56 | ;; 57 | ;; # Additional binding on margin area 58 | ;; 59 | ;; You can place this in your init.el to make mouse wheel scroll on margin area just like it scroll on the visible window. 60 | ;; 61 | ;; (dolist (margin '(" " " ")) 62 | ;; (global-set-key (kbd (concat margin "")) 'ignore) 63 | ;; (global-set-key (kbd (concat margin "")) 'ignore) 64 | ;; (dolist (multiple '("" "double-" "triple-")) 65 | ;; (global-set-key (kbd (concat margin "<" multiple "wheel-up>")) 'mwheel-scroll) 66 | ;; (global-set-key (kbd (concat margin "<" multiple "wheel-down>")) 'mwheel-scroll))) 67 | 68 | ;;; Code: 69 | (require 'cl-lib) 70 | (require 'subr-x) 71 | 72 | ;; linum-mode is deprecated since 26.1, use display-line-numbers-mode instead 73 | (when (version< emacs-version "26.1") 74 | (require 'linum)) 75 | 76 | ;;---------------------------------------------------------------------------- 77 | ;; external definitions 78 | ;;---------------------------------------------------------------------------- 79 | (defvar linum-format) 80 | (declare-function linum-update-current "linum") 81 | 82 | (defvar minimap-buffer-name) 83 | (defvar minimap-minimum-width) 84 | (defvar minimap-width-fraction) 85 | (defvar minimap-window-location) 86 | (declare-function minimap-get-window "minimap") 87 | 88 | (declare-function treemacs-get-local-window "treemacs-scope") 89 | 90 | (defvar org-side-tree-display-side) 91 | (declare-function org-side-tree-has-tree-p "org-side-tree") 92 | 93 | ;;---------------------------------------------------------------------------- 94 | ;; group definitions 95 | ;;---------------------------------------------------------------------------- 96 | (defgroup perfect-margin nil 97 | "Auto center windows, work with minimap and/or linum-mode." 98 | :group 'emacs) 99 | 100 | (defcustom perfect-margin-lighter " \u24c2" 101 | "Mode-line indicator for symbol `perfect-margin-mode'." 102 | :type '(choice (const :tag "No lighter" "") string) 103 | :safe 'stringp 104 | :group 'perfect-margin) 105 | 106 | (defcustom perfect-margin-visible-width 128 107 | "The visible width of main window to be kept at center." 108 | :group 'perfect-margin 109 | :type '(choice (const :tag "fill-column" -1) number)) 110 | 111 | (defcustom perfect-margin-hide-fringes nil 112 | "Whether to set both fringes in all windows to 0." 113 | :group 'perfect-margin 114 | :type 'boolean) 115 | 116 | (defcustom perfect-margin-only-set-left-margin nil 117 | "Set the left margin only, leave right margin untouched." 118 | :group 'perfect-margin 119 | :type 'boolean) 120 | 121 | (defcustom perfect-margin-disable-in-splittable-check nil 122 | "Disable margins when `window-splittable-p' is called. 123 | 124 | When this option is enabled, `perfect-margin-mode' will temporarily remove 125 | the margins when `window-splittable-p' is called to determine if a window can 126 | be split. This allows `split-window-sensibly' to split a window when its 127 | total width is sufficient, even if its visible width (excluding margins) is 128 | below the split threshold." 129 | :group 'perfect-margin 130 | :type 'boolean) 131 | 132 | (defcustom perfect-margin-ignore-regexps 133 | '("^minibuf" "^[[:space:]]*\\*") 134 | "List of strings to determine if window is ignored. 135 | 136 | Each string is used as regular expression to match the window buffer name." 137 | :group 'perfect-margin 138 | :type '(repeat regexp)) 139 | 140 | (defcustom perfect-margin-ignore-filters 141 | '(window-minibuffer-p) 142 | "List of functions to determine if window is ignored. 143 | 144 | Each function is called with window as its sole arguemnt, 145 | returning a non-nil value indicate to ignore the window." 146 | :group 'perfect-margin 147 | :type '(list function)) 148 | 149 | (defcustom perfect-margin-ignore-modes 150 | '(exwm-mode 151 | doc-view-mode 152 | nov-mode) 153 | "List of symbols of ignored major modes." 154 | :type '(repeat symbol) 155 | :group 'perfect-margin) 156 | 157 | (defcustom perfect-margin-force-regexps 158 | '() 159 | "List of strings to force margins, even the window would be in the ignored list. 160 | 161 | Each string is used as regular expression to match the window buffer name." 162 | :group 'perfect-margin 163 | :type '(repeat regexp)) 164 | 165 | (defcustom perfect-margin-enable-debug-log nil 166 | "Enable output debug log." 167 | :group 'perfect-margin 168 | :type 'boolean) 169 | 170 | ;;---------------------------------------------------------------------------- 171 | ;; env predictors 172 | ;;---------------------------------------------------------------------------- 173 | ;; linum-mode is a minor mode 174 | (defun perfect-margin-with-linum-p () 175 | "Whether `linum-mode' is found and turn on." 176 | (bound-and-true-p linum-mode)) 177 | 178 | ;; display-line-numbers-mode is a minor mode 179 | (defun perfect-margin-with-display-line-numbers-p () 180 | "Whether `display-line-numbers-mode' is found and turn on." 181 | (bound-and-true-p display-line-numbers-mode)) 182 | 183 | ;; minimap-mode is a minor mode 184 | (defun perfect-margin-with-minimap-p () 185 | "Whether `minimap-mode' is found and turn on." 186 | (bound-and-true-p minimap-mode)) 187 | 188 | ;; treemacs-mode is a function 189 | (defun perfect-margin-with-treemacs-visible-p () 190 | "Whether `treemacs-mode' is found and treemacs window is visible." 191 | (and 192 | (fboundp 'treemacs-mode) 193 | (fboundp 'treemacs-get-local-window) 194 | (treemacs-get-local-window))) 195 | 196 | ;; org-side-tree-mode is a major mode 197 | (defun perfect-margin-with-org-side-tree-p () 198 | "Whether `org-side-tree' is found." 199 | (and 200 | (fboundp 'org-side-tree) 201 | (fboundp 'org-side-tree-has-tree-p))) 202 | 203 | ;;---------------------------------------------------------------------------- 204 | ;; Private functions 205 | ;;---------------------------------------------------------------------------- 206 | (defun perfect-margin--show-line-numbers-p () 207 | "Whether line numbers are displayed." 208 | (or (perfect-margin-with-linum-p) 209 | (perfect-margin-with-display-line-numbers-p))) 210 | 211 | (defun perfect-margin--line-number-width () 212 | "Return the width consumed by line number display in current buffer. 213 | For `display-line-numbers-mode', use the actual display width. 214 | For `linum-mode', calculate based on max line number with minimum of 3." 215 | (cond 216 | ;; Modern Emacs: display-line-numbers-mode uses a separate gutter 217 | ((perfect-margin-with-display-line-numbers-p) 218 | (if (fboundp 'line-number-display-width) 219 | (ceiling (line-number-display-width 'columns)) 220 | ;; Fallback: estimate based on line count + 2 for padding 221 | (+ 2 (floor (log (max 1 (line-number-at-pos (point-max))) 10))))) 222 | ;; Legacy: linum-mode uses margin space 223 | ((perfect-margin-with-linum-p) 224 | (max 3 (1+ (floor (log (max 1 (line-number-at-pos (point-max))) 10))))) 225 | (t 0))) 226 | 227 | (defun perfect-margin--default-left-margin () 228 | "Default left margin. 229 | For `linum-mode', returns the calculated width since linum uses margin space. 230 | For `display-line-numbers-mode', returns 0 since it uses a separate gutter." 231 | (if (perfect-margin-with-linum-p) 232 | (perfect-margin--line-number-width) 233 | 0)) 234 | 235 | (defun perfect-margin--init-window-margins (&optional win) 236 | "Calculate target window margins as if there is only one window on frame. 237 | Accounts for space consumed by `display-line-numbers-mode' gutter. 238 | If WIN is provided, check line numbers in that window's context. 239 | Uses `with-selected-window' because `line-number-display-width' requires 240 | the window to be selected, not just its buffer to be current." 241 | (let* ((visible-width (if (> perfect-margin-visible-width 0) 242 | perfect-margin-visible-width 243 | fill-column)) 244 | (base-margin (round (max 0 (/ (- (frame-width) visible-width) 2)))) 245 | (line-num-width (if win 246 | (with-selected-window win 247 | (if (perfect-margin-with-display-line-numbers-p) 248 | (perfect-margin--line-number-width) 249 | 0)) 250 | (if (perfect-margin-with-display-line-numbers-p) 251 | (perfect-margin--line-number-width) 252 | 0))) 253 | ;; Subtract full line-num-width so content stays at same position 254 | ;; when line numbers are toggled (margin + gutter = base-margin) 255 | (init-margin-width (max 0 (- base-margin line-num-width)))) 256 | (cons 257 | init-margin-width 258 | (if perfect-margin-only-set-left-margin 0 init-margin-width)))) 259 | 260 | (defun perfect-margin--left-adjacent-covered-p (a-win b-win) 261 | "If A-WIN is left adjacent to B-WIN." 262 | (let ((a-edges (window-edges a-win)) 263 | (b-edges (window-edges b-win))) 264 | (and (= (nth 2 a-edges) (nth 0 b-edges)) 265 | (<= (nth 1 a-edges) (nth 1 b-edges)) 266 | (>= (nth 3 a-edges) (nth 3 b-edges))))) 267 | 268 | (defun perfect-margin--get-right-margin (win &optional new-right-margin) 269 | "Return the value to be use as WIN's right margin. 270 | 271 | If `perfect-margin-only-set-left-margin' is nil, return right margin of WIN. 272 | If NEW-RIGHT-MARGIN is non-nil, return it, otherwise use default value." 273 | (cond 274 | (perfect-margin-only-set-left-margin (cdr (window-margins win))) 275 | (new-right-margin new-right-margin) 276 | (t (cdr (perfect-margin--init-window-margins win))))) 277 | 278 | (defun perfect-margin--get-min-margins (margin-candidates) 279 | "Find the minimums in the car and cdr positions of MARGIN-CANDIDATES." 280 | ;; Example usage: 281 | ;; (perfect-margin--get-min-margins '((3 . 7) (5 . 6))) 282 | ;; It returns: (3 . 6) 283 | ;; (perfect-margin--get-min-margins '((3 . 7) (1 . 6) (8 . 2))) 284 | ;; It returns: (1 . 2) 285 | (let ((min-first nil) 286 | (min-second nil)) 287 | (dolist (pair margin-candidates) 288 | (let ((car-val (car pair)) 289 | (cdr-val (cdr pair))) 290 | (when (or (null min-first) (< car-val min-first)) 291 | (setq min-first car-val)) 292 | (when (or (null min-second) (< cdr-val min-second)) 293 | (setq min-second cdr-val)))) 294 | (cons min-first min-second))) 295 | 296 | (defun perfect-margin--force-margin-p (win) 297 | "If set margins of WIN unconditionaly." 298 | (let ((name (buffer-name (window-buffer win)))) 299 | (cl-some (lambda (regexp) (string-match-p regexp name)) perfect-margin-force-regexps))) 300 | 301 | (defun perfect-margin--auto-margin-ignore-p (win) 302 | "Conditions for filtering window (WIN) to setup margin." 303 | (let* ((buffer (window-buffer win)) 304 | (name (buffer-name buffer))) 305 | (or (with-current-buffer buffer 306 | (apply #'derived-mode-p perfect-margin-ignore-modes)) 307 | (cl-some #'identity 308 | (nconc (mapcar (lambda (regexp) (string-match-p regexp name)) perfect-margin-ignore-regexps) 309 | (mapcar (lambda (func) (funcall func win)) perfect-margin-ignore-filters))) 310 | (perfect-margin--supported-side-window-p win)))) 311 | 312 | (defun perfect-margin--supported-side-window-p (win) 313 | "Side window(WIN) that won't affect main window's margins." 314 | (or (and (perfect-margin-with-minimap-p) 315 | (or (string-match minimap-buffer-name (buffer-name (window-buffer win))) 316 | (perfect-margin--minimap-window-p win))) 317 | (and (perfect-margin-with-treemacs-visible-p) 318 | (eq win (treemacs-get-local-window))) 319 | (and (perfect-margin-with-org-side-tree-p) 320 | (with-current-buffer (window-buffer win) 321 | (eq major-mode 'org-side-tree-mode))))) 322 | 323 | ;;---------------------------------------------------------------------------- 324 | ;; Minimap 325 | ;;---------------------------------------------------------------------------- 326 | (defun perfect-margin--minimap-window-p (win) 327 | "Judge if the window(WIN) is the minimap window itself, when it's live." 328 | (when (and (perfect-margin-with-minimap-p) 329 | (minimap-get-window) 330 | (window-live-p (minimap-get-window))) 331 | (let ((minimap-edges (window-edges (minimap-get-window))) 332 | (current-edges (window-edges win))) 333 | (and (= (nth 0 minimap-edges) (nth 0 current-edges)) 334 | (= (nth 1 minimap-edges) (nth 1 current-edges)) 335 | (= (nth 2 minimap-edges) (nth 2 current-edges)))))) 336 | 337 | (defun perfect-margin--minimap-left-adjacent-covered-p (win) 338 | "Judge if the window(WIN) is left adjacent to minimap window." 339 | (when (and (perfect-margin-with-minimap-p) 340 | (minimap-get-window) 341 | (window-live-p (minimap-get-window))) 342 | (perfect-margin--left-adjacent-covered-p (minimap-get-window) win))) 343 | 344 | (defun perfect-margin-minimap-margin-window (win) 345 | "Setup window margins with minimap at different stage. 346 | 347 | WIN will be any visible window, excluding the ignored windows." 348 | (when (perfect-margin-with-minimap-p) 349 | (let ((init-window-margins (perfect-margin--init-window-margins win)) 350 | (win-edges (window-edges win))) 351 | (cond 352 | ;; minimap left adjacent 353 | ((perfect-margin--minimap-left-adjacent-covered-p win) 354 | (if (not (>= (nth 2 win-edges) (frame-width))) 355 | (cons (perfect-margin--default-left-margin) 0) 356 | (cons (max (perfect-margin--default-left-margin) 357 | (- (car init-window-margins) 358 | (window-total-width (minimap-get-window)))) 359 | (perfect-margin--get-right-margin win)))) 360 | ;; minimap right adjacent 361 | (t 362 | (cons (car init-window-margins) 363 | (perfect-margin--get-right-margin 364 | win 365 | (- (cdr init-window-margins) 366 | (window-total-width (minimap-get-window)))))))))) 367 | 368 | ;;---------------------------------------------------------------------------- 369 | ;; Treemacs 370 | ;;---------------------------------------------------------------------------- 371 | (defun perfect-margin--treemacs-left-adjacent-covered-p (win) 372 | "Judge if the window(WIN) is left adjacent to treemacs window." 373 | (perfect-margin--left-adjacent-covered-p (treemacs-get-local-window) win)) 374 | 375 | (defun perfect-margin-treemacs-margin-window (win) 376 | "Setup treemacs window margins. 377 | 378 | WIN will be any visible window, excluding the ignored windows." 379 | (when (perfect-margin-with-treemacs-visible-p) 380 | (let ((init-window-margins (perfect-margin--init-window-margins win)) 381 | (win-edges (window-edges win)) 382 | (treemacs-window (treemacs-get-local-window))) 383 | (cond 384 | ((perfect-margin--treemacs-left-adjacent-covered-p win) 385 | (cons (max (perfect-margin--default-left-margin) 386 | (- (car init-window-margins) 387 | (window-total-width treemacs-window))) 388 | (perfect-margin--get-right-margin win))))))) 389 | 390 | ;;---------------------------------------------------------------------------- 391 | ;; Org-side-tree 392 | ;;---------------------------------------------------------------------------- 393 | (defun perfect-margin-org-side-tree-margin-window (win) 394 | "Setup org-side-tree window margins. 395 | 396 | WIN will be any visible window, excluding the ignored windows." 397 | (when (perfect-margin-with-org-side-tree-p) 398 | (let* ((init-window-margins (perfect-margin--init-window-margins win)) 399 | (tree-buffer (org-side-tree-has-tree-p (window-buffer win))) 400 | (tree-window (if tree-buffer 401 | (get-buffer-window tree-buffer) 402 | (get-buffer-window "*Org-Side-Tree*")))) 403 | (when (and tree-window 404 | (window-live-p tree-window)) 405 | (cond 406 | ((and (eq org-side-tree-display-side 'left) 407 | (perfect-margin--left-adjacent-covered-p tree-window win)) 408 | (cons (max (perfect-margin--default-left-margin) 409 | (- (car init-window-margins) (window-total-width tree-window))) 410 | (perfect-margin--get-right-margin win))) 411 | ((and (eq org-side-tree-display-side 'right) 412 | (perfect-margin--left-adjacent-covered-p win tree-window)) 413 | (cons (car init-window-margins) 414 | (perfect-margin--get-right-margin 415 | win 416 | (- (cdr init-window-margins) (window-total-width tree-window)))))))))) 417 | 418 | ;;---------------------------------------------------------------------------- 419 | ;; Main 420 | ;;---------------------------------------------------------------------------- 421 | (defun perfect-margin--main-window () 422 | "Find the main window based on the largest width and height." 423 | (car 424 | (sort 425 | (window-list) 426 | (lambda (w1 w2) 427 | (> (* (window-total-width w1) (window-body-height w1)) 428 | (* (window-total-width w2) (window-body-height w2))))))) 429 | 430 | (defun perfect-margin--vertically-split-main-window-p (main-win) 431 | "Check if the MAIN-WIN has been vertically split. 432 | 433 | checks if there is any other window that shares the same vertical start position 434 | as the main window. If such a window exists, it indicates that the main window 435 | has been vertically split." 436 | (let ((main-start (window-top-line main-win))) 437 | (catch 'split 438 | (dolist (win (window-list)) 439 | (when (and (not (perfect-margin--supported-side-window-p win)) 440 | (not (eq win main-win)) 441 | (eq main-start (window-top-line win))) 442 | (throw 'split t))) 443 | nil))) 444 | 445 | (defvar perfect-margin-margin-window-function-list 446 | '(perfect-margin-minimap-margin-window 447 | perfect-margin-treemacs-margin-window 448 | perfect-margin-org-side-tree-margin-window 449 | (lambda (win) (perfect-margin--init-window-margins win)))) 450 | 451 | (defun perfect-margin--set-win-margin (win main-win) 452 | (if (< (window-total-width win) (window-total-width main-win)) 453 | (progn 454 | (when perfect-margin-enable-debug-log 455 | (message "%S margins reset" win)) 456 | (set-window-margins win 0 0)) 457 | (let ((margin-candidates (thread-last 458 | perfect-margin-margin-window-function-list 459 | (mapcar (lambda (f) (funcall f win))) 460 | (remove nil) 461 | (remove t)))) 462 | (when margin-candidates 463 | (let ((min-margins (perfect-margin--get-min-margins margin-candidates)) 464 | (win-fringes (window-fringes win))) 465 | (when perfect-margin-enable-debug-log 466 | (message "%S candidaets: %S min-margins: %S" win margin-candidates min-margins)) 467 | (set-window-margins win (car min-margins) (cdr min-margins)) 468 | ;; draw the fringes inside the margin space 469 | ;; for package like git-gutter-fringe to display indicator near the line number 470 | (set-window-fringes win (nth 0 win-fringes) (nth 1 win-fringes) nil))))) 471 | (when perfect-margin-hide-fringes 472 | (set-window-fringes win 0 0))) 473 | 474 | (defun perfect-margin-margin-windows () 475 | "Setup margins, keep the visible main window always at center." 476 | (let ((main-win (perfect-margin--main-window))) 477 | (if (perfect-margin--vertically-split-main-window-p main-win) 478 | ;; reset all windows if main window is vertically split 479 | (dolist (win (window-list)) 480 | (cond 481 | ((perfect-margin--force-margin-p win) (perfect-margin--set-win-margin win main-win)) 482 | ((perfect-margin--auto-margin-ignore-p win) nil) 483 | (t (set-window-margins win 0 0)))) 484 | ;; set the margins for windows 485 | (dolist (win (window-list)) 486 | (cond 487 | ((perfect-margin--force-margin-p win) (perfect-margin--set-win-margin win main-win)) 488 | ((perfect-margin--auto-margin-ignore-p win) nil) 489 | (t (perfect-margin--set-win-margin win main-win))))))) 490 | 491 | (defun perfect-margin-margin-frame (&optional _) 492 | "Hook to resize window when frame size change." 493 | (when (and (fboundp 'frame-size-changed-p) 494 | (frame-size-changed-p)) 495 | (perfect-margin-margin-windows))) 496 | 497 | ;;---------------------------------------------------------------------------- 498 | ;; Advice 499 | ;;---------------------------------------------------------------------------- 500 | (defun perfect-margin--linum-format (line) 501 | "Function for `linum-format' to format LINE with consistent width. 502 | Width is calculated based on max line number, with minimum of 3." 503 | (let ((width (max 3 (1+ (floor (log (max 1 (line-number-at-pos (point-max))) 10)))))) 504 | (propertize 505 | (format (concat "%" (number-to-string width) "d") line) 506 | 'face 507 | 'linum))) 508 | 509 | (defvar perfect-margin--linum-update-win-left-margin nil 510 | "Variable to store original window marings before `linum-update-window'.") 511 | 512 | (defadvice linum-update-window (before perfect-margin-linum-update-before (win)) 513 | "Save window's original left margin." 514 | (setq perfect-margin--linum-update-win-left-margin (or (car (window-margins win)) 0))) 515 | 516 | (defadvice linum-update-window (after perfect-margin-linum-update-after (win)) 517 | "Restore windonw's original left margin, as `linum-update-window' always reset left margin." 518 | (set-window-margins win perfect-margin--linum-update-win-left-margin (cdr (window-margins win)))) 519 | 520 | (defadvice minimap-update (after minimap-update-no-fringe nil) 521 | "Prevent fringe overlay of target buffer from drawing on `minimap-window'." 522 | (when (and (minimap-get-window) 523 | (window-live-p (minimap-get-window)) 524 | minimap-hide-fringes) 525 | (set-window-fringes (minimap-get-window) 0 0))) 526 | 527 | (defadvice split-window (before perfect-margin--disable-margins nil) 528 | "Adjust all existing windows before 'split-window' is called." 529 | (dolist (win (window-list)) 530 | (unless (perfect-margin--auto-margin-ignore-p win) 531 | (set-window-margins win 0 0) 532 | (when perfect-margin-hide-fringes 533 | (set-window-fringes win 0 0))))) 534 | 535 | (defun perfect-margin--window-splittable-p-advice (orig-fun window &optional horizontal) 536 | "Advice for `window-splittable-p' to temporarily remove margins when called. 537 | 538 | If WINDOW is not managed by perfect-margin, HORIZONTAL is nil, 539 | or `perfect-margin-disable-in-splittable-check' is nil, 540 | the function will not modify the margins and directly call ORIG-FUN." 541 | (if (or (not horizontal) 542 | (not perfect-margin-disable-in-splittable-check) 543 | (perfect-margin--auto-margin-ignore-p window)) 544 | (funcall orig-fun window horizontal) 545 | (let ((margins (window-margins window))) 546 | (prog2 547 | (set-window-margins window 0 (if perfect-margin-only-set-left-margin 548 | (cdr margins) 0)) 549 | (funcall orig-fun window horizontal) 550 | (set-window-margins window (car margins) (cdr margins)))))) 551 | 552 | ;; I'm tired of a width-changing minimap on the right, just use a fixed width 553 | (defun perfect-margin--minimap-create-window-advice (orig-fun &rest args) 554 | "Advice to modify the behavior of `minimap-create-window'. 555 | Create the minimap-window to be fixed width, ignore the ARGS, 556 | ORIG-FUN is `split-window-horizontally'." 557 | (let* ((original-split-window-horizontally (symbol-function 'split-window-horizontally)) 558 | (min-width (round (max minimap-minimum-width 559 | (* minimap-width-fraction (frame-width))))) 560 | (fixed-width (if (eq minimap-window-location 'left) min-width (* -1 min-width)))) 561 | (cl-letf (((symbol-function 'split-window-horizontally) 562 | (lambda (&optional size) 563 | (funcall original-split-window-horizontally fixed-width)))) 564 | (when perfect-margin-enable-debug-log 565 | (message "minimap fixed width %S" fixed-width)) 566 | (apply orig-fun args)))) 567 | 568 | ;; make the minimap-window on the right to be consistent when open/close the left side window 569 | (defun perfect-margin--fix-minimap-width () 570 | "Ensure that the minimap window maintains a fixed total width." 571 | (when (perfect-margin-with-minimap-p) 572 | (let ((minimap-win (minimap-get-window)) 573 | (fixed-width (* minimap-width-fraction (frame-width)))) 574 | (when (window-live-p minimap-win) 575 | (with-selected-window minimap-win 576 | (let* ((current-total-width (window-total-width)) 577 | (delta (round (- fixed-width current-total-width)))) 578 | (when (not (zerop delta)) 579 | ;; Resize by delta to match fixed width 580 | (window-resize nil delta t)))))))) 581 | 582 | ;;---------------------------------------------------------------------------- 583 | ;; MINOR mode definition 584 | ;;---------------------------------------------------------------------------- 585 | ;;;###autoload 586 | (defun perfect-margin--on-display-line-numbers-toggle () 587 | "Handler for `display-line-numbers-mode' toggle. 588 | Recalculates margins when line numbers are turned on or off." 589 | (when perfect-margin-mode 590 | (perfect-margin-margin-windows))) 591 | 592 | (define-minor-mode perfect-margin-mode 593 | "Auto center windows." 594 | :init-value nil 595 | :lighter perfect-margin-lighter 596 | :global t 597 | (if perfect-margin-mode 598 | ;; add hook and activate 599 | (progn 600 | (when (perfect-margin-with-linum-p) 601 | (ad-activate 'linum-update-window) 602 | (when (eq linum-format 'dynamic) 603 | (setq linum-format 'perfect-margin--linum-format))) 604 | (when (perfect-margin-with-minimap-p) 605 | (ad-activate 'minimap-update) 606 | (advice-add 'minimap-create-window :around #'perfect-margin--minimap-create-window-advice)) 607 | (when (fboundp 'minimap-mode) 608 | (add-hook 'window-configuration-change-hook 'perfect-margin--fix-minimap-width)) 609 | (ad-activate 'split-window) 610 | (advice-add 'window-splittable-p :around #'perfect-margin--window-splittable-p-advice) 611 | (add-hook 'window-configuration-change-hook 'perfect-margin-margin-windows) 612 | (add-hook 'window-size-change-functions 'perfect-margin-margin-frame) 613 | ;; Recalculate margins when line numbers are toggled 614 | (add-hook 'display-line-numbers-mode-hook #'perfect-margin--on-display-line-numbers-toggle) 615 | (perfect-margin-margin-windows)) 616 | ;; remove hook and restore margin 617 | (when (perfect-margin-with-linum-p) 618 | (ad-deactivate 'linum-update-window) 619 | (when (eq linum-format 'perfect-margin--linum-format) 620 | (setq linum-format 'dynamic)) 621 | (linum-update-current)) 622 | (when (perfect-margin-with-minimap-p) 623 | (ad-deactivate 'minimap-update) 624 | (advice-remove 'minimap-create-window #'perfect-margin--minimap-create-window-advice)) 625 | (when (fboundp 'minimap-mode) 626 | (remove-hook 'window-configuration-change-hook 'perfect-margin--fix-minimap-width)) 627 | (ad-deactivate 'split-window) 628 | (advice-remove 'window-splittable-p #'perfect-margin--window-splittable-p-advice) 629 | (remove-hook 'window-configuration-change-hook 'perfect-margin-margin-windows) 630 | (remove-hook 'window-size-change-functions 'perfect-margin-margin-frame) 631 | (remove-hook 'display-line-numbers-mode-hook #'perfect-margin--on-display-line-numbers-toggle) 632 | (dolist (window (window-list)) 633 | (unless (perfect-margin--auto-margin-ignore-p window) 634 | (set-window-margins window 0 0))))) 635 | 636 | (provide 'perfect-margin) 637 | 638 | ;;; perfect-margin.el ends here 639 | --------------------------------------------------------------------------------