1 ;;; wconf.el --- Minimal window layout manager -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
5 ;; Author: Ingo Lohmar <i.lohmar@gmail.com>
6 ;; URL: https://github.com/ilohmar/wconf
8 ;; Keywords: windows, frames, layout
9 ;; Package-Requires: ((emacs "24.4"))
11 ;; This file is part of GNU Emacs.
13 ;; This program is free software: you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
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 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
28 ;; See the file README.org
33 "Easily use several window configurations."
36 (defcustom wconf-change-config-function #'wconf-change-config-default
37 "Function called with current config whenever it is set."
40 (defcustom wconf-file (expand-file-name "wconf-window-configs.el"
42 "File used to save and load window configurations."
45 (defcustom wconf-fallback-buffer-name "*scratch*"
46 "Name of the buffer to substitute for buffers which are not available."
49 (defcustom wconf-no-configs-string "-----"
50 "String to use if there are no configurations at all."
53 (defcustom wconf-no-config-name "---"
54 "String to use for the empty window configuration."
57 ;; internal variables and helper functions
59 (defvar wconf--configs nil
60 "List of configurations; each item a list (active stored name).")
62 (defvar wconf--index nil
63 "Index of currently shown configuration. After clean and load
64 this can be nil although wconf--configs is not empty.")
66 (defvar wconf-string nil
67 "String representing information on the current configuration.")
71 (defsubst wconf--ensure-configs (&optional current)
72 (unless wconf--configs
73 (error "wconf: No window configurations"))
74 (when (and current (not wconf--index))
75 (error "wconf: No window configuration is currently used")))
77 (defsubst wconf--ensure-index (&optional index)
78 (unless (<= 0 index (1- (length wconf--configs)))
79 (error "wconf: No window configuration index %s" index)))
81 (defun wconf--current-config ()
82 (window-state-get (frame-root-window (selected-frame))
86 (nth index wconf--configs))
88 (defun wconf--to-string (index)
91 (number-to-string index)
92 (cl-caddr (wconf- index)))
93 (concat "-:" wconf-no-config-name)))
95 (defun wconf--update-info ()
96 (when (functionp wconf-change-config-function)
97 (funcall wconf-change-config-function
98 ;; both will be nil if no list
101 (car (wconf- wconf--index))))))
103 (defun wconf--update-active-config ()
105 (setf (car (wconf- wconf--index)) (wconf--current-config))))
107 (defun wconf--use-config (index)
108 (setq wconf--index index)
109 (window-state-put (car (wconf- wconf--index))
110 (frame-root-window (selected-frame))
112 (wconf--update-info))
114 (defun wconf--reset ()
115 "Remove all configurations."
116 (setq wconf--configs nil)
117 (setq wconf--index nil)
118 (wconf--update-info))
120 (defun wconf--copy (wc)
121 "Return a deep copy of WC, using `copy-tree'."
126 (defun wconf-change-config-default (index config)
127 "Update `wconf-string' to represent configuration CONFIG at
129 (setq wconf-string (if wconf--configs
130 (wconf--to-string index)
131 wconf-no-configs-string))
132 (force-mode-line-update))
134 (defun wconf-save (&optional filename)
135 "Save stored configurations in FILENAME, defaults to
138 (let ((filename (or filename wconf-file)))
139 (with-temp-file filename
140 (prin1 (mapcar #'cdr wconf--configs) ;-> (wc name)
142 (message "wconf: Save stored configurations in %s" filename)))
144 (defun wconf--sanitize-buffer (b)
145 (unless (get-buffer (cadr b))
146 (setf (cadr b) wconf-fallback-buffer-name
147 (cdr (assoc 'start b)) 1
148 (cdr (assoc 'point b)) 1
149 (cdr (assoc 'dedicated b)) nil)))
151 (defun wconf--sanitize-window-tree (node)
152 (let ((buf (assoc 'buffer node)))
153 (if buf ;in a leaf already
154 (wconf--sanitize-buffer buf)
157 (memq (car x) '(leaf vc hc)))
158 (wconf--sanitize-window-tree (cdr x))))
162 (defun wconf-load (&optional filename)
163 "Load stored configurations from FILENAME, defaults to
166 (let ((filename (or filename wconf-file)))
167 (unless (file-readable-p filename)
168 (error "wconf: Cannot read file %s" filename))
171 (insert-file-contents filename)
172 (goto-char (point-min))
175 (lambda (f) ;(wc name)
176 (wconf--sanitize-window-tree (car f))
177 (cons (wconf--copy (car f)) f))
178 (read (current-buffer)))))
179 (message "wconf: Load stored configurations from %s" filename))
180 (wconf--update-info))
182 ;; these functions affect the whole list of configs
185 (defun wconf-create (&optional new)
186 "Clone the current configuration or create a new \"empty\" one.
187 The new configuration is appended to the list and becomes active.
189 With optional prefix argument NEW, or if there are no
190 configurations yet, create a new configuration from the current
193 (wconf--update-active-config)
195 (append wconf--configs
197 (if (or new (not wconf--configs))
199 (message "wconf: Created new configuration %s"
200 (length wconf--configs))
201 (list (wconf--current-config)
202 (wconf--current-config)
204 (wconf--ensure-configs 'current)
205 (let ((wc (wconf- wconf--index)))
206 (message "wconf: Cloned configuration %s"
207 (wconf--to-string wconf--index))
208 (list (wconf--copy (car wc))
209 (wconf--copy (cadr wc))
211 (wconf--use-config (1- (length wconf--configs))))
214 "Kill current configuration."
216 (wconf--ensure-configs 'current)
217 (let ((old-string (wconf--to-string wconf--index)))
219 (append (butlast wconf--configs
220 (- (length wconf--configs) wconf--index))
222 (- (length wconf--configs) wconf--index 1))))
224 (wconf--use-config (if (< (1- (length wconf--configs)) wconf--index)
228 (wconf--update-info))
229 (message "wconf: Killed configuration %s" old-string)))
231 (defun wconf-swap (i j)
232 "Swap configurations at positions I and J."
235 (wconf--ensure-configs 'current) ;interactive? then want current config
238 (read-number "Swap current config with index: "))))
239 (wconf--ensure-configs)
240 (wconf--ensure-index i)
241 (wconf--ensure-index j)
242 (wconf--update-active-config)
243 (let ((wc (wconf- i)))
244 (setf (nth i wconf--configs) (wconf- j))
245 (setf (nth j wconf--configs) wc))
246 (when (memq wconf--index (list i j))
247 (wconf--use-config wconf--index))
248 (message "wconf: Swapped configurations %s and %s"
249 (number-to-string i) (number-to-string j)))
251 ;; manipulate single config
253 (defun wconf-rename (name)
254 "Rename current configuration to NAME."
257 (wconf--ensure-configs 'current)
259 (read-string "New window configuration name: "
260 (cl-caddr (wconf- wconf--index))))))
261 (wconf--ensure-configs 'current)
262 (setf (cl-caddr (wconf- wconf--index)) name)
263 (message "wconf: Renamed configuration to \"%s\"" name)
264 (wconf--update-info))
266 ;; interaction b/w stored and active configs
268 ;; these commands only make sense when there are wconf--configs, and
269 ;; after wconf--index has become non-nil
271 (defsubst wconf--store (wc)
272 (setf (cadr wc) (wconf--copy (car wc))))
274 (defsubst wconf--restore (wc)
275 (setf (car wc) (wconf--copy (cadr wc))))
277 (defun wconf-store ()
278 "Store currently active configuration."
280 (wconf--ensure-configs 'current)
281 (wconf--update-active-config)
282 (wconf--store (wconf- wconf--index))
283 (message "wconf: Stored configuration %s" (wconf--to-string wconf--index)))
285 (defun wconf-store-all ()
286 "Store all active configurations."
288 (wconf--ensure-configs 'current)
289 (wconf--update-active-config)
290 (mapc #'wconf--store wconf--configs)
291 (message "wconf: Stored all configurations"))
293 (defun wconf-restore ()
294 "Restore stored configuration."
296 (wconf--ensure-configs 'current)
297 (wconf--restore (wconf- wconf--index))
298 (wconf--use-config wconf--index)
299 (message "wconf: Restored configuration %s" (wconf--to-string wconf--index)))
301 (defun wconf-restore-all ()
302 "Restore all stored configurations."
304 (wconf--ensure-configs 'current)
305 (mapc #'wconf--restore wconf--configs)
306 (wconf--use-config wconf--index)
307 (message "wconf: Restored all configurations"))
311 (defun wconf-switch-to-config (index &optional force)
312 "Change to current config INDEX."
314 (wconf--ensure-configs)
315 (let ((index (or index
316 (read-number "Switch to config number: "))))
317 (wconf--ensure-index index)
318 ;; remember active config (w/o name etc)
319 (wconf--update-active-config)
320 ;; maybe use new configuration
321 (if (and (eq wconf--index index)
323 (message "wconf: Nothing to do")
324 (wconf--use-config index)
325 (message "wconf: Switched to configuration %s"
326 (wconf--to-string index)))))
328 (defun wconf-use-previous ()
329 "Switch to previous window configuration."
331 (wconf--ensure-configs)
332 (wconf-switch-to-config (mod (1- (or wconf--index 1))
333 (length wconf--configs))))
335 (defun wconf-use-next ()
336 "Switch to next window configuration."
338 (wconf--ensure-configs)
339 (wconf-switch-to-config (mod (1+ (or wconf--index -1))
340 (length wconf--configs))))
343 ;;; wconf.el ends here