├── 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 | [](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 | 
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 |
--------------------------------------------------------------------------------