├── readme.md └── window-layout.el /readme.md: -------------------------------------------------------------------------------- 1 | # window-layout management library 2 | 3 | Split a frame or window into some windows according to a layout recipe. 4 | 5 | ## Example 6 | 7 | ```el 8 | ;; Layout function 9 | ; -> three pane layout. 10 | (setq wm ; <-- window management object 11 | (wlf:layout 12 | '(| (:left-size-ratio 0.3) 13 | folder 14 | (- (:upper-max-size 15) 15 | summary 16 | message)) 17 | '((:name folder 18 | :buffer "folder buffer") 19 | (:name summary 20 | :buffer "summary buffer") 21 | (:name message 22 | :buffer "message buffer") 23 | ))) 24 | 25 | ;; Window controlling 26 | (wlf:show wm 'summary) 27 | (wlf:hide wm 'summary) 28 | (wlf:toggle wm 'summary) 29 | (wlf:select wm 'summary) 30 | (wlf:toggle-maximize wm 'summary) 31 | 32 | ;; Window updating 33 | (wlf:refresh wm) 34 | (wlf:reset-window-sizes wm) 35 | (wlf:reset-init wm) 36 | 37 | ;; Accessing a buffer 38 | (wlf:get-buffer wm 'summary) ; -> <#buffer object or buffer name> 39 | (wlf:set-buffer wm 'summary "*scratch*") 40 | 41 | ;; Accessing a window 42 | (wlf:get-window wm 'summary) ; -> <#window object> 43 | 44 | ;; Layout hook 45 | (defun wlf:test-hook (wset) (message "HOOK : %s" wset)) 46 | (wlf:layout-hook-add wm 'wlf:test-hook) 47 | (wlf:layout-hook-remove wm 'wlf:test-hook) 48 | ``` 49 | 50 | ## API Document 51 | 52 | ### Layout function 53 | 54 | - `wlf:layout` 55 | - Lay out windows and return a management object. 56 | - Arguments: (see the following sections) 57 | - recipe : layout recipe list 58 | - window-params : window options 59 | - subwindow-p : optional flag 60 | - Return 61 | - a wset object, the window management object 62 | - `wlf:no-layout` 63 | - Just return a management object, does not change window layout. 64 | - The arguments and return value are the same as `wlf:layout` ones. 65 | 66 | #### Layout recipe: 67 | 68 | ``` 69 | ( (split type) (split option) 70 | (left window name or recipe) 71 | (right window name or recipe) ) 72 | ``` 73 | 74 | - **split type** 75 | - `-` : split vertically 76 | - `|` : split horizontally 77 | - **split option** (the prefix 'left' can be replaced by 'right', 'upper' and 'lower'.) 78 | - `:left-size` (column or row number) window size 79 | - `:left-max-size` (column or row number) if window size is larger than this value, the window is shrunken. 80 | - `:left-size-ratio` (0.0 - 1.0) window size ratio. the size of the other side is the rest. 81 | 82 | Note: 83 | - The split option can be omitted. 84 | - The size parameters, `:size`, `:max-size` and `:size-ratio`, are mutually exclusive. 85 | - The size of a window is related with one of the other side window. So, if both side windows set conflict size parameters, the window size may not be adjusted as you write. 86 | 87 | #### Window options: 88 | 89 | - `:name` the window name. 90 | - `:buffer` a buffer name or a buffer object to show the window. If nil or omitted, the current buffer remains. 91 | - `:default-hide` (t/nil) if t, the window is hided initially. (default: nil) 92 | - `:fix-size` (t/nil) if t, when the windows are laid out again, the window size is remained. (default: nil) 93 | 94 | #### subwindow-p option: 95 | 96 | If this option is not nil, this function splits the windows within 97 | the current window. If this option is nil or omitted, this function 98 | uses the entire space of the current frame. Because some user 99 | actions and complicated window layouts may cause unexpected split 100 | behaviors, it is easy to use the entire space of a frame. 101 | 102 | #### Return value (Window management object): 103 | 104 | You should not access the management object directly, because it is not 105 | intended direct access. 106 | You can make some management objects to switch the window layout. 107 | 108 | ### Access functions 109 | 110 | - `wlf:get-buffer` 111 | - `wlf:set-buffer` 112 | - `wlf:wset-live-p` 113 | 114 | ### Control functions 115 | 116 | - `wlf:refresh` 117 | - `wlf:reset-window-sizes` 118 | - `wlf:reset-init` 119 | - `wlf:show` 120 | - `wlf:hide` 121 | - `wlf:toggle` 122 | - `wlf:toggle-maximize` 123 | - `wlf:select` 124 | 125 | ### Layout hook 126 | 127 | After splitting windows, registered hook are called with one 128 | argument, the window management object. 129 | 130 | - `wlf:layout-hook-add` 131 | - `wlf:layout-hook-remove` 132 | 133 | ## License 134 | 135 | GPLv3 136 | -------------------------------------------------------------------------------- /window-layout.el: -------------------------------------------------------------------------------- 1 | ;;; window-layout.el --- window layout manager -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2024 SAKURAI Masashi 4 | 5 | ;; Author: SAKURAI Masashi 6 | ;; Version: 1.5 7 | ;; Keywords: window, layout 8 | 9 | ;; This program is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with this program. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; Split a frame or window into some windows according to a layout 25 | ;; recipe. 26 | 27 | ;;; Example code 28 | 29 | ;; ;; Layout function 30 | ;; ; -> three pane layout. 31 | ;; (setq wm ; <-- window management object 32 | ;; (wlf:layout 33 | ;; '(| (:left-size-ratio 0.3) 34 | ;; folder 35 | ;; (- (:upper-max-size 15) 36 | ;; summary 37 | ;; message)) 38 | ;; '((:name folder 39 | ;; :buffer "folder buffer") 40 | ;; (:name summary 41 | ;; :buffer "summary buffer") 42 | ;; (:name message 43 | ;; :buffer "message buffer") 44 | ;; ))) 45 | 46 | ;; ;; Window controlling 47 | ;; (wlf:show wm 'summary) 48 | ;; (wlf:hide wm 'summary) 49 | ;; (wlf:toggle wm 'summary) 50 | ;; (wlf:select wm 'summary) 51 | ;; (wlf:toggle-maximize wm 'summary) 52 | 53 | ;; ;; Window updating 54 | ;; (wlf:refresh wm) 55 | ;; (wlf:reset-window-sizes wm) 56 | ;; (wlf:reset-init wm) 57 | 58 | ;; ;; Accessing a buffer 59 | ;; (wlf:get-buffer wm 'summary) -> <#buffer object> 60 | ;; (wlf:set-buffer wm 'summary "*scratch*") 61 | 62 | ;; ;; Accessing a window 63 | ;; (wlf:get-window wm 'summary) 64 | 65 | ;; ;; Layout hook 66 | ;; (defun wlf:test-hook (wset) (message "HOOK : %s" wset)) 67 | ;; (wlf:layout-hook-add wm 'wlf:test-hook) 68 | ;; (wlf:layout-hook-remove wm 'wlf:test-hook) 69 | 70 | ;;; `wlf:layout' function 71 | 72 | ;; * Layout recipe: 73 | 74 | ;; ( (split type) (split option) 75 | ;; (left window name or recipe) 76 | ;; (right window name or recipe) ) 77 | 78 | ;; - : split vertically 79 | ;; | : split horizontally 80 | 81 | ;; split option (the prefix 'left' can be replaced by 'right', 'upper' and 'lower'.) 82 | ;; :left-size (column or row number) window size 83 | ;; :left-max-size (column or row number) if window size is larger than this value, the window is shrunken. 84 | ;; :left-size-ratio (0.0 - 1.0) window size ratio. the size of the other side is the rest. 85 | ;; 86 | ;; Note: 87 | ;; The split option can be omitted. 88 | ;; The size parameters, :size, :max-size and :size-ratio, are mutually 89 | ;; exclusive. The size of a window is related with one of the other 90 | ;; side window. So, if both side windows set size parameters, the 91 | ;; window size may not be adjusted as you write. 92 | 93 | ;; * Window options: 94 | 95 | ;; :name [*] the window name. 96 | ;; :buffer a buffer name or a buffer object to show the window. If nil or omitted, the current buffer remains. If symbol, it is evaluated as a global variable. 97 | ;; :default-hide (t/nil) if t, the window is hided initially. (default: nil) 98 | ;; :fix-size (t/nil) if t, when the windows are laid out again, the window size is remained. (default: nil) 99 | 100 | ;; * subwindow-p option: 101 | 102 | ;; If this option is not nil, this function splits the windows within 103 | ;; the current window. If this option is nil or omitted, this function 104 | ;; uses the entire space of the current frame. Because some user 105 | ;; actions and complicated window layouts may cause unexpected split 106 | ;; behaviors, it is easy to use the entire space of a frame. 107 | 108 | ;; * Return value (Window management object): 109 | 110 | ;; You should not access the management object directly, because it is not 111 | ;; intended direct access. 112 | ;; You can make some management objects to switch the window layout. 113 | 114 | ;; * Layout hook 115 | 116 | ;; After splitting windows, registered hook are called with one 117 | ;; argument, the window management object. 118 | 119 | 120 | 121 | ;;; Code: 122 | 123 | (eval-when-compile (require 'cl-lib)) 124 | 125 | (defmacro wlf:aif (test-form then-form &rest else-forms) 126 | (declare (debug (form form &rest form)) 127 | (indent 2)) 128 | `(let ((it ,test-form)) 129 | (if it ,then-form ,@else-forms))) 130 | 131 | (defmacro wlf:acond (&rest clauses) 132 | (declare (debug (&rest form))) 133 | (if (null clauses) nil 134 | (let ((cl1 (car clauses)) 135 | (sym (gensym))) 136 | `(let ((,sym ,(car cl1))) 137 | (if ,sym 138 | (let ((it ,sym)) ,@(cdr cl1)) 139 | (wlf:acond ,@(cdr clauses))))))) 140 | 141 | (defun wlf:current-first-line-point (window) 142 | "[internal] return the point at the beginning of the first line 143 | of the WINDOW." 144 | (when (windowp window) 145 | (with-selected-window window 146 | (with-current-buffer (window-buffer window) 147 | (save-excursion 148 | (move-to-window-line 0) 149 | (point)))))) 150 | 151 | (cl-defstruct wlf:wset 152 | "Window-set management structure 153 | 154 | recipe : an input recipe object. 155 | winfo-list : a list of window management structures. 156 | wholep : if non nil, this function uses whole frame window. 157 | layout-hook : if doing layout windows, these hooks are called. 158 | The hook function has one argument: wset object." 159 | recipe winfo-list wholep layout-hook) 160 | 161 | (cl-defstruct wlf:window 162 | "Window management structure 163 | 164 | name : a symbol of the window name. 165 | options : an option plist given by the recipe. 166 | shown : 'show/'hide. if 'hide, the window is not displayed. 167 | window : a window object. 168 | vertical : if the window is split vertically, the value is t. 169 | last-size : if the window is alive, the window size is saved before laying out. 170 | edges : a list of window edges returned by `window-edges'. 171 | first-line-point : the cursor position of the window. 172 | window-point : the scroll position of the window. 173 | (If more than 2 windows display the same buffer, 174 | the values of first-line-point and window-point may 175 | be different between windows.)" 176 | name options shown window vertical last-size edges first-line-point window-point) 177 | 178 | (defun wlf:window-shown-set (winfo i) 179 | "[internal] translate the argument: nil -> 'hide / t -> 'show" 180 | (setf (wlf:window-shown winfo) (if i 'show 'hide))) 181 | 182 | (defun wlf:window-shown-p (winfo) 183 | "[internal] Return t, if the window should be shown." 184 | (eq 'show (wlf:window-shown winfo))) 185 | 186 | (defun wlf:window-shown-toggle (winfo) 187 | "[internal] Toggle window displaying state." 188 | (setf (wlf:window-shown winfo) 189 | (if (and (wlf:window-shown-p winfo) 190 | (wlf:window-live-window winfo)) 191 | 'hide 'show))) 192 | 193 | (defun wlf:window-window-by-edge (winfo) 194 | "[internal] Return a window object which corresponding to WINFO. 195 | This function retrieves the window object from the edge position 196 | in current frame." 197 | (and (wlf:window-edges winfo) 198 | (cl-destructuring-bind 199 | (left top right bottom) (wlf:window-edges winfo) 200 | (let ((swin (window-at (+ 2 left) (+ 2 top)))) 201 | (and swin 202 | (cl-destructuring-bind 203 | (sl st sr sb) (window-edges swin) 204 | (if (and (equal left sl) (< (abs (- top st)) 3)) t 205 | (message "OLD:%S NEW:%S" 206 | (wlf:window-edges winfo) (window-edges swin)) nil)) 207 | swin))))) 208 | 209 | (defun wlf:window-live-window (winfo) 210 | "[internal] Return a window object if the window is not null and 211 | alive, return nil otherwise." 212 | (let ((win (wlf:window-window winfo))) 213 | (and (wlf:window-shown-p winfo) win (window-live-p win) win))) 214 | 215 | (defun wlf:window-size (winfo) 216 | "[internal] Return current window size." 217 | (let ((window (wlf:window-window winfo))) 218 | (cond 219 | ((wlf:window-vertical winfo) 220 | (window-height window)) 221 | (t 222 | (window-width window))))) 223 | 224 | (defmacro wlf:window-option-get (winfo option-key) 225 | "[internal] Return an option value from an option property list." 226 | `(plist-get (wlf:window-options ,winfo) ',option-key)) 227 | 228 | 229 | 230 | (defun wlf:clear-windows (winfo-list wholep) 231 | "[internal] Destroy windows and return the window object to 232 | start dividing." 233 | (cond 234 | (wholep ; using the whole area 235 | (delete-other-windows (get-largest-window)) 236 | (get-largest-window)) 237 | (t ; nested windows 238 | (let ((wins 239 | (cl-loop for i in winfo-list 240 | for win = (wlf:window-live-window i) 241 | if win 242 | collect win))) 243 | (if (> (length wins) 1) 244 | (cl-loop for w in (cdr wins) 245 | unless (one-window-p) 246 | do (delete-window w))) 247 | (or (car wins) (selected-window)))))) 248 | 249 | (defun wlf:get-winfo (name winfo-list) 250 | "[internal] Select a window info object from a winfo list." 251 | (wlf:aif 252 | (cl-loop for i in winfo-list 253 | when (eq (wlf:window-name i) name) 254 | return i) it 255 | (error "Window name %s is not found." name))) 256 | 257 | (defun wlf:build-windows-rec (recipe winfo-list) 258 | "[internal] Split the selected window with the recipe." 259 | (let* 260 | ((split-type (car recipe)) 261 | (split-action 262 | (cond 263 | ((eq '- split-type) 'split-window-vertically) 264 | ((eq '| split-type) 'split-window-horizontally) 265 | (t 'split-window-vertically))) 266 | (verticalp (eq 'split-window-vertically split-action)) 267 | (split-options (cadr recipe)) 268 | (recipe-nodes (cddr recipe)) 269 | (former-recipe (car recipe-nodes)) 270 | (latter-recipe (cadr recipe-nodes)) 271 | (latter-window (funcall split-action)) 272 | (former-window (selected-window))) 273 | 274 | (unless (window-live-p former-window) 275 | (error "Can not create a window (former-window is not live)")) 276 | (select-window former-window) 277 | (when (and split-options 278 | (plist-get split-options ':leftp)) 279 | (wlf:apply-split-options split-options verticalp)) 280 | (if (symbolp former-recipe) 281 | (let ((winfo (wlf:get-winfo former-recipe winfo-list))) 282 | (unless (wlf:window-shown winfo) 283 | (wlf:window-shown-set winfo (null (wlf:window-option-get winfo :default-hide)))) 284 | (setf (wlf:window-window winfo) former-window) 285 | (setf (wlf:window-vertical winfo) verticalp) 286 | (wlf:apply-winfo winfo)) 287 | (wlf:build-windows-rec former-recipe winfo-list)) 288 | 289 | (unless (window-live-p latter-window) 290 | (error "Can not create a window (latter-window is not live.)")) 291 | (select-window latter-window) 292 | (when (and split-options 293 | (plist-get split-options ':rightp)) 294 | (wlf:apply-split-options split-options verticalp)) 295 | (if (symbolp latter-recipe) 296 | (let ((winfo (wlf:get-winfo latter-recipe winfo-list))) 297 | (unless (wlf:window-shown winfo) 298 | (wlf:window-shown-set winfo (null (wlf:window-option-get winfo :default-hide)))) 299 | (setf (wlf:window-window winfo) latter-window) 300 | (setf (wlf:window-vertical winfo) (eq 'split-window-vertically split-action)) 301 | (wlf:apply-winfo winfo)) 302 | (wlf:build-windows-rec latter-recipe winfo-list)) 303 | )) 304 | 305 | (defun wlf:apply-split-options (split-options verticalp) 306 | "[internal] Apply split options to the current window." 307 | (let ((size (if verticalp 308 | (window-height) 309 | (window-width)))) 310 | (condition-case err 311 | (wlf:acond 312 | ((plist-get split-options ':max-size) 313 | (if (< it size) 314 | (wlf:window-shrink (selected-window) 315 | verticalp (- size it)))) 316 | ((plist-get split-options ':size) 317 | (wlf:window-resize (selected-window) verticalp it)) 318 | ((plist-get split-options ':size-ratio) 319 | (wlf:window-resize 320 | (selected-window) verticalp 321 | (truncate (* 2 size it))))) 322 | (error (message "wlf:warning : %s" err))))) 323 | 324 | (defun wlf:window-shrink (window verticalp shrink-size) 325 | "[internal] Shrink window size." 326 | (cond 327 | (verticalp 328 | (shrink-window shrink-size)) 329 | (t 330 | (shrink-window-horizontally shrink-size)))) 331 | 332 | (defun wlf:window-resize (window verticalp target-size) 333 | "[internal] Resize window." 334 | (with-selected-window window 335 | (cond 336 | (verticalp 337 | (let ((current-size (window-height window))) 338 | (shrink-window 339 | (- current-size target-size)))) 340 | (t 341 | (let ((current-size (window-width window))) 342 | (shrink-window-horizontally 343 | (- current-size target-size))))))) 344 | 345 | (defun wlf:apply-winfo (winfo) 346 | "[internal] Apply layout options to the current window." 347 | (if (not (wlf:window-shown-p winfo)) 348 | (delete-window (selected-window)) 349 | (let ((buffer (wlf:aif (wlf:window-option-get winfo :buffer) 350 | (get-buffer 351 | (if (symbolp it) 352 | (symbol-value it) it))))) 353 | (when (buffer-live-p buffer) 354 | (set-window-buffer (selected-window) buffer))))) 355 | 356 | (defun wlf:set-window-points (winfo) 357 | "[internal] Set the scroll position and cursor one which are recorded in WINFO." 358 | (let ((window (wlf:window-window winfo))) 359 | (when (window-live-p window) 360 | (with-selected-window window 361 | (wlf:aif (wlf:window-first-line-point winfo) 362 | (progn 363 | ;;(message "WLF:set-window-points: [%s] first-line: %i" (wlf:window-name winfo) it) 364 | (goto-char it) 365 | (recenter 0))) 366 | ;; Go to previously saved window point if point is shown or 367 | ;; end of buffer is shown (therefore the point is shown). In 368 | ;; the latter case, window point (`it') is also at the EOB 369 | ;; (therefore `<' check does not work). 370 | (wlf:aif (wlf:window-window-point winfo) 371 | (progn 372 | (let ((win-last-point (window-end nil t))) 373 | ;;(message "WLF:set-window-points: [%s] window-point: %i last-point: %i" (wlf:window-name winfo) it win-last-point) 374 | (goto-char (if (or (< it win-last-point) 375 | (= (point-max) win-last-point)) 376 | it 377 | (1- win-last-point)))))))))) 378 | 379 | (defun wlf:collect-window-edges (winfo-list) 380 | "[internal] At the end of window laying out, this function is 381 | called to collect window edges." 382 | (cl-loop for winfo in winfo-list 383 | if (wlf:window-live-window winfo) 384 | do 385 | (setf (wlf:window-edges winfo) 386 | (window-edges (wlf:window-window winfo))))) 387 | 388 | (defun wlf:calculate-last-window-sizes (winfo-list) 389 | "[internal] Calculate summations of the last window size: width and height. 390 | Return a cons cell, car is width and cdr is height." 391 | (cl-loop for winfo in winfo-list 392 | with width = 0 with height = 0 393 | for size = (wlf:window-last-size winfo) 394 | if size 395 | do (cond 396 | ((wlf:window-vertical winfo) 397 | (cl-incf height size)) 398 | (t 399 | (cl-incf width size))) 400 | finally return (cons width height))) 401 | 402 | (defun wlf:calculate-init-window-sizes (winfo-list) 403 | "[internal] Calculate summations of the initial window size: width and height. 404 | Return a cons cell, car is width and cdr is height." 405 | (cl-loop for winfo in winfo-list 406 | with width = 0 with height = 0 407 | for win = (wlf:window-live-window winfo) 408 | if win 409 | do (cond 410 | ((wlf:window-vertical winfo) 411 | (cl-incf height (window-height win))) 412 | (t 413 | (cl-incf width (window-width win)))) 414 | finally return (cons width height))) 415 | 416 | (defun wlf:restore-window-sizes (winfo-list) 417 | "[internal] Restore the window sizes those are modified by the user." 418 | ;;checking window layout modification 419 | (let* ((init-size (wlf:calculate-init-window-sizes winfo-list)) 420 | (last-size (wlf:calculate-last-window-sizes winfo-list)) 421 | (total-width (car init-size)) 422 | (total-height (cdr init-size)) 423 | (width-remainp (eql (car last-size) total-width)) 424 | (height-remainp (eql (cdr last-size) total-height))) 425 | ;;restore window size 426 | (cl-loop for winfo in winfo-list 427 | for win = (wlf:window-live-window winfo) 428 | for to-size = (wlf:window-last-size winfo) 429 | for verticalp = (wlf:window-vertical winfo) 430 | do 431 | (when (and win to-size 432 | (null (wlf:window-option-get winfo :fix-size)) 433 | (if verticalp 434 | (and height-remainp (not (eql to-size total-height))) 435 | (and width-remainp (not (eql to-size total-width))))) 436 | (wlf:window-resize win verticalp to-size))))) 437 | 438 | (defun wlf:make-winfo-list (wparams) 439 | "[internal] Return a list of window info objects." 440 | (cl-loop for p in wparams 441 | collect (make-wlf:window 442 | :name (plist-get p ':name) 443 | :options p))) 444 | 445 | (defun wlf:translate-recipe (recipe) 446 | "[internal] Translate split options recursively. 447 | :left-foo, :upper-foo --> :leftp t :foo 448 | :right-foo, :lower-foo --> :rightp t :foo 449 | " 450 | (if (or (symbolp recipe) (null recipe)) 451 | recipe 452 | (let* 453 | (split-options 454 | new-split-options 455 | (recipe-nodes 456 | (if (= 3 (length recipe)) 457 | (cdr recipe) 458 | (setq split-options (cadr recipe)) 459 | (cddr recipe)))) 460 | (when split-options 461 | (cl-loop for i in split-options 462 | do 463 | (if (symbolp i) 464 | (let* ((label-name (symbol-name i))) 465 | (wlf:acond 466 | ((string-match ":\\(left\\|upper\\)-" label-name) 467 | (setq label-name 468 | (concat ":" (substring label-name (match-end 0)))) 469 | (push ':leftp new-split-options) 470 | (push t new-split-options)) 471 | ((string-match ":\\(right\\|lower\\)-" label-name) 472 | (setq label-name 473 | (concat ":" (substring label-name (match-end 0)))) 474 | (push ':rightp new-split-options) 475 | (push t new-split-options))) 476 | (push (intern label-name) new-split-options)) 477 | (push i new-split-options)))) 478 | (list (car recipe) (nreverse new-split-options) 479 | (wlf:translate-recipe (car recipe-nodes)) 480 | (wlf:translate-recipe (cadr recipe-nodes)))))) 481 | 482 | (defun wlf:max-window-size-p (winfo) 483 | "[internal] If current window size is equal to the frame 484 | size (maximum window size), return t. Otherwise return nil." 485 | (let ((wsize (wlf:window-size winfo))) 486 | (cond 487 | ((wlf:window-vertical winfo) 488 | (>= wsize (1- (frame-height)))) 489 | (t 490 | (>= wsize (1- (frame-width))))))) 491 | 492 | (defun wlf:save-current-window-sizes (recipe winfo-list) 493 | "[internal] Save current window sizes and points, before clearing 494 | the windows. The saved sizes are used at `wlf:restore-window-sizes'. 495 | The saved points are used in `wlf:apply-winfo'." 496 | (cl-loop for winfo in winfo-list 497 | do (setf (wlf:window-last-size winfo) nil)) 498 | (wlf:aif 499 | (frame-parameter (selected-frame) 'wlf:recipe) 500 | (if (equal recipe it) 501 | (cl-loop for winfo in winfo-list do 502 | (let ((win (wlf:window-live-window winfo))) 503 | (setf (wlf:window-last-size winfo) 504 | (and win 505 | (wlf:max-window-size-p winfo) 506 | (wlf:window-size winfo)) 507 | (wlf:window-window-point winfo) 508 | (and win (window-point win)) 509 | (wlf:window-first-line-point winfo) 510 | (wlf:current-first-line-point win)))))) 511 | (set-frame-parameter (selected-frame) 'wlf:recipe recipe)) 512 | 513 | 514 | 515 | (defun wlf:layout (recipe window-params &optional subwindow-p) 516 | "Lay out windows and return a management object. 517 | RECIPE is a structure of splitting windows. 518 | WINDOW-PARAMS is a list of the window layout parameters. 519 | If SUBWINDOW-P is nil, this function uses the entire space of the current frame. 520 | If SUBWINDOW-P is non-nil, this function splits the windows within the current window. 521 | See the comment text to know the further information about parameters. 522 | " 523 | (wlf:layout-internal (wlf:no-layout recipe window-params subwindow-p))) 524 | 525 | (defun wlf:no-layout (recipe window-params &optional subwindow-p) 526 | "Just return a management object, does not change window 527 | layout. See the comment of `wlf:layout' function for arguments." 528 | (make-wlf:wset :recipe (wlf:translate-recipe recipe) 529 | :winfo-list (wlf:make-winfo-list window-params) 530 | :wholep (not subwindow-p))) 531 | 532 | (defmacro wlf:with-wset (wset &rest body) 533 | "Define local variables: recipe, winfo-list, wholep, layout-hook." 534 | (declare (debug (symbolp &rest form)) 535 | (indent 1)) 536 | `(let* 537 | ((recipe (wlf:wset-recipe wset)) 538 | (winfo-list (wlf:wset-winfo-list wset)) 539 | (wholep (wlf:wset-wholep wset)) 540 | (layout-hook (wlf:wset-layout-hook wset))) 541 | ,@body)) 542 | 543 | (defun wlf:layout-internal (wset &optional restore-window-size) 544 | "[internal] Lay out windows and return a management object. 545 | If RESTORE-WINDOW-SIZE is not nil, this function does not restore 546 | the current window size which can be modified by users." 547 | (wlf:maximize-info-clear) 548 | (wlf:with-wset wset 549 | (let ((last-wname (wlf:get-window-name wset (selected-window))) 550 | (last-buffer (current-buffer)) val) 551 | (wlf:save-current-window-sizes recipe winfo-list) 552 | (select-window (wlf:clear-windows winfo-list wholep)) 553 | (wlf:build-windows-rec recipe winfo-list) 554 | (mapc #'wlf:set-window-points winfo-list) 555 | (unless restore-window-size 556 | (wlf:restore-window-sizes winfo-list)) 557 | (wlf:collect-window-edges winfo-list) 558 | (setq val (make-wlf:wset :recipe recipe 559 | :winfo-list winfo-list 560 | :wholep wholep)) 561 | 562 | (cl-loop for h in (wlf:wset-layout-hook wset) 563 | do (funcall h wset)) 564 | 565 | (wlf:aif (or 566 | ;; Try to find active window by window name first: 567 | (ignore-errors (wlf:get-window wset last-wname)) 568 | ;; If not found, select the same buffer as before: 569 | (get-buffer-window last-buffer)) 570 | (select-window it)) 571 | val))) 572 | 573 | (defun wlf:layout-hook-add (wset func) 574 | "Add FUNC to layout-hook of the WSET, and return the layout-hook. 575 | The function FUNC should have one argument : wset object." 576 | (let ((hook (wlf:wset-layout-hook wset))) 577 | (unless (member func hook) 578 | (setf (wlf:wset-layout-hook wset) (cons func hook))) 579 | (wlf:wset-layout-hook wset))) 580 | 581 | (defun wlf:layout-hook-remove (wset func) 582 | "Remove FUNC from layout-hook of the WSET, and return the layout-hook." 583 | (let ((hook (wlf:wset-layout-hook wset))) 584 | (when (member func hook) 585 | (setf (wlf:wset-layout-hook wset) (remove func hook))) 586 | (wlf:wset-layout-hook wset))) 587 | 588 | (defun wlf:refresh (wset) 589 | "Refresh the window layout. WSET is a management object which 590 | is returned by `wlf:layout'." 591 | (wlf:layout-internal wset)) 592 | 593 | (defun wlf:reset-window-sizes (wset) 594 | "Reset the window sizes by window recipe parameters." 595 | (wlf:layout-internal wset t)) 596 | 597 | (defun wlf:reset-init (wset) 598 | "Reset the window sizes and display statuses by window recipe parameters." 599 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 600 | do (setf (wlf:window-shown winfo) nil)) 601 | (wlf:layout-internal wset t)) 602 | 603 | (defun wlf:show (wset &rest winfo-names) 604 | "Display the window. WSET is the management object which is 605 | returned by `wlf:layout'. WINFO-NAME is the window name which is 606 | defined by the argument of `wlf:layout'." 607 | (cl-loop for wn in winfo-names 608 | do 609 | (wlf:window-shown-set 610 | (wlf:get-winfo 611 | wn (wlf:wset-winfo-list wset)) t)) 612 | (wlf:layout-internal wset)) 613 | 614 | (defun wlf:hide (wset &rest winfo-names) 615 | "Hide the window. WSET is the management object which 616 | is returned by `wlf:layout'. WINFO-NAME is the window name which is 617 | defined by the argument of `wlf:layout'." 618 | (cl-loop for wn in winfo-names 619 | do 620 | (wlf:window-shown-set 621 | (wlf:get-winfo 622 | wn (wlf:wset-winfo-list wset)) nil)) 623 | (wlf:layout-internal wset)) 624 | 625 | (defun wlf:toggle (wset &rest winfo-names) 626 | "Toggle the window. WSET is the management object which 627 | is returned by `wlf:layout'. WINFO-NAME is the window name which is 628 | defined by the argument of `wlf:layout'." 629 | (cl-loop for wn in winfo-names 630 | do 631 | (wlf:window-shown-toggle 632 | (wlf:get-winfo wn (wlf:wset-winfo-list wset)))) 633 | (wlf:layout-internal wset)) 634 | 635 | (defun wlf:select (wset winfo-name) 636 | "Select the indicated window. WSET is the management object 637 | which is returned by `wlf:layout'. WINFO-NAME is the window name 638 | which is defined by the argument of `wlf:layout'. If the window 639 | is nil or deleted, no window is selected." 640 | (wlf:aif 641 | (wlf:window-live-window 642 | (wlf:get-winfo winfo-name (wlf:wset-winfo-list wset))) 643 | (select-window it))) 644 | 645 | (defun wlf:get-window (wset winfo-name) 646 | "Return the indicated window. WSET is the management object 647 | which is returned by `wlf:layout'. WINFO-NAME is the window name 648 | which is defined by the argument of `wlf:layout'. If the window 649 | is nil or deleted, return nil. Note that after the other window 650 | configuration is applied by `set-window-configuration', this 651 | function may return nil because the window configured by wlf is 652 | not alive. Since many functions calls `set-window-configuration', 653 | programs depend on `wlf:get-window' should watch invocations of 654 | `set-window-configuration'. 655 | 656 | See also `wlf:get-window-name'. It is approximately the inverse 657 | function of `wlf:get-window'. `wlf:get-window-name' converts 658 | window to window name." 659 | (wlf:window-live-window 660 | (wlf:get-winfo winfo-name (wlf:wset-winfo-list wset)))) 661 | 662 | (defun wlf:set-buffer (wset winfo-name buf &optional selectp) 663 | "Set the buffer on the window. WSET is the management object 664 | which is returned by `wlf:layout'. WINFO-NAME is the window name 665 | which is defined by the argument of `wlf:layout'. BUF is a buffer 666 | name or object to show in the window." 667 | (when (stringp buf) (setq buf (get-buffer buf))) 668 | (let* ((winfo 669 | (wlf:get-winfo 670 | winfo-name (wlf:wset-winfo-list wset))) 671 | (window (wlf:window-live-window winfo)) 672 | (curwin (selected-window))) 673 | (unless (buffer-live-p buf) 674 | (error "Buffer is dead. at wlf:set-buffer. (%s)" winfo-name)) 675 | (plist-put (wlf:window-options winfo) :buffer buf) 676 | (when (and window (not (eql (get-buffer buf) (window-buffer window)))) 677 | (set-window-buffer window buf)) 678 | (cond 679 | (selectp 680 | (select-window window)) 681 | ((active-minibuffer-window) 682 | (select-window (minibuffer-window)))) 683 | window)) 684 | 685 | (defun wlf:get-buffer (wset winfo-name) 686 | "Return the buffer object on the window. 687 | This function uses the structure data, not currently displayed 688 | window. WSET is the management object which is returned by 689 | `wlf:layout'. WINFO-NAME is the window name which is defined by 690 | the argument of `wlf:layout'." 691 | (wlf:window-option-get 692 | (wlf:get-winfo winfo-name (wlf:wset-winfo-list wset)) :buffer)) 693 | 694 | (defun wlf:window-name-p (wset winfo-name) 695 | "Return t if WINFO-NAME exists in WSET. Otherwise return nil." 696 | (if (wlf:get-winfo winfo-name (wlf:wset-winfo-list wset)) t 697 | nil)) 698 | 699 | (defun wlf:window-displayed-p (wset winfo-name) 700 | "Return t if the window of WINFO-NAME is displayed. Otherwise return nil." 701 | (wlf:aif (wlf:get-winfo winfo-name (wlf:wset-winfo-list wset)) 702 | (wlf:window-shown-p it) 703 | nil)) 704 | 705 | (defun wlf:wopts-replace-buffer (wopts buffer-alist) 706 | "Helper function for the argument of `wlf:layout'. This 707 | function replaces or adds buffer objects in the window options. 708 | WOPTS is a window option list. BUFFER-ALIST is an alist of pairs 709 | of a window name and a buffer object (or buffer name)." 710 | (cl-loop 711 | for pair in buffer-alist 712 | for name = (car pair) 713 | for buf = (cdr pair) 714 | for opts = (cl-loop 715 | for i in wopts 716 | if (eq (plist-get i ':name) name) 717 | return i) 718 | do (plist-put opts ':buffer buf)) 719 | wopts) 720 | 721 | (defun wlf:copy-windows (wset) 722 | "Return a copied wset object for `set-window-configuration' hacking." 723 | (make-wlf:wset 724 | :recipe (wlf:wset-recipe wset) 725 | :winfo-list (cl-loop for i in (wlf:wset-winfo-list wset) 726 | collect (wlf:copy-winfo i)) 727 | :wholep (wlf:wset-wholep wset) 728 | :layout-hook (wlf:wset-layout-hook wset))) 729 | 730 | (defun wlf:copy-winfo (winfo) 731 | "[internal] Return a shallow copied window object." 732 | (make-wlf:window 733 | :name (wlf:window-name winfo) 734 | :options (copy-sequence (wlf:window-options winfo)) 735 | :shown (wlf:window-shown winfo) 736 | :window (wlf:window-window winfo) 737 | :vertical (wlf:window-vertical winfo) 738 | :last-size (wlf:window-last-size winfo) 739 | :edges (wlf:window-edges winfo))) 740 | 741 | (defun wlf:maximize-info-get () 742 | "[internal] Return toggle-maximize info." 743 | (frame-parameter (selected-frame) 'wlf:maximize)) 744 | 745 | (defun wlf:maximize-info-set (val) 746 | "[internal] Set toggle-maximize info." 747 | (set-frame-parameter (selected-frame) 'wlf:maximize val)) 748 | 749 | (defun wlf:maximize-info-clear () 750 | "[internal] Clear toggle-maximize info." 751 | (wlf:maximize-info-set nil)) 752 | 753 | (defun wlf:collect-window-states (wset) 754 | "[internal] Collect current window states." 755 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 756 | collect (cons (wlf:window-name winfo) 757 | (wlf:window-shown-p winfo)))) 758 | 759 | (defun wlf:revert-window-states (wset states) 760 | "[internal] Revert window states whose are collected by 761 | `wlf:collect-window-states'." 762 | (cl-loop for s in states 763 | with winfo-list = (wlf:wset-winfo-list wset) 764 | for wname = (car s) 765 | for winfo = (wlf:get-winfo wname winfo-list) 766 | do (wlf:window-shown-set winfo (cdr s)))) 767 | 768 | (defun wlf:maximize-window-states (wset winfo-name) 769 | "[internal] Set show state at the WINFO-NAME window, set hide 770 | state at the other windows." 771 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 772 | if (eq (wlf:window-name winfo) winfo-name) 773 | do (wlf:window-shown-set winfo t) 774 | else 775 | do (wlf:window-shown-set winfo nil))) 776 | 777 | (defun wlf:toggle-maximize (wset winfo-name) 778 | "Toggle WINFO-NAME window maximizing." 779 | ;; structure : (wlf:maximize maximized-winfo-name states-list) 780 | (let ((prev-states (wlf:maximize-info-get)) 781 | (current-states (wlf:collect-window-states wset)) 782 | next-states) 783 | (cond 784 | (prev-states ; now maximizing 785 | (cond 786 | ((eq winfo-name (cadr prev-states)) ; revert windows 787 | (wlf:revert-window-states wset (caddr prev-states))) 788 | (t ; maximize other window 789 | (setf (cadr prev-states) winfo-name) 790 | (setq next-states prev-states) 791 | (wlf:maximize-window-states wset winfo-name)))) 792 | (t ; maximize a window 793 | (wlf:maximize-window-states wset winfo-name) 794 | (setq next-states 795 | (list 'wlf:maximize winfo-name current-states)))) 796 | (wlf:layout-internal wset) 797 | (wlf:maximize-info-set next-states))) 798 | 799 | (defun wlf:get-window-name (wset window) 800 | "Return the window name that is corresponding to the WINDOW object. 801 | If the WINDOW is not found, return nil. 802 | 803 | See also `wlf:get-window'. It is sort of the inverse function. 804 | It returns WINDOW by given name." 805 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 806 | for win = (wlf:window-window winfo) 807 | if (and win (window-live-p win) 808 | (eql win window)) 809 | return (wlf:window-name winfo))) 810 | 811 | (defun wlf:wset-live-p (wset) 812 | "Return t if WSET is valid, return nil otherwise." 813 | (let ((die-count 0) (shown-count 0)) 814 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 815 | for win = (wlf:window-window winfo) 816 | if (wlf:window-shown-p winfo) 817 | do 818 | (cl-incf shown-count) 819 | (unless (and win (window-live-p win)) 820 | (cl-incf die-count))) 821 | (cond 822 | ((= die-count 0) t) 823 | (t (cl-loop for winfo in (wlf:wset-winfo-list wset) 824 | for (left top right bottom) = (wlf:window-edges winfo) 825 | for sw = (window-at left top) 826 | with windows = nil 827 | do 828 | (when sw 829 | (cond 830 | ((memq sw windows) (cl-return nil)) 831 | (t (push sw windows)))) 832 | finally return t))))) 833 | 834 | (defun wlf:wset-clear-window-points (wset) 835 | "Clear the last preserved window-point and first-line-point slots for all windows. 836 | If users want change the window layout and re-use `wlf:wset' instance, 837 | this function should be called to forget wrong window-positions." 838 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 839 | do 840 | (setf (wlf:window-window-point winfo) nil 841 | (wlf:window-first-line-point winfo) nil))) 842 | 843 | (defun wlf:wset-fix-windows (wset) 844 | "Update window object instances with the current window configuration." 845 | (cl-loop for winfo in (wlf:wset-winfo-list wset) 846 | for win = (wlf:window-window winfo) 847 | do 848 | (cond 849 | ((wlf:window-shown-p winfo) ; should be displayed 850 | (cond 851 | (win ; the previous window object exists 852 | (let ((swin (wlf:window-window-by-edge winfo))) 853 | (cond 854 | (swin ; found a window object 855 | (setf (wlf:window-window winfo) swin 856 | (wlf:window-edges winfo) (window-edges swin))) 857 | (t ; found no window object 858 | (wlf:window-shown-set winfo nil) 859 | (setf (wlf:window-window winfo) nil 860 | (wlf:window-edges winfo) nil))))) 861 | (t ; no previous window object 862 | (wlf:window-shown-set winfo nil) 863 | (setf (wlf:window-window winfo) nil 864 | (wlf:window-edges winfo) nil)))) 865 | (t ; not displayed 866 | (setf (wlf:window-window winfo) nil 867 | (wlf:window-edges winfo) nil))))) 868 | 869 | 870 | 871 | ;;; test 872 | 873 | ;; (setq ss 874 | ;; (wlf:layout 875 | ;; '(| (:left-max-size 20) 876 | ;; folder 877 | ;; (- (:lower-size-ratio 0.6) 878 | ;; summary message)) 879 | ;; '((:name folder :buffer "*info*") 880 | ;; (:name summary :buffer "*Messages*") 881 | ;; (:name message :buffer "window-layout.el" :default-hide nil)))) 882 | 883 | ;; (setq dd 884 | ;; (wlf:no-layout 885 | ;; '(| (:left-size-ratio 0.33) 886 | ;; folder 887 | ;; (| (:left-size-ratio 0.5) 888 | ;; summary message)) 889 | ;; '((:name folder :buffer "*info*") 890 | ;; (:name summary :buffer "*Messages*") 891 | ;; (:name message :buffer "window-layout.el")))) 892 | 893 | ;; (setq ff (wlf:no-layout 894 | ;; '(| (:left-max-size 40) 895 | ;; (- (:upper-size-ratio 0.25) 896 | ;; files 897 | ;; (- (:upper-size-ratio 0.3) 898 | ;; history sub)) 899 | ;; (| (:right-max-size 30) 900 | ;; main imenu)) 901 | ;; '((:name main :buffer "window-layout.el") 902 | ;; (:name files :buffer "*scratch*") 903 | ;; (:name history :buffer "*Messages*") 904 | ;; (:name sub :buffer "*Info*") 905 | ;; (:name imenu :buffer "*info*" :default-hide t)))) 906 | 907 | ;; (wlf:show ss 'folder) 908 | ;; (wlf:hide ss 'folder) 909 | ;; (wlf:toggle-maximize ss 'message) 910 | ;; (wlf:toggle-maximize ss 'folder) 911 | ;; (wlf:toggle ss 'folder 'summary) 912 | ;; (wlf:toggle ss 'summary) 913 | ;; (wlf:reset-window-sizes ss) 914 | ;; (wlf:select ss 'summary) 915 | ;; (wlf:get-buffer ss 'message) 916 | ;; (wlf:set-buffer ss 'message "*scratch*") 917 | ;; (wlf:refresh ss) 918 | ;; (wlf:refresh dd) 919 | ;; (wlf:refresh ff) 920 | ;; (wlf:toggle ff 'imenu) 921 | ;; (wlf:reset-window-sizes ff) 922 | 923 | ;; (wlf:get-window-name ss (selected-window)) 924 | ;; (wlf:wset-live-p ss) 925 | ;; (wlf:wset-live-p ss 1) 926 | 927 | ;; (wlf:wopts-replace-buffer 928 | ;; '((:name folder :buffer "*info*") 929 | ;; (:name summary :buffer "*Messages*") 930 | ;; (:name message :buffer "window-layout.el" :default-hide nil)) 931 | ;; '((folder . "*Messages*") (summary . "*scratch*"))) 932 | 933 | ;; (defun wlf:test-hook (wset) (message "HOOK : %s" wset)) 934 | ;; (wlf:layout-hook-add ss 'wlf:test-hook) 935 | ;; (wlf:layout-hook-remove ss 'wlf:test-hook) 936 | 937 | (provide 'window-layout) 938 | ;;; window-layout.el ends here 939 | --------------------------------------------------------------------------------