commit | author | age
|
5cb5f7
|
1 |
;;; workgroups2.el --- New workspaces for Emacs |
C |
2 |
;;; -*- coding: utf-8; lexical-binding: t -*- |
|
3 |
;; |
|
4 |
;; Copyright (C) 2013-2014 Sergey Pashinin |
|
5 |
;; Copyright (C) 2010-2011 tlh |
|
6 |
;; |
|
7 |
;; Author: Sergey Pashinin <sergey at pashinin dot com> |
|
8 |
;; Keywords: session management window-configuration persistence |
|
9 |
;; Package-Version: 20141102.1922 |
|
10 |
;; Homepage: https://github.com/pashinin/workgroups2 |
|
11 |
;; Version: 1.2.0 |
|
12 |
;; Package-Requires: ((cl-lib "0.4") (dash "2.8.0") (anaphora "1.0.0") (f "0.17")) |
|
13 |
;; |
|
14 |
;; This program is free software; you can redistribute it and/or modify |
|
15 |
;; it under the terms of the GNU General Public License as published by |
|
16 |
;; the Free Software Foundation; either version 2 of the License, or (at |
|
17 |
;; your option) any later version. |
|
18 |
;; |
|
19 |
;; This program is distributed in the hope that it will be useful, but |
|
20 |
;; WITHOUT ANY WARRANTY; without even the implied warranty of |
|
21 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
22 |
;; General Public License for more details. |
|
23 |
;; |
|
24 |
;; You should have received a copy of the GNU General Public License |
|
25 |
;; along with this program; if not, write to the Free Software |
|
26 |
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
|
27 |
;; USA |
|
28 |
;; |
|
29 |
;;; Commentary: |
|
30 |
;; |
|
31 |
;; Workgroups2 is an Emacs session manager. It is based on the |
|
32 |
;; experimental branch of the original "workgroups" extension. |
|
33 |
;; |
|
34 |
;; If you find a bug - please post it here: |
|
35 |
;; https://github.com/pashinin/workgroups2/issues |
|
36 |
;; |
|
37 |
;; |
|
38 |
;; Install |
|
39 |
;; ---------------------- |
|
40 |
;; See the README.md file at: https://github.com/pashinin/workgroups2 |
|
41 |
;; Add the lines below to your .emacs configuration. |
|
42 |
;; |
|
43 |
;; (require 'workgroups2) |
|
44 |
;; |
|
45 |
;; <settings here> |
|
46 |
;; |
|
47 |
;; (workgroups-mode 1) ; put this one at the bottom of .emacs |
|
48 |
;; |
|
49 |
;; |
|
50 |
;; Configure |
|
51 |
;; ---------------------- |
|
52 |
;; ;; Change prefix key (before activating WG) |
|
53 |
;; (setq wg-prefix-key (kbd "C-c z")) |
|
54 |
;; |
|
55 |
;; ;; Change workgroups session file |
|
56 |
;; (setq wg-session-file "~/.emacs.d/.emacs_workgroups" |
|
57 |
;; |
|
58 |
;; ;; Set your own keyboard shortcuts to reload/save/switch WG: |
|
59 |
;; (global-set-key (kbd "<pause>") 'wg-reload-session) |
|
60 |
;; (global-set-key (kbd "C-S-<pause>") 'wg-save-session) |
|
61 |
;; (global-set-key (kbd "s-z") 'wg-switch-to-workgroup) |
|
62 |
;; (global-set-key (kbd "s-/") 'wg-switch-to-previous-workgroup) |
|
63 |
;; |
|
64 |
;; |
|
65 |
;; Use |
|
66 |
;; ---------------------- |
|
67 |
;; Most commands start with prefix `wg-prefix-key'. |
|
68 |
;; You can change it before activating workgroups. |
|
69 |
;; By default prefix is: "C-c z" |
|
70 |
;; |
|
71 |
;; <prefix> <key> |
|
72 |
;; |
|
73 |
;; <prefix> c - create workgroup |
|
74 |
;; <prefix> A - rename workgroup |
|
75 |
;; <prefix> k - kill workgroup |
|
76 |
;; <prefix> v - switch to workgroup |
|
77 |
;; <prefix> C-s - save session |
|
78 |
;; <prefix> C-f - load session |
|
79 |
;; |
|
80 |
;; |
|
81 |
;; Help |
|
82 |
;; ---------------------- |
|
83 |
;; Type "<prefix> ?" for more help |
|
84 |
;; |
|
85 |
;; See also: https://github.com/pashinin/workgroups2/wiki |
|
86 |
;; |
|
87 |
;;; Code: |
|
88 |
|
|
89 |
(require 'cl-lib) |
|
90 |
(require 'f) |
|
91 |
(require 'dash) |
|
92 |
(require 'ring) |
|
93 |
(require 'anaphora) |
|
94 |
|
|
95 |
|
|
96 |
(defconst wg-version "1.2.0" "Current version of Workgroups.") |
|
97 |
|
|
98 |
(defgroup workgroups nil |
|
99 |
"Workgroups for Emacs -- Emacs session manager" |
|
100 |
:group 'convenience) |
|
101 |
|
|
102 |
(defcustom wg-session-file "~/.emacs_workgroups" |
|
103 |
"Default filename to be used to save workgroups." |
|
104 |
:type 'file |
|
105 |
:group 'workgroups) |
|
106 |
(defvaralias 'wg-default-session-file 'wg-session-file) |
|
107 |
|
|
108 |
(defcustom wg-prefix-key (kbd "C-c z") |
|
109 |
"Workgroups' prefix key. |
|
110 |
Setting this variable requires that `workgroups-mode' be turned |
|
111 |
off and then on again to take effect." |
|
112 |
:type 'string |
|
113 |
:group 'workgroups) |
|
114 |
|
|
115 |
(defvar workgroups-mode-map nil "Workgroups Mode's keymap.") |
|
116 |
|
|
117 |
(defvar wg-incorrectly-restored-bufs nil |
|
118 |
"FIXME: docstring this.") |
|
119 |
;; TODO: check it on switching WG |
|
120 |
|
|
121 |
(defvar wg-record-incorrectly-restored-bufs nil |
|
122 |
"FIXME: docstring this.") |
|
123 |
|
|
124 |
(defvar wg-log-level 1 |
|
125 |
"Use later. |
|
126 |
0 means no messages at all (for tests)") |
|
127 |
|
|
128 |
(defcustom wg-emacs-exit-save-behavior 'save |
|
129 |
"Determines save behavior on Emacs exit. |
|
130 |
|
|
131 |
`ask' Ask the user whether to save if there are unsaved changes |
|
132 |
`save' Call `wg-save-session' when there are unsaved changes |
|
133 |
Anything else Exit Emacs without saving changes" |
|
134 |
:type 'symbol |
|
135 |
:group 'workgroups) |
|
136 |
|
|
137 |
(defcustom wg-workgroups-mode-exit-save-behavior 'save |
|
138 |
"Determines save behavior on `workgroups-mode' exit. |
|
139 |
|
|
140 |
`ask' Ask the user whether to saveif there are unsaved changes |
|
141 |
`save' Call `wg-save-session' when there are unsaved changes |
|
142 |
Anything else Exit `workgroups-mode' without saving changes" |
|
143 |
:type 'symbol |
|
144 |
:group 'workgroups) |
|
145 |
|
|
146 |
(defcustom wg-session-load-on-start (not (daemonp)) |
|
147 |
"Load a session file on Workgroups start. |
|
148 |
Don't do it with Emacs --daemon option." |
|
149 |
:type 'boolean |
|
150 |
:group 'workgroups) |
|
151 |
(defvaralias 'wg-use-default-session-file 'wg-session-load-on-start) |
|
152 |
|
|
153 |
(defcustom workgroups-mode nil |
|
154 |
"Non-nil if Workgroups mode is enabled." |
|
155 |
:set 'custom-set-minor-mode |
|
156 |
:initialize 'custom-initialize-default |
|
157 |
:group 'workgroups |
|
158 |
:type 'boolean) |
|
159 |
|
|
160 |
(defcustom wg-first-wg-name "First workgroup" |
|
161 |
"Title of the first workgroup created." |
|
162 |
:type 'string |
|
163 |
:group 'workgroups) |
|
164 |
|
|
165 |
(defcustom wg-modeline-string " wg" |
|
166 |
"Appears in modeline." |
|
167 |
:type 'string |
|
168 |
:group 'workgroups) |
|
169 |
|
|
170 |
(defcustom wg-mode-line-display-on (not (featurep 'powerline)) |
|
171 |
"Toggles Workgroups' mode-line display." |
|
172 |
:type 'boolean |
|
173 |
:group 'workgroups |
|
174 |
:set (lambda (sym val) |
|
175 |
(custom-set-default sym val) |
|
176 |
(force-mode-line-update))) |
|
177 |
|
|
178 |
(defcustom wg-mode-line-use-faces nil |
|
179 |
"Non-nil means use faces in the mode-line display. |
|
180 |
It can be tricky to choose faces that are visible in both active |
|
181 |
and inactive mode-lines, so this feature defaults to off." |
|
182 |
:type 'boolean |
|
183 |
:group 'workgroups) |
|
184 |
|
|
185 |
(defcustom wg-mode-line-decor-left-brace "(" |
|
186 |
"String displayed at the left of the mode-line display." |
|
187 |
:type 'string |
|
188 |
:group 'workgroups) |
|
189 |
|
|
190 |
(defcustom wg-mode-line-decor-right-brace ")" |
|
191 |
"String displayed at the right of the mode-line display." |
|
192 |
:type 'string |
|
193 |
:group 'workgroups) |
|
194 |
|
|
195 |
(defcustom wg-mode-line-decor-divider ":" |
|
196 |
"String displayed between elements of the mode-line display." |
|
197 |
:type 'string |
|
198 |
:group 'workgroups) |
|
199 |
|
|
200 |
(defcustom wg-mode-line-decor-window-dedicated |
|
201 |
#("#" 0 1 (help-echo "This window is dedicated to its buffer.")) |
|
202 |
"Indicates that the window is dedicated to its buffer." |
|
203 |
:type 'string |
|
204 |
:group 'workgroups) |
|
205 |
|
|
206 |
(defcustom wg-mode-line-decor-window-undedicated |
|
207 |
#("-" 0 1 (help-echo "This window is not dedicated to its buffer.")) |
|
208 |
"Indicates that the window is not dedicated to its buffer." |
|
209 |
:type 'string |
|
210 |
:group 'workgroups) |
|
211 |
|
|
212 |
(defcustom wg-mode-line-decor-session-modified |
|
213 |
#("*" 0 1 (help-echo "The session is modified")) |
|
214 |
"Indicates that the session is modified." |
|
215 |
:type 'string |
|
216 |
:group 'workgroups) |
|
217 |
|
|
218 |
(defcustom wg-mode-line-decor-session-unmodified |
|
219 |
#("-" 0 1 (help-echo "The session is unmodified")) |
|
220 |
"Indicates that the session is unmodified." |
|
221 |
:type 'string |
|
222 |
:group 'workgroups) |
|
223 |
|
|
224 |
(defcustom wg-mode-line-decor-workgroup-modified |
|
225 |
#("*" 0 1 (help-echo "The current workgroup is modified")) |
|
226 |
"Indicates that the current workgroup is modified." |
|
227 |
:type 'string |
|
228 |
:group 'workgroups) |
|
229 |
|
|
230 |
(defcustom wg-mode-line-decor-workgroup-unmodified |
|
231 |
#("-" 0 1 (help-echo "The current workgroup is unmodified")) |
|
232 |
"Indicates that the current workgroup is unmodified." |
|
233 |
:type 'string |
|
234 |
:group 'workgroups) |
|
235 |
|
|
236 |
(defcustom wg-load-last-workgroup t |
|
237 |
"Load last active (not first) workgroup from all your workgroups if it exists." |
|
238 |
:group 'workgroups |
|
239 |
:type 'boolean) |
|
240 |
|
|
241 |
(defcustom wg-control-frames t |
|
242 |
"Save/restore frames." |
|
243 |
:group 'workgroups |
|
244 |
:type 'boolean) |
|
245 |
|
|
246 |
(defcustom workgroups-mode-hook nil |
|
247 |
"Hook run when `workgroups-mode' is turned on." |
|
248 |
:type 'hook |
|
249 |
:group 'workgroups) |
|
250 |
|
|
251 |
(defcustom workgroups-mode-exit-hook nil |
|
252 |
"Hook run when `workgroups-mode' is turned off." |
|
253 |
:type 'hook |
|
254 |
:group 'workgroups) |
|
255 |
|
|
256 |
(defcustom wg-before-switch-to-workgroup-hook nil |
|
257 |
"Hook run by `wg-switch-to-workgroup'." |
|
258 |
:type 'hook |
|
259 |
:group 'workgroups) |
|
260 |
|
|
261 |
(defcustom wg-after-switch-to-workgroup-hook nil |
|
262 |
"Hook run by `wg-switch-to-workgroup'." |
|
263 |
:type 'hook |
|
264 |
:group 'workgroups) |
|
265 |
(define-obsolete-variable-alias 'wg-switch-to-workgroup-hook 'wg-after-switch-to-workgroup-hook) |
|
266 |
|
|
267 |
(defcustom wg-pre-window-configuration-change-hook nil |
|
268 |
"Hook run before any function that triggers `window-configuration-change-hook'." |
|
269 |
:type 'hook |
|
270 |
:group 'workgroups) |
|
271 |
|
|
272 |
(defcustom wg-open-this-wg nil |
|
273 |
"Try to open this workgroup on start. |
|
274 |
If nil - nothing happens." |
|
275 |
:type 'string |
|
276 |
:group 'workgroups) |
|
277 |
|
|
278 |
|
|
279 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
280 |
;; |
|
281 |
;; FIXME: |
|
282 |
;; |
|
283 |
;; Only set `wg-workgroup-base-wconfig' on `wg-save-session-as' or |
|
284 |
;; `delete-frame' and only with the most recently changed working-wconfig. |
|
285 |
;; Then, since it's not overwritten on every call to |
|
286 |
;; `wg-workgroup-working-wconfig', its restoration can be retried after manually |
|
287 |
;; recreating buffers that couldn't be restored. So it takes over the |
|
288 |
;; 'incorrect restoration' portion of the base wconfig's duty. All that leaves |
|
289 |
;; to base wconfigs is that they're a saved wconfig the user felt was important. |
|
290 |
;; So why not allow more of of them? A workgroup could stash an unlimited |
|
291 |
;; number of wconfigs. |
|
292 |
;; |
|
293 |
;; TODO: |
|
294 |
;; |
|
295 |
;; * Write new commands for restoring stashed wconfigs |
|
296 |
;; |
|
297 |
;; * Add this message on improper restoration of `base-wconfig': |
|
298 |
;; |
|
299 |
;; "Unable to restore 'buf1', 'buf2'... Hit C-whatever to retry after |
|
300 |
;; manually recreating these buffers." |
|
301 |
;; |
|
302 |
;; |
|
303 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
304 |
|
|
305 |
|
|
306 |
;; TODO: possibly add `buffer-file-coding-system', `text-scale-mode-amount' |
|
307 |
(defcustom wg-buffer-local-variables-alist |
|
308 |
`((major-mode nil wg-deserialize-buffer-major-mode) |
|
309 |
(mark-ring wg-serialize-buffer-mark-ring wg-deserialize-buffer-mark-ring) |
|
310 |
(left-fringe-width nil nil) |
|
311 |
(right-fringe-width nil nil) |
|
312 |
(fringes-outside-margins nil nil) |
|
313 |
(left-margin-width nil nil) |
|
314 |
(right-margin-width nil nil) |
|
315 |
(vertical-scroll-bar nil nil)) |
|
316 |
"Alist mapping buffer-local variable symbols to serdes functions. |
|
317 |
|
|
318 |
The `car' of each entry should be a buffer-local variable symbol. |
|
319 |
|
|
320 |
The `cadr' of the entry should be either nil or a function of no |
|
321 |
arguments. If nil, the variable's value is used as-is, and |
|
322 |
should have a readable printed representation. If a function, |
|
323 |
`funcall'ing it should yield a serialization of the value of the |
|
324 |
variable. |
|
325 |
|
|
326 |
The `caddr' of the entry should be either nil or a function of |
|
327 |
one argument. If nil, the serialized value from above is |
|
328 |
assigned to the variable as-is. It a function, `funcall'ing it |
|
329 |
on the serialized value from above should do whatever is |
|
330 |
necessary to properly restore the original value of the variable. |
|
331 |
For example, in the case of `major-mode' it should funcall the |
|
332 |
value (a major-mode function symbol) rather than just assigning |
|
333 |
it to `major-mode'." |
|
334 |
:type 'alist |
|
335 |
:group 'workgroups) |
|
336 |
|
|
337 |
|
|
338 |
(defcustom wg-nowg-string "No workgroups" |
|
339 |
"Display this string if there are no workgroups and |
|
340 |
`wg-display-nowg' is t." |
|
341 |
:type 'string |
|
342 |
:group 'workgroups) |
|
343 |
|
|
344 |
(defcustom wg-display-nowg nil |
|
345 |
"Display something if there are no workgroups." |
|
346 |
:type 'boolean |
|
347 |
:group 'workgroups) |
|
348 |
|
|
349 |
(defcustom wg-restore-remote-buffers t |
|
350 |
"Restore buffers that get \"t\" with `file-remote-p'." |
|
351 |
:type 'boolean |
|
352 |
:group 'workgroups) |
|
353 |
|
|
354 |
(defcustom wg-restore-frame-position t |
|
355 |
"Non-nil means restore frame position on workgroup restore." |
|
356 |
:type 'boolean |
|
357 |
:group 'workgroups) |
|
358 |
|
|
359 |
(defcustom wg-restore-scroll-bars t |
|
360 |
"Non-nil means restore scroll-bar settings on workgroup restore." |
|
361 |
:type 'boolean |
|
362 |
:group 'workgroups) |
|
363 |
|
|
364 |
(defcustom wg-restore-fringes t |
|
365 |
"Non-nil means restore fringe settings on workgroup restore." |
|
366 |
:type 'boolean |
|
367 |
:group 'workgroups) |
|
368 |
|
|
369 |
(defcustom wg-restore-margins t |
|
370 |
"Non-nil means restore margin settings on workgroup restore." |
|
371 |
:type 'boolean |
|
372 |
:group 'workgroups) |
|
373 |
|
|
374 |
(defcustom wg-restore-point t |
|
375 |
"Non-nil means restore `point' on workgroup restore. |
|
376 |
This is included mainly so point restoration can be suspended |
|
377 |
during `wg-morph' -- you probably want this non-nil." |
|
378 |
:type 'boolean |
|
379 |
:group 'workgroups) |
|
380 |
|
|
381 |
(defcustom wg-restore-point-max t |
|
382 |
"Controls point restoration when point is at `point-max'. |
|
383 |
If `point' is at `point-max' when a wconfig is created, put |
|
384 |
`point' back at `point-max' when the wconfig is restored, even if |
|
385 |
`point-max' has increased in the meantime. This is useful in, |
|
386 |
say, irc buffers where `point-max' is constantly increasing." |
|
387 |
:type 'boolean |
|
388 |
:group 'workgroups) |
|
389 |
|
|
390 |
(defcustom wg-restore-mark t |
|
391 |
"Non-nil means restore mark data on workgroup restore." |
|
392 |
:type 'boolean |
|
393 |
:group 'workgroups) |
|
394 |
|
|
395 |
(defcustom wg-restore-window-dedicated-p t |
|
396 |
"Non-nil means restore `window-dedicated-p' on workgroup restore." |
|
397 |
:type 'boolean |
|
398 |
:group 'workgroups) |
|
399 |
|
|
400 |
(defcustom wg-remember-frame-for-each-wg nil |
|
401 |
"When switching workgroups - restore frame parameters for each workgroup. |
|
402 |
|
|
403 |
When nil - save/restore frame parameters to/from the first workgroup." |
|
404 |
:type 'boolean |
|
405 |
:group 'workgroups) |
|
406 |
|
|
407 |
|
|
408 |
(defcustom wg-wconfig-undo-list-max 20 |
|
409 |
"Number of past window configs to retain for undo." |
|
410 |
:type 'integer |
|
411 |
:group 'workgroups) |
|
412 |
|
|
413 |
(defcustom wg-wconfig-kill-ring-max 20 |
|
414 |
"Maximum length of the `wg-wconfig-kill-ring'." |
|
415 |
:type 'integer |
|
416 |
:group 'workgroups) |
|
417 |
|
|
418 |
(defvar wg-wconfig-kill-ring nil |
|
419 |
"Ring of killed or kill-ring-saved wconfigs.") |
|
420 |
|
|
421 |
(defvar wg-buffer-uid nil |
|
422 |
"Symbol for the current buffer's wg-buf's uid. |
|
423 |
Every Workgroups buffer object (wg-buf) has a uid. When |
|
424 |
Workgroups creates or encounters an Emacs buffer object |
|
425 |
corresponding to a wg-buf, it tags it with the wg-buf's uid to |
|
426 |
unambiguously pair the two.") |
|
427 |
(make-variable-buffer-local 'wg-buffer-uid) |
|
428 |
|
|
429 |
(defcustom wg-flag-modified t |
|
430 |
"Show \"modified\" flags in modeline." |
|
431 |
:type 'boolean |
|
432 |
:group 'workgroups |
|
433 |
:set (lambda (sym val) |
|
434 |
(custom-set-default sym val) |
|
435 |
(force-mode-line-update))) |
|
436 |
|
|
437 |
|
|
438 |
|
|
439 |
(defvar wg-window-configuration-changed nil |
|
440 |
"Flag set by `window-configuration-change-hook'.") |
|
441 |
|
|
442 |
(defvar wg-already-updated-working-wconfig nil |
|
443 |
"Flag set by `wg-update-working-wconfig-hook'.") |
|
444 |
|
|
445 |
(defvar wg-undoify-window-configuration-change t |
|
446 |
"Should windows undo info be updated or not. |
|
447 |
When you change window configuration.") |
|
448 |
|
|
449 |
(defvar wg-current-workgroup nil "Bound to the current workgroup.") |
|
450 |
|
|
451 |
(defvar wg-window-min-width 2 |
|
452 |
"Bound to `window-min-width' when restoring wtrees.") |
|
453 |
|
|
454 |
(defvar wg-window-min-height 1 |
|
455 |
"Bound to `window-min-height' when restoring wtrees.") |
|
456 |
|
|
457 |
(defvar wg-window-min-pad 2 |
|
458 |
"Added to `wg-window-min-foo' to produce the actual minimum window size.") |
|
459 |
|
|
460 |
(defvar wg-actual-min-width (+ wg-window-min-width wg-window-min-pad) |
|
461 |
"Actual minimum window width when creating windows.") |
|
462 |
|
|
463 |
(defvar wg-actual-min-height (+ wg-window-min-height wg-window-min-pad) |
|
464 |
"Actual minimum window height when creating windows.") |
|
465 |
|
|
466 |
(defvar wg-min-edges `(0 0 ,wg-actual-min-width ,wg-actual-min-height) |
|
467 |
"Smallest allowable edge list of windows created by Workgroups.") |
|
468 |
|
|
469 |
(defvar wg-null-edges '(0 0 0 0) "Null edge list.") |
|
470 |
|
|
471 |
(defvar wg-window-tree-selected-window nil |
|
472 |
"Used during wconfig restoration to hold the selected window.") |
|
473 |
|
|
474 |
(defvar wg-buffer-workgroup nil |
|
475 |
"A workgroup in which this buffer most recently appeared. |
|
476 |
Buffer-local.") |
|
477 |
(make-variable-buffer-local 'wg-buffer-workgroup) |
|
478 |
|
|
479 |
(defcustom wg-default-buffer "*scratch*" |
|
480 |
"Show this in case everything else fails. |
|
481 |
When a buffer can't be restored, when creating a blank wg." |
|
482 |
:type 'string |
|
483 |
:group 'workgroups) |
|
484 |
|
|
485 |
|
|
486 |
;; |
|
487 |
;; Crazy stuff... |
|
488 |
;; |
|
489 |
(defcustom wg-associate-blacklist (list "*helm mini*" "*Messages*" "*scratch*" |
|
490 |
"*helm action*") |
|
491 |
"Do not autoassociate these buffers." |
|
492 |
:type 'list |
|
493 |
:group 'workgroups) |
|
494 |
|
|
495 |
(defconst wg-buffer-list-original (symbol-function 'buffer-list)) |
|
496 |
(fset 'wg-buffer-list-emacs wg-buffer-list-original) |
|
497 |
|
|
498 |
(defun buffer-list (&optional frame) |
|
499 |
"Redefinition of `buffer-list'. |
|
500 |
Pass FRAME to it. |
|
501 |
Remove file and dired buffers that are not associated with workgroup." |
|
502 |
(let ((res (wg-buffer-list-emacs frame)) |
|
503 |
(wg-buf-uids (wg-workgroup-associated-buf-uids))) |
|
504 |
(--remove (and (or (buffer-file-name it) |
|
505 |
(eq (buffer-local-value 'major-mode it) 'dired-mode)) |
|
506 |
;;(not (member b wg-buffers)) |
|
507 |
(not (member (wg-buffer-uid-or-add it) wg-buf-uids))) |
|
508 |
res))) |
|
509 |
|
|
510 |
(defconst wg-buffer-list-function (symbol-function 'buffer-list)) |
|
511 |
(fset 'buffer-list wg-buffer-list-original) |
|
512 |
|
|
513 |
;; locate-dominating-file |
|
514 |
(defcustom wg-mess-with-buffer-list nil |
|
515 |
"Redefine `buffer-list' to show buffers for each workgroup. |
|
516 |
|
|
517 |
Crazy stuff that allows to reduce amount of code, gives new |
|
518 |
features but is fucking unstable, so disabled by default" |
|
519 |
:type 'boolean |
|
520 |
:group 'workgroups |
|
521 |
:set (lambda (sym val) |
|
522 |
(custom-set-default sym val) |
|
523 |
(if (and workgroups-mode val) |
|
524 |
(fset 'buffer-list wg-buffer-list-function) |
|
525 |
(fset 'buffer-list wg-buffer-list-original)))) |
|
526 |
(fset 'buffer-list wg-buffer-list-original) |
|
527 |
|
|
528 |
(defcustom wg-use-faces t |
|
529 |
"Non-nil means use faces in various messages." |
|
530 |
:type 'boolean |
|
531 |
:group 'workgroups) |
|
532 |
|
|
533 |
(defcustom wg-list-display-decor-left-brace "( " |
|
534 |
"String displayed to the left of the list display." |
|
535 |
:type 'string |
|
536 |
:group 'workgroups) |
|
537 |
|
|
538 |
(defcustom wg-list-display-decor-right-brace " )" |
|
539 |
"String displayed to the right of the list display." |
|
540 |
:type 'string |
|
541 |
:group 'workgroups) |
|
542 |
|
|
543 |
(defcustom wg-list-display-decor-divider " | " |
|
544 |
"String displayed between elements of the list display." |
|
545 |
:type 'string |
|
546 |
:group 'workgroups) |
|
547 |
|
|
548 |
(defcustom wg-list-display-decor-current-left "-<{ " |
|
549 |
"String displayed to the left of the current element of the list display." |
|
550 |
:type 'string |
|
551 |
:group 'workgroups) |
|
552 |
|
|
553 |
(defcustom wg-list-display-decor-current-right " }>-" |
|
554 |
"String displayed to the right of the current element of the list display." |
|
555 |
:type 'string |
|
556 |
:group 'workgroups) |
|
557 |
|
|
558 |
(defcustom wg-list-display-decor-previous-left "< " |
|
559 |
"String displayed to the left of the previous element of the list display." |
|
560 |
:type 'string |
|
561 |
:group 'workgroups) |
|
562 |
|
|
563 |
(defcustom wg-list-display-decor-previous-right " >" |
|
564 |
"String displayed to the right of the previous element of the list display." |
|
565 |
:type 'string |
|
566 |
:group 'workgroups) |
|
567 |
|
|
568 |
|
|
569 |
(defvar wg-face-abbrevs nil |
|
570 |
"Assoc list mapping face abbreviations to face names.") |
|
571 |
|
|
572 |
(defmacro wg-defface (face key spec doc &rest args) |
|
573 |
"`defface' wrapper adding a lookup key used by `wg-fontify'." |
|
574 |
(declare (indent 2)) |
|
575 |
`(progn |
|
576 |
(cl-pushnew (cons ,key ',face) wg-face-abbrevs :test #'equal) |
|
577 |
(defface ,face ,spec ,doc ,@args))) |
|
578 |
|
|
579 |
(wg-defface wg-current-workgroup-face :cur |
|
580 |
'((t :inherit font-lock-constant-face :bold nil)) |
|
581 |
"Face used for current elements in list displays." |
|
582 |
:group 'workgroups) |
|
583 |
|
|
584 |
(wg-defface wg-previous-workgroup-face :prev |
|
585 |
'((t :inherit font-lock-keyword-face :bold nil)) |
|
586 |
"Face used for the name of the previous workgroup in the list display." |
|
587 |
:group 'workgroups) |
|
588 |
|
|
589 |
(wg-defface wg-other-workgroup-face :other |
|
590 |
'((t :inherit font-lock-string-face :bold nil)) |
|
591 |
"Face used for the names of other workgroups in the list display." |
|
592 |
:group 'workgroups) |
|
593 |
|
|
594 |
(wg-defface wg-command-face :cmd |
|
595 |
'((t :inherit font-lock-function-name-face :bold nil)) |
|
596 |
"Face used for command/operation strings." |
|
597 |
:group 'workgroups) |
|
598 |
|
|
599 |
(wg-defface wg-divider-face :div |
|
600 |
'((t :inherit font-lock-builtin-face :bold nil)) |
|
601 |
"Face used for dividers." |
|
602 |
:group 'workgroups) |
|
603 |
|
|
604 |
(wg-defface wg-brace-face :brace |
|
605 |
'((t :inherit font-lock-builtin-face :bold nil)) |
|
606 |
"Face used for left and right braces." |
|
607 |
:group 'workgroups) |
|
608 |
|
|
609 |
(wg-defface wg-message-face :msg |
|
610 |
'((t :inherit font-lock-string-face :bold nil)) |
|
611 |
"Face used for messages." |
|
612 |
:group 'workgroups) |
|
613 |
|
|
614 |
(wg-defface wg-mode-line-face :mode |
|
615 |
'((t :inherit font-lock-doc-face :bold nil)) |
|
616 |
"Face used for workgroup position and name in the mode-line display." |
|
617 |
:group 'workgroups) |
|
618 |
|
|
619 |
(wg-defface wg-filename-face :file |
|
620 |
'((t :inherit font-lock-keyword-face :bold nil)) |
|
621 |
"Face used for filenames." |
|
622 |
:group 'workgroups) |
|
623 |
|
|
624 |
|
|
625 |
;;; fancy displays |
|
626 |
|
|
627 |
(defun wg-element-display (elt elt-string &optional current-elt-p previous-elt-p) |
|
628 |
"Return display string for ELT." |
|
629 |
(cond ((and current-elt-p (funcall current-elt-p elt)) |
|
630 |
(wg-fontify (:cur (concat wg-list-display-decor-current-left |
|
631 |
elt-string |
|
632 |
wg-list-display-decor-current-right)))) |
|
633 |
((and previous-elt-p (funcall previous-elt-p elt)) |
|
634 |
(wg-fontify (:prev (concat wg-list-display-decor-previous-left |
|
635 |
elt-string |
|
636 |
wg-list-display-decor-previous-right)))) |
|
637 |
(t (wg-fontify (:other elt-string))))) |
|
638 |
|
|
639 |
(defun wg-workgroup-display (workgroup index) |
|
640 |
"Return display string for WORKGROUP at INDEX." |
|
641 |
(if (not workgroup) wg-nowg-string |
|
642 |
(wg-element-display |
|
643 |
workgroup |
|
644 |
(format "%d: %s" index (wg-workgroup-name workgroup)) |
|
645 |
'wg-current-workgroup-p |
|
646 |
'wg-previous-workgroup-p))) |
|
647 |
|
|
648 |
(defun wg-buffer-display (buffer index) |
|
649 |
"Return display string for BUFFER. INDEX is ignored." |
|
650 |
(if (not buffer) "No buffers" |
|
651 |
(wg-element-display |
|
652 |
(wg-get-buffer buffer) |
|
653 |
(format "%s" (wg-buffer-name buffer)) |
|
654 |
'wg-current-buffer-p))) |
|
655 |
|
|
656 |
(defun wg-message (format-string &rest args) |
|
657 |
"Call `message' with FORMAT-STRING and ARGS." |
|
658 |
(if (> wg-log-level 0) (apply #'message format-string args))) |
|
659 |
|
|
660 |
(defmacro wg-fontified-message (&rest format) |
|
661 |
"`wg-fontify' FORMAT and call `wg-message' on it." |
|
662 |
(declare (indent defun)) |
|
663 |
`(wg-message (wg-fontify ,@format))) |
|
664 |
|
|
665 |
(defun wg-add-face (facekey string) |
|
666 |
"Return a copy of STRING fontified according to FACEKEY. |
|
667 |
FACEKEY must be a key in `wg-face-abbrevs'." |
|
668 |
(let ((face (wg-aget wg-face-abbrevs facekey)) |
|
669 |
(string (copy-sequence string))) |
|
670 |
(unless face (error "No face with key %s" facekey)) |
|
671 |
(if (not wg-use-faces) string |
|
672 |
(put-text-property 0 (length string) 'face face string) |
|
673 |
string))) |
|
674 |
|
|
675 |
(defmacro wg-fontify (&rest specs) |
|
676 |
"A small fontification DSL. |
|
677 |
The results of all SPECS are `concat'd together. |
|
678 |
If a spec is a cons with a keyword car, apply `wg-add-face' to it. |
|
679 |
Other conses get evaluated, and should produce a strings. |
|
680 |
If a spec is a string it is used unmodified. |
|
681 |
Anything else is formatted with %s to produce a string." |
|
682 |
(declare (indent defun)) |
|
683 |
`(concat |
|
684 |
,@(wg-docar (spec specs) |
|
685 |
(cond ((and (consp spec) |
|
686 |
(keywordp (car spec))) |
|
687 |
`(wg-add-face ,@spec)) |
|
688 |
;;((listp spec) (cdr (eval spec))) |
|
689 |
;;((listp spec) |
|
690 |
;; ;;(prin1-to-string (nth 1 (eval spec))) |
|
691 |
;; ) |
|
692 |
((consp spec) spec) |
|
693 |
((stringp spec) spec) |
|
694 |
(t `(format "%s" ,spec)))))) |
|
695 |
|
|
696 |
(defmacro wg-with-gensyms (syms &rest body) |
|
697 |
"Bind all symbols in SYMS to `gensym's, and eval BODY." |
|
698 |
(declare (indent 1)) |
|
699 |
`(let (,@(mapcar (lambda (sym) `(,sym (cl-gensym))) syms)) ,@body)) |
|
700 |
|
|
701 |
(defmacro wg-dbind (args expr &rest body) |
|
702 |
"Bind the variables in ARGS to the result of EXPR and execute BODY. |
|
703 |
Abbreviation of `destructuring-bind'." |
|
704 |
(declare (indent 2)) |
|
705 |
`(cl-destructuring-bind ,args ,expr ,@body)) |
|
706 |
|
|
707 |
(defun wg-partition (list &optional n step) |
|
708 |
"Take LIST, return a list of N length sublists, offset by STEP. |
|
709 |
N defaults to 2, and STEP defaults to N. |
|
710 |
Iterative to prevent stack overflow." |
|
711 |
(let* ((n (or n 2)) (step (or step n)) acc) |
|
712 |
(while list |
|
713 |
(push (-take n list) acc) |
|
714 |
(setq list (nthcdr step list))) |
|
715 |
(nreverse acc))) |
|
716 |
|
|
717 |
(defmacro wg-when-boundp (symbols &rest body) |
|
718 |
"When all SYMBOLS are bound, `eval' BODY." |
|
719 |
(declare (indent 1)) |
|
720 |
`(when (and ,@(mapcar (lambda (sym) `(boundp ',sym)) symbols)) |
|
721 |
,@body)) |
|
722 |
|
|
723 |
(defmacro wg-docar (spec &rest body) |
|
724 |
"do-style wrapper for `mapcar'. |
|
725 |
|
|
726 |
\(fn (VAR LIST) BODY...)" |
|
727 |
(declare (indent 1)) |
|
728 |
`(mapcar (lambda (,(car spec)) ,@body) ,(cadr spec))) |
|
729 |
|
|
730 |
(defmacro wg-dohash (spec &rest body) |
|
731 |
"do-style wrapper for `maphash'. |
|
732 |
|
|
733 |
\(fn (KEY VALUE TABLE [RESULT]) BODY...)" |
|
734 |
(declare (indent 1)) |
|
735 |
(wg-dbind (key val table &optional result) spec |
|
736 |
`(progn (maphash (lambda (,key ,val) ,@body) ,table) ,result))) |
|
737 |
|
|
738 |
(defmacro wg-doconcat (spec &rest body) |
|
739 |
"do-style wrapper for `mapconcat'. |
|
740 |
|
|
741 |
\(fn (VAR SEQ [SEPARATOR]) BODY...)" |
|
742 |
(declare (indent 1)) |
|
743 |
(wg-dbind (elt seq &optional sep) spec |
|
744 |
`(mapconcat (lambda (,elt) ,@body) ,seq (or ,sep "")))) |
|
745 |
|
|
746 |
(defmacro wg-asetf (&rest places-and-values) |
|
747 |
"Anaphoric `setf'." |
|
748 |
`(progn ,@(mapcar (lambda (pv) `(let ((it ,(car pv))) (setf ,@pv))) |
|
749 |
(wg-partition places-and-values 2)))) |
|
750 |
|
|
751 |
(defmacro wg-destructuring-dolist (spec &rest body) |
|
752 |
"Loop over a list. |
|
753 |
Evaluate BODY, destructuring LIST into SPEC, then evaluate RESULT |
|
754 |
to get a return value, defaulting to nil. The only hitch is that |
|
755 |
spec must end in dotted style, collecting the rest of the list |
|
756 |
into a var, like so: (a (b c) . rest) |
|
757 |
|
|
758 |
\(fn (SPEC LIST [RESULT]) BODY...)" |
|
759 |
(declare (indent 1)) |
|
760 |
(wg-dbind (loopspec list &optional result) spec |
|
761 |
(let ((rest (cdr (last loopspec)))) |
|
762 |
(wg-with-gensyms (list-sym) |
|
763 |
`(let ((,list-sym ,list)) |
|
764 |
(while ,list-sym |
|
765 |
(wg-dbind ,loopspec ,list-sym |
|
766 |
,@body |
|
767 |
(setq ,list-sym ,rest))) |
|
768 |
,result))))) |
|
769 |
|
|
770 |
|
|
771 |
;;; numbers |
|
772 |
|
|
773 |
(defun wg-step-to (n m step) |
|
774 |
"Increment or decrement N toward M by STEP. |
|
775 |
Return M when the difference between N and M is less than STEP." |
|
776 |
(cond ((= n m) n) |
|
777 |
((< n m) (min (+ n step) m)) |
|
778 |
((> n m) (max (- n step) m)))) |
|
779 |
|
|
780 |
(defun wg-within (num lo hi &optional hi-inclusive) |
|
781 |
"Return t when NUM is within bounds LO and HI. |
|
782 |
HI-INCLUSIVE non-nil means the HI bound is inclusive." |
|
783 |
(and (>= num lo) (if hi-inclusive (<= num hi) (< num hi)))) |
|
784 |
|
|
785 |
(defun wg-int-to-b36-one-digit (i) |
|
786 |
"Return a character in 0..9 or A..Z from I, and integer 0<=I<32. |
|
787 |
Cribbed from `org-id-int-to-b36-one-digit'." |
|
788 |
(cond ((not (wg-within i 0 36)) |
|
789 |
(error "%s out of range" i)) |
|
790 |
((< i 10) (+ ?0 i)) |
|
791 |
((< i 36) (+ ?A i -10)))) |
|
792 |
|
|
793 |
(defun wg-b36-to-int-one-digit (i) |
|
794 |
"Turn a character 0..9, A..Z, a..z into a number 0..61. |
|
795 |
The input I may be a character, or a single-letter string. |
|
796 |
Cribbed from `org-id-b36-to-int-one-digit'." |
|
797 |
(and (stringp i) (setq i (string-to-char i))) |
|
798 |
(cond ((and (>= i ?0) (<= i ?9)) (- i ?0)) |
|
799 |
((and (>= i ?A) (<= i ?Z)) (+ (- i ?A) 10)) |
|
800 |
(t (error "Invalid b36 character")))) |
|
801 |
|
|
802 |
(defun wg-int-to-b36 (i &optional length) |
|
803 |
"Return a base 36 string from I." |
|
804 |
(let ((base 36) b36) |
|
805 |
(cl-labels ((add-digit () (push (wg-int-to-b36-one-digit (mod i base)) b36) |
|
806 |
(setq i (/ i base)))) |
|
807 |
(add-digit) |
|
808 |
(while (> i 0) (add-digit)) |
|
809 |
(setq b36 (cl-map 'string 'identity b36)) |
|
810 |
(if (not length) b36 |
|
811 |
(concat (make-string (max 0 (- length (length b36))) ?0) b36))))) |
|
812 |
|
|
813 |
(defun wg-b36-to-int (str) |
|
814 |
"Convert STR, a base-36 string, into the corresponding integer. |
|
815 |
Cribbed from `org-id-b36-to-int'." |
|
816 |
(let ((result 0)) |
|
817 |
(mapc (lambda (i) |
|
818 |
(setq result (+ (* result 36) |
|
819 |
(wg-b36-to-int-one-digit i)))) |
|
820 |
str) |
|
821 |
result)) |
|
822 |
|
|
823 |
|
|
824 |
|
|
825 |
;;; lists |
|
826 |
|
|
827 |
(defmacro wg-removef-p (item seq-place &rest keys) |
|
828 |
"If ITEM is a `member*' of SEQ-PLACE, remove it from SEQ-PLACE and return t. |
|
829 |
Otherwise return nil. KEYS can be any keywords accepted by `remove*'." |
|
830 |
`(> (length ,seq-place) |
|
831 |
(length (setf ,seq-place (cl-remove ,item ,seq-place ,@keys))))) |
|
832 |
|
|
833 |
(defmacro wg-pushnew-p (item seq-place &rest keys) |
|
834 |
"If ITEM is not a `member' of SEQ-PLACE, push it to SEQ-PLACE and return t. |
|
835 |
Otherwise return nil. KEYS can be any keyword args accepted by `pushnew'." |
|
836 |
`(< (length ,seq-place) |
|
837 |
(length (cl-pushnew ,item ,seq-place ,@keys)))) |
|
838 |
|
|
839 |
(defun wg-range (start end) |
|
840 |
"Return a list of integers from START up to but not including END." |
|
841 |
(let (accum) |
|
842 |
(dotimes (i (- end start) (nreverse accum)) |
|
843 |
(push (+ start i) accum)))) |
|
844 |
|
|
845 |
(defun wg-insert-before (elt list index) |
|
846 |
"Insert ELT into LIST before INDEX." |
|
847 |
(if (zerop index) (cons elt list) |
|
848 |
(-insert-at index elt list))) |
|
849 |
|
|
850 |
(defun wg-move-elt (elt list index &rest keys) |
|
851 |
"Move ELT before INDEX in LIST. |
|
852 |
KEYS is passed to `remove*'." |
|
853 |
(wg-insert-before elt (apply 'cl-remove elt list keys) index)) |
|
854 |
|
|
855 |
(defun wg-cyclic-nth (list n) |
|
856 |
"Return the Nth element of LIST, modded by the length of list." |
|
857 |
(nth (mod n (length list)) list)) |
|
858 |
|
|
859 |
(defun wg-cyclic-offset-elt (elt list n) |
|
860 |
"Cyclically offset ELT's position in LIST by N." |
|
861 |
(-when-let (pos (cl-position elt list)) |
|
862 |
(wg-move-elt elt list (mod (+ n pos) (length list))))) |
|
863 |
|
|
864 |
(defun wg-cyclic-nth-from-elt (elt list n &rest keys) |
|
865 |
"Return the elt in LIST N places cyclically from ELT. |
|
866 |
If ELT is not present is LIST, return nil. |
|
867 |
KEYS is passed to `position'." |
|
868 |
(-when-let (pos (apply 'cl-position elt list keys)) |
|
869 |
(wg-cyclic-nth list (+ pos n)))) |
|
870 |
|
|
871 |
(defun wg-util-swap (elt1 elt2 list) |
|
872 |
"Return a copy of LIST with ELT1 and ELT2 swapped. |
|
873 |
Return nil when ELT1 and ELT2 aren't both present." |
|
874 |
(-when-let* ((p1 (cl-position elt1 list)) |
|
875 |
(p2 (cl-position elt2 list))) |
|
876 |
(wg-move-elt elt1 (wg-move-elt elt2 list p1) p2))) |
|
877 |
|
|
878 |
(defun wg-dups-p (list &rest keys) |
|
879 |
"Return non-nil when LIST contains duplicate elements. |
|
880 |
|
|
881 |
Keywords supported: :test :key |
|
882 |
|
|
883 |
\(fn LIST [KEYWORD VALUE]...)" |
|
884 |
(let ((test (or (plist-get keys :test) 'eq)) |
|
885 |
(key (or (plist-get keys :key) 'identity))) |
|
886 |
(cl-loop for (elt . rest) on list |
|
887 |
for elt = (funcall key elt) |
|
888 |
when (cl-find elt rest :test test :key key) return elt))) |
|
889 |
|
|
890 |
(defun wg-string-list-union (&optional list1 list2) |
|
891 |
"Return the `union' of LIST1 and LIST2, using `string=' as the test. |
|
892 |
This only exists to get rid of duplicate lambdas in a few reductions." |
|
893 |
(cl-union list1 list2 :test 'string=)) |
|
894 |
|
|
895 |
|
|
896 |
|
|
897 |
;;; alists |
|
898 |
|
|
899 |
(defun wg-aget (alist key &optional default) |
|
900 |
"Return the value of KEY in ALIST. Uses `assq'. |
|
901 |
If PARAM is not found, return DEFAULT which defaults to nil." |
|
902 |
(aif (assq key alist) (cdr it) default)) |
|
903 |
|
|
904 |
(defun wg-aput (alist key value) |
|
905 |
"Return a new alist from ALIST with KEY's value set to VALUE." |
|
906 |
(let* ((found nil) |
|
907 |
(new (wg-docar (kvp alist) |
|
908 |
(if (not (eq key (car kvp))) kvp |
|
909 |
(setq found (cons key value)))))) |
|
910 |
(if found new (cons (cons key value) new)))) |
|
911 |
|
|
912 |
(defun wg-aremove (alist key) |
|
913 |
"`remove' KEY's key-value-pair from ALIST." |
|
914 |
(remove (assoc key alist) alist)) |
|
915 |
|
|
916 |
|
|
917 |
;;; symbols and strings |
|
918 |
|
|
919 |
(defun wg-toggle (symbol) |
|
920 |
"Toggle SYMBOL's truthiness." |
|
921 |
(set symbol (not (symbol-value symbol)))) |
|
922 |
|
|
923 |
(defun wg-get-buffer (buffer-or-name) |
|
924 |
"Return BUFFER-OR-NAME's buffer, or error." |
|
925 |
(or (get-buffer buffer-or-name) |
|
926 |
(error "%S does not identify a buffer" buffer-or-name))) |
|
927 |
|
|
928 |
(defun wg-buffer-name (buffer-or-name) |
|
929 |
"Return BUFFER-OR-NAME's `buffer-name', or error." |
|
930 |
(buffer-name (wg-get-buffer buffer-or-name))) |
|
931 |
|
|
932 |
(defun wg-buffer-major-mode (buffer-or-name) |
|
933 |
"Return BUFFER's major-mode." |
|
934 |
(with-current-buffer buffer-or-name major-mode)) |
|
935 |
|
|
936 |
(defun wg-current-buffer-p (buffer-or-name) |
|
937 |
"Return t if BUFFER-OR-NAME is the current buffer, nil otherwise." |
|
938 |
(eq (wg-get-buffer buffer-or-name) (current-buffer))) |
|
939 |
|
|
940 |
(defun wg-symcat (&rest symbols-and-strings) |
|
941 |
"Return a new interned symbol by concatenating SYMBOLS-AND-STRINGS." |
|
942 |
(intern (mapconcat (lambda (obj) (if (symbolp obj) (symbol-name obj) obj)) |
|
943 |
symbols-and-strings ""))) |
|
944 |
|
|
945 |
(defmacro wg-defstruct (prefix name-form &rest slot-defs) |
|
946 |
"`defstruct' wrapper that namespace-prefixes all generated functions. |
|
947 |
Note: this doesn't yet work with :conc-name, and possibly other |
|
948 |
options." |
|
949 |
(declare (indent 2)) |
|
950 |
(let* ((name (if (consp name-form) (car name-form) name-form)) |
|
951 |
(prefixed-name (wg-symcat prefix "-" name))) |
|
952 |
(cl-labels ((rebind (opstr) |
|
953 |
(let ((oldfnsym (wg-symcat opstr "-" prefix "-" name))) |
|
954 |
`((fset ',(wg-symcat prefix "-" opstr "-" name) |
|
955 |
(symbol-function ',oldfnsym)) |
|
956 |
(fmakunbound ',oldfnsym))))) |
|
957 |
;; `eval-and-compile' gets rid of byte-comp warnings ("function `foo' not |
|
958 |
;; known to be defined"). We can accomplish this with `declare-function' |
|
959 |
;; too, but it annoyingly requires inclusion of the function's arglist, |
|
960 |
;; which gets ugly. |
|
961 |
`(eval-and-compile |
|
962 |
(cl-defstruct ,(if (symbolp name-form) prefixed-name |
|
963 |
`(,prefixed-name ,@(cdr name-form))) |
|
964 |
,@slot-defs) |
|
965 |
,@(rebind "make") |
|
966 |
,@(rebind "copy") |
|
967 |
',prefixed-name)))) |
|
968 |
|
|
969 |
(wg-defstruct wg session |
|
970 |
(uid (wg-generate-uid)) |
|
971 |
(name) |
|
972 |
(modified) |
|
973 |
(parameters) |
|
974 |
(file-name) |
|
975 |
(version wg-version) |
|
976 |
(workgroup-list) |
|
977 |
(buf-list)) |
|
978 |
|
|
979 |
(wg-defstruct wg workgroup |
|
980 |
(uid (wg-generate-uid)) |
|
981 |
(name) |
|
982 |
(modified) |
|
983 |
(parameters) |
|
984 |
(base-wconfig) |
|
985 |
(selected-frame-wconfig) |
|
986 |
(saved-wconfigs) |
|
987 |
(strong-buf-uids) |
|
988 |
(weak-buf-uids)) |
|
989 |
|
|
990 |
(wg-defstruct wg workgroup-state |
|
991 |
(undo-pointer) |
|
992 |
(undo-list)) |
|
993 |
|
|
994 |
(wg-defstruct wg wconfig |
|
995 |
(uid (wg-generate-uid)) |
|
996 |
(name) |
|
997 |
(parameters) |
|
998 |
(left) |
|
999 |
(top) |
|
1000 |
(width) |
|
1001 |
(height) |
|
1002 |
(vertical-scroll-bars) |
|
1003 |
(scroll-bar-width) |
|
1004 |
(wtree)) |
|
1005 |
|
|
1006 |
(wg-defstruct wg wtree |
|
1007 |
(uid) |
|
1008 |
(dir) |
|
1009 |
(edges) |
|
1010 |
(wlist)) |
|
1011 |
|
|
1012 |
(wg-defstruct wg win |
|
1013 |
(uid) |
|
1014 |
(parameters) |
|
1015 |
(edges) |
|
1016 |
(point) |
|
1017 |
(start) |
|
1018 |
(hscroll) |
|
1019 |
(dedicated) |
|
1020 |
(selected) |
|
1021 |
(minibuffer-scroll) |
|
1022 |
(buf-uid)) |
|
1023 |
|
|
1024 |
(wg-defstruct wg buf |
|
1025 |
(uid (wg-generate-uid)) |
|
1026 |
(name) |
|
1027 |
(file-name) |
|
1028 |
(point) |
|
1029 |
(mark) |
|
1030 |
(local-vars) |
|
1031 |
(special-data) |
|
1032 |
;; This may be used later: |
|
1033 |
(gc)) |
|
1034 |
|
|
1035 |
|
|
1036 |
(defmacro wg-with-slots (obj slot-bindings &rest body) |
|
1037 |
"Bind OBJ's slot values to symbols in BINDS, then eval BODY. |
|
1038 |
The car of each element of SLOT-BINDINGS is the bound symbol, and |
|
1039 |
the cadr as the accessor function." |
|
1040 |
(declare (indent 2)) |
|
1041 |
(wg-with-gensyms (objsym) |
|
1042 |
`(let* ((,objsym ,obj) |
|
1043 |
,@(wg-docar (slot slot-bindings) |
|
1044 |
`(,(car slot) (,(cadr slot) ,objsym)))) |
|
1045 |
,@body))) |
|
1046 |
|
|
1047 |
(defun wg-add-or-remove-hooks (remove &rest pairs) |
|
1048 |
"Add FUNCTION to or remove it from HOOK, depending on REMOVE." |
|
1049 |
(dolist (pair (wg-partition pairs 2)) |
|
1050 |
(funcall (if remove 'remove-hook 'add-hook) |
|
1051 |
(car pair) (cadr pair)))) |
|
1052 |
|
|
1053 |
(defmacro wg-set-parameter (place parameter value) |
|
1054 |
"Set PARAMETER to VALUE at PLACE. |
|
1055 |
This needs to be a macro to allow specification of a setf'able place." |
|
1056 |
(wg-with-gensyms (p v) |
|
1057 |
`(let ((,p ,parameter) (,v ,value)) |
|
1058 |
(wg-pickelable-or-error ,p) |
|
1059 |
(wg-pickelable-or-error ,v) |
|
1060 |
(setf ,place (wg-aput ,place ,p ,v)) |
|
1061 |
,v))) |
|
1062 |
|
|
1063 |
(defun wg-time-to-b36 () |
|
1064 |
"Convert `current-time' into a b36 string." |
|
1065 |
(apply 'concat (wg-docar (time (current-time)) |
|
1066 |
(wg-int-to-b36 time 4)))) |
|
1067 |
|
|
1068 |
(defun wg-b36-to-time (b36) |
|
1069 |
"Parse the time in B36 string from UID." |
|
1070 |
(cl-loop for i from 0 to 8 by 4 |
|
1071 |
collect (wg-b36-to-int (cl-subseq b36 i (+ i 4))))) |
|
1072 |
(defalias 'wg-uid-to-time 'wg-b36-to-time) |
|
1073 |
|
|
1074 |
(defun wg-generate-uid (&optional prefix) |
|
1075 |
"Return a new uid, optionally prefixed by PREFIX." |
|
1076 |
(concat prefix (wg-time-to-b36) "-" (wg-int-to-b36 string-chars-consed))) |
|
1077 |
|
|
1078 |
(defun wg-uid-to-seconds (uid) |
|
1079 |
"Return the `float-time' parsed from UID with `wg-uid-to-time'." |
|
1080 |
(float-time (wg-uid-to-time uid))) |
|
1081 |
|
|
1082 |
|
|
1083 |
(defun wg-get-value (arg) |
|
1084 |
"Get a value of ARG if it exists." |
|
1085 |
(if (boundp `,arg) arg)) |
|
1086 |
|
|
1087 |
(defmacro wg-support (mode pkg params) |
|
1088 |
"Macro to create (de)serialization functions for a buffer. |
|
1089 |
You need to save/restore a specific MODE which is loaded from a |
|
1090 |
package PKG. In PARAMS you give local variables to save and a |
|
1091 |
deserialization function." |
|
1092 |
(declare (indent 2)) |
|
1093 |
`(let ((mode-str (symbol-name ,mode)) |
|
1094 |
(args ,params)) |
|
1095 |
|
|
1096 |
(eval `(defun ,(intern (format "wg-deserialize-%s-buffer" mode-str)) (buffer) |
|
1097 |
"DeSerialization function created with `wg-support'. |
|
1098 |
Gets saved variables and runs code to restore a BUFFER." |
|
1099 |
(when (require ',,pkg nil 'noerror) |
|
1100 |
(wg-dbind (this-function variables) (wg-buf-special-data buffer) |
|
1101 |
(let ((default-directory (car variables)) |
|
1102 |
(df (cdr (assoc 'deserialize ',,params))) |
|
1103 |
(user-vars (cadr variables))) |
|
1104 |
(if df |
|
1105 |
(funcall df buffer user-vars) |
|
1106 |
(get-buffer-create wg-default-buffer)) |
|
1107 |
))))) |
|
1108 |
|
|
1109 |
(eval `(defun ,(intern (format "wg-serialize-%s-buffer" mode-str)) (buffer) |
|
1110 |
"Serialization function created with `wg-support'. |
|
1111 |
Saves some variables to restore a BUFFER later." |
|
1112 |
(when (get-buffer buffer) |
|
1113 |
(with-current-buffer buffer |
|
1114 |
(when (eq major-mode ',,mode) |
|
1115 |
(let ((sf (cdr (assoc 'serialize ',,params))) |
|
1116 |
(save (cdr (assoc 'save ',,params)))) |
|
1117 |
(list ',(intern (format "wg-deserialize-%s-buffer" mode-str)) |
|
1118 |
(list default-directory |
|
1119 |
(if sf |
|
1120 |
(funcall sf buffer) |
|
1121 |
(if save (mapcar 'wg-get-value save))) |
|
1122 |
)))))))) |
|
1123 |
;; Maybe change a docstring for functions |
|
1124 |
;;(put (intern (format "wg-serialize-%s-buffer" (symbol-name mode))) |
|
1125 |
;; 'function-documentation |
|
1126 |
;; (format "A function created by `wg-support'.")) |
|
1127 |
|
|
1128 |
;; Add function to `wg-special-buffer-serdes-functions' variable |
|
1129 |
(eval `(add-to-list 'wg-special-buffer-serdes-functions |
|
1130 |
',(intern (format "wg-serialize-%s-buffer" mode-str)) t)) |
|
1131 |
)) |
|
1132 |
|
|
1133 |
(defconst wg-font-lock-keywords |
|
1134 |
'(("(\\(wg-support\\|wg-support\\)[ \t]*" |
|
1135 |
(1 font-lock-keyword-face) |
|
1136 |
;;(2 font-lock-keyword-face) |
|
1137 |
))) |
|
1138 |
(font-lock-add-keywords 'emacs-lisp-mode wg-font-lock-keywords) |
|
1139 |
|
|
1140 |
(defvar wg-current-session nil "Current session object.") |
|
1141 |
(defun wg-current-session (&optional noerror) |
|
1142 |
"Return `wg-current-session' or error unless NOERROR." |
|
1143 |
(or wg-current-session |
|
1144 |
(if workgroups-mode |
|
1145 |
(unless noerror (error "No session is defined")) |
|
1146 |
(unless noerror |
|
1147 |
(error "Activate workgroups with (workgroups-mode 1)"))))) |
|
1148 |
|
|
1149 |
;; locate-dominating-file |
|
1150 |
(defun wg-get-first-existing-dir (&optional dir) |
|
1151 |
"Test if DIR exists and return it. |
|
1152 |
If not - try to go to the parent dir and do the same." |
|
1153 |
(let* ((d (or dir default-directory))) |
|
1154 |
(if (file-directory-p d) d |
|
1155 |
(let* ((cur d) (parent (file-name-directory (directory-file-name cur)))) |
|
1156 |
(while (and (> (length cur) (length parent)) |
|
1157 |
(not (file-directory-p parent))) |
|
1158 |
(message "Test %s" parent) |
|
1159 |
(setq cur parent) |
|
1160 |
(setq parent (file-name-directory (directory-file-name cur)))) |
|
1161 |
parent)))) |
|
1162 |
|
|
1163 |
|
|
1164 |
(defvar wg-pickel-identifier '~pickel!~ |
|
1165 |
"Symbol identifying a stream as a pickel.") |
|
1166 |
|
|
1167 |
(defvar wg-pickel-pickelable-types |
|
1168 |
'(integer |
|
1169 |
float |
|
1170 |
symbol |
|
1171 |
string |
|
1172 |
cons |
|
1173 |
vector |
|
1174 |
hash-table |
|
1175 |
buffer |
|
1176 |
marker |
|
1177 |
;;window-configuration |
|
1178 |
;;frame |
|
1179 |
;;window |
|
1180 |
;;process |
|
1181 |
) |
|
1182 |
"Types pickel can serialize.") |
|
1183 |
|
|
1184 |
(defvar wg-pickel-object-serializers |
|
1185 |
'((integer . identity) |
|
1186 |
(float . identity) |
|
1187 |
(string . identity) |
|
1188 |
(symbol . wg-pickel-symbol-serializer) |
|
1189 |
(cons . wg-pickel-cons-serializer) |
|
1190 |
(vector . wg-pickel-vector-serializer) |
|
1191 |
(hash-table . wg-pickel-hash-table-serializer) |
|
1192 |
(buffer . wg-pickel-buffer-serializer) |
|
1193 |
(marker . wg-pickel-marker-serializer) |
|
1194 |
;;(window-configuration . wg-pickel-window-configuration-serializer) |
|
1195 |
) |
|
1196 |
"Alist mapping types to object serialization functions.") |
|
1197 |
(defvar wg-pickel-object-deserializers |
|
1198 |
'((s . wg-pickel-deserialize-uninterned-symbol) |
|
1199 |
(c . wg-pickel-deserialize-cons) |
|
1200 |
(v . wg-pickel-deserialize-vector) |
|
1201 |
(h . wg-pickel-deserialize-hash-table) |
|
1202 |
(b . wg-pickel-deserialize-buffer) |
|
1203 |
(m . wg-pickel-deserialize-marker) |
|
1204 |
;;(f . wg-pickel-deserialize-frame) |
|
1205 |
) |
|
1206 |
"Alist mapping type keys to object deserialization functions.") |
|
1207 |
|
|
1208 |
(defvar wg-pickel-link-serializers |
|
1209 |
'((cons . wg-pickel-cons-link-serializer) |
|
1210 |
(vector . wg-pickel-vector-link-serializer) |
|
1211 |
(hash-table . wg-pickel-hash-table-link-serializer)) |
|
1212 |
"Alist mapping types to link serialization functions.") |
|
1213 |
(defvar wg-pickel-link-deserializers |
|
1214 |
`((c . wg-pickel-cons-link-deserializer) |
|
1215 |
(v . wg-pickel-vector-link-deserializer) |
|
1216 |
(h . wg-pickel-hash-table-link-deserializer)) |
|
1217 |
"Alist mapping type keys to link deserialization functions.") |
|
1218 |
|
|
1219 |
|
|
1220 |
|
|
1221 |
;;; errors and predicates |
|
1222 |
|
|
1223 |
(put 'wg-pickel-unpickelable-type-error |
|
1224 |
'error-conditions |
|
1225 |
'(error wg-pickel-errors wg-pickel-unpickelable-type-error)) |
|
1226 |
|
|
1227 |
(put 'wg-pickel-unpickelable-type-error |
|
1228 |
'error-message |
|
1229 |
"Attemp to pickel unpickelable type") |
|
1230 |
|
|
1231 |
(defun wg-pickelable-or-error (obj) |
|
1232 |
"Error when OBJ isn't pickelable." |
|
1233 |
(unless (memq (type-of obj) wg-pickel-pickelable-types) |
|
1234 |
(signal 'wg-pickel-unpickelable-type-error |
|
1235 |
(format "Can't pickel objects of type: %S" (type-of obj)))) |
|
1236 |
(cl-typecase obj |
|
1237 |
(cons |
|
1238 |
(wg-pickelable-or-error (car obj)) |
|
1239 |
(wg-pickelable-or-error (cdr obj))) |
|
1240 |
(vector |
|
1241 |
(cl-map nil 'wg-pickelable-or-error obj)) |
|
1242 |
(hash-table |
|
1243 |
(wg-dohash (key value obj) |
|
1244 |
(wg-pickelable-or-error key) |
|
1245 |
(wg-pickelable-or-error value))))) |
|
1246 |
|
|
1247 |
(defun wg-pickelable-p (obj) |
|
1248 |
(condition-case err |
|
1249 |
(progn (wg-pickelable-or-error obj) t) |
|
1250 |
(wg-pickel-unpickelable-type-error nil))) |
|
1251 |
|
|
1252 |
(defun wg-pickel-p (obj) |
|
1253 |
"Return t when OBJ is a pickel, nil otherwise." |
|
1254 |
(and (consp obj) (eq (car obj) wg-pickel-identifier))) |
|
1255 |
|
|
1256 |
|
|
1257 |
|
|
1258 |
;; accessor functions |
|
1259 |
|
|
1260 |
(defun wg-pickel-object-serializer (obj) |
|
1261 |
"Return the object serializer for the `type-of' OBJ." |
|
1262 |
(or (wg-aget wg-pickel-object-serializers (type-of obj)) |
|
1263 |
(error "Invalid type: %S" (type-of obj)))) |
|
1264 |
|
|
1265 |
(defun wg-pickel-link-serializer (obj) |
|
1266 |
"Return the link serializer for the `type-of' OBJ." |
|
1267 |
(wg-aget wg-pickel-link-serializers (type-of obj))) |
|
1268 |
|
|
1269 |
(defun wg-pickel-object-deserializer (key) |
|
1270 |
"Return the object deserializer for type key KEY, or error." |
|
1271 |
(or (wg-aget wg-pickel-object-deserializers key) |
|
1272 |
(error "Invalid object deserializer key: %S" key))) |
|
1273 |
|
|
1274 |
(defun wg-pickel-link-deserializer (key) |
|
1275 |
"Return the link deserializer for type key KEY, or error." |
|
1276 |
(or (wg-aget wg-pickel-link-deserializers key) |
|
1277 |
(error "Invalid link deserializer key: %S" key))) |
|
1278 |
|
|
1279 |
|
|
1280 |
|
|
1281 |
;;; bindings |
|
1282 |
|
|
1283 |
(defun wg-pickel-make-bindings-table (obj) |
|
1284 |
"Return a table binding unique subobjects of OBJ to ids." |
|
1285 |
(let ((binds (make-hash-table :test 'eq)) |
|
1286 |
(id -1)) |
|
1287 |
(cl-labels |
|
1288 |
((inner (obj) |
|
1289 |
(unless (gethash obj binds) |
|
1290 |
(puthash obj (cl-incf id) binds) |
|
1291 |
(cl-case (type-of obj) |
|
1292 |
(cons |
|
1293 |
(inner (car obj)) |
|
1294 |
(inner (cdr obj))) |
|
1295 |
(vector |
|
1296 |
(dotimes (idx (length obj)) |
|
1297 |
(inner (aref obj idx)))) |
|
1298 |
(hash-table |
|
1299 |
(wg-dohash (key val obj) |
|
1300 |
(inner key) |
|
1301 |
(inner val))))))) |
|
1302 |
(inner obj) |
|
1303 |
binds))) |
|
1304 |
|
|
1305 |
|
|
1306 |
|
|
1307 |
;;; Objects |
|
1308 |
(defun wg-pickel-symbol-serializer (symbol) |
|
1309 |
"Return SYMBOL's serialization." |
|
1310 |
(cond ((eq symbol t) t) |
|
1311 |
((eq symbol nil) nil) |
|
1312 |
((intern-soft symbol) symbol) |
|
1313 |
(t (list 's (symbol-name symbol))))) |
|
1314 |
(defun wg-pickel-deserialize-uninterned-symbol (name) |
|
1315 |
"Return a new uninterned symbol from NAME." |
|
1316 |
(make-symbol name)) |
|
1317 |
|
|
1318 |
|
|
1319 |
;; buffer |
|
1320 |
(defun wg-pickel-buffer-serializer (buffer) |
|
1321 |
"Return BUFFER's UID in workgroups buffer list." |
|
1322 |
(list 'b (wg-add-buffer-to-buf-list buffer))) |
|
1323 |
(defun wg-pickel-deserialize-buffer (uid) |
|
1324 |
"Return a restored buffer from it's UID." |
|
1325 |
(wg-restore-buffer (wg-find-buf-by-uid uid))) |
|
1326 |
|
|
1327 |
|
|
1328 |
;; marker |
|
1329 |
(defun wg-pickel-marker-serializer (marker) |
|
1330 |
"Return MARKER's data." |
|
1331 |
(list 'm (list (marker-position marker) |
|
1332 |
(wg-add-buffer-to-buf-list (marker-buffer marker))))) |
|
1333 |
(defun wg-pickel-deserialize-marker (data) |
|
1334 |
"Return marker from it's DATA." |
|
1335 |
(let ((m (make-marker))) |
|
1336 |
(set-marker m (car data) (wg-pickel-deserialize-buffer (car (cdr data)))))) |
|
1337 |
|
|
1338 |
|
|
1339 |
;; cons - http://www.gnu.org/software/emacs/manual/html_node/eintr/cons.html |
|
1340 |
(defun wg-pickel-cons-serializer (cons) |
|
1341 |
"Return CONS's serialization." |
|
1342 |
(list 'c)) |
|
1343 |
(defun wg-pickel-deserialize-cons () |
|
1344 |
"Return a new cons cell initialized to nil." |
|
1345 |
(cons nil nil)) |
|
1346 |
(defun wg-pickel-cons-link-serializer (cons binds) |
|
1347 |
"Return the serialization of CONS's links in BINDS." |
|
1348 |
(list 'c |
|
1349 |
(gethash cons binds) |
|
1350 |
(gethash (car cons) binds) |
|
1351 |
(gethash (cdr cons) binds))) |
|
1352 |
(defun wg-pickel-cons-link-deserializer (cons-id car-id cdr-id binds) |
|
1353 |
"Relink a cons cell with its car and cdr in BINDS." |
|
1354 |
(let ((cons (gethash cons-id binds))) |
|
1355 |
(setcar cons (gethash car-id binds)) |
|
1356 |
(setcdr cons (gethash cdr-id binds)))) |
|
1357 |
|
|
1358 |
|
|
1359 |
|
|
1360 |
;; vector - http://www.gnu.org/software/emacs/manual/html_node/elisp/Vector-Functions.html |
|
1361 |
;; (wg-unpickel (wg-pickel (make-vector 9 'Z))) |
|
1362 |
;; |
|
1363 |
(defun wg-pickel-vector-serializer (vector) |
|
1364 |
"Return VECTOR's serialization." |
|
1365 |
(list 'v (length vector))) |
|
1366 |
(defun wg-pickel-deserialize-vector (length) |
|
1367 |
"Return a new vector of length LENGTH." |
|
1368 |
(make-vector length nil)) |
|
1369 |
(defun wg-pickel-vector-link-serializer (vector binds) |
|
1370 |
"Return the serialization of VECTOR's links in BINDS." |
|
1371 |
(let (result) |
|
1372 |
(dotimes (i (length vector) result) |
|
1373 |
(setq result |
|
1374 |
(nconc (list 'v |
|
1375 |
(gethash vector binds) |
|
1376 |
i |
|
1377 |
(gethash (aref vector i) binds)) |
|
1378 |
result))))) |
|
1379 |
(defun wg-pickel-vector-link-deserializer (vector-id index value-id binds) |
|
1380 |
"Relink a vector with its elements in BINDS." |
|
1381 |
(aset (gethash vector-id binds) index (gethash value-id binds))) |
|
1382 |
|
|
1383 |
|
|
1384 |
;; hash table - http://www.gnu.org/software/emacs/manual/html_node/elisp/Hash-Tables.html |
|
1385 |
(defun wg-pickel-hash-table-serializer (table) |
|
1386 |
"Return HASH-TABLE's serialization." |
|
1387 |
(list 'h |
|
1388 |
(hash-table-test table) |
|
1389 |
(hash-table-size table) |
|
1390 |
(hash-table-rehash-size table) |
|
1391 |
(hash-table-rehash-threshold table) |
|
1392 |
(hash-table-weakness table))) |
|
1393 |
(defun wg-pickel-deserialize-hash-table (test size rsize rthresh weakness) |
|
1394 |
"Return a new hash-table with the specified properties." |
|
1395 |
(make-hash-table :test test :size size :rehash-size rsize |
|
1396 |
:rehash-threshold rthresh :weakness weakness)) |
|
1397 |
(defun wg-pickel-hash-table-link-serializer (table binds) |
|
1398 |
"Return the serialization of TABLE's links in BINDS." |
|
1399 |
(let (result) |
|
1400 |
(wg-dohash (key value table result) |
|
1401 |
(setq result |
|
1402 |
(nconc (list 'h |
|
1403 |
(gethash key binds) |
|
1404 |
(gethash value binds) |
|
1405 |
(gethash table binds)) |
|
1406 |
result))))) |
|
1407 |
(defun wg-pickel-hash-table-link-deserializer (key-id value-id table-id binds) |
|
1408 |
"Relink a hash-table with its keys and values in BINDS." |
|
1409 |
(puthash (gethash key-id binds) |
|
1410 |
(gethash value-id binds) |
|
1411 |
(gethash table-id binds))) |
|
1412 |
|
|
1413 |
|
|
1414 |
;; TODO |
|
1415 |
(defun wg-pickel-window-configuration-serializer (wc) |
|
1416 |
"Return Window configuration WC's serialization." |
|
1417 |
(list 'wc 1)) |
|
1418 |
|
|
1419 |
|
|
1420 |
(defun wg-pickel-serialize-objects (binds) |
|
1421 |
"Return a list of serializations of the objects in BINDS." |
|
1422 |
(let (result) |
|
1423 |
(wg-dohash (obj id binds result) |
|
1424 |
(setq result |
|
1425 |
(nconc (list id (funcall (wg-pickel-object-serializer obj) obj)) |
|
1426 |
result))))) |
|
1427 |
(defun wg-pickel-deserialize-objects (serial-objects) |
|
1428 |
"Return a hash-table of objects deserialized from SERIAL-OBJECTS." |
|
1429 |
(let ((binds (make-hash-table))) |
|
1430 |
(wg-destructuring-dolist ((id obj . rest) serial-objects binds) |
|
1431 |
(puthash id |
|
1432 |
(if (atom obj) obj |
|
1433 |
(wg-dbind (key . data) obj |
|
1434 |
(apply (wg-pickel-object-deserializer key) data))) |
|
1435 |
binds)))) |
|
1436 |
|
|
1437 |
|
|
1438 |
|
|
1439 |
(defun wg-pickel-serialize-links (binds) |
|
1440 |
"Return a list of serializations of the links between objects in BINDS." |
|
1441 |
(let (result) |
|
1442 |
(wg-dohash (obj id binds result) |
|
1443 |
(awhen (wg-pickel-link-serializer obj) |
|
1444 |
(setq result (nconc (funcall it obj binds) result)))))) |
|
1445 |
(defun wg-pickel-deserialize-links (serial-links binds) |
|
1446 |
"Return BINDS after relinking all its objects according to SERIAL-LINKS." |
|
1447 |
(wg-destructuring-dolist ((key arg1 arg2 arg3 . rest) serial-links binds) |
|
1448 |
(funcall (wg-pickel-link-deserializer key) arg1 arg2 arg3 binds))) |
|
1449 |
|
|
1450 |
(defun wg-pickel (obj) |
|
1451 |
"Return the serialization of OBJ." |
|
1452 |
(wg-pickelable-or-error obj) |
|
1453 |
(let ((binds (wg-pickel-make-bindings-table obj))) |
|
1454 |
(list wg-pickel-identifier |
|
1455 |
(wg-pickel-serialize-objects binds) |
|
1456 |
(wg-pickel-serialize-links binds) |
|
1457 |
(gethash obj binds)))) |
|
1458 |
|
|
1459 |
(defun wg-pickel-to-string (obj) |
|
1460 |
"Serialize OBJ to a string and return the string." |
|
1461 |
(format "%S" (wg-pickel obj))) |
|
1462 |
|
|
1463 |
(defun wg-unpickel (pickel) |
|
1464 |
"Return the deserialization of PICKEL." |
|
1465 |
(unless (wg-pickel-p pickel) |
|
1466 |
(error "Attempt to unpickel a non-pickel.")) |
|
1467 |
(wg-dbind (id serial-objects serial-links result) pickel |
|
1468 |
(gethash |
|
1469 |
result |
|
1470 |
(wg-pickel-deserialize-links |
|
1471 |
serial-links |
|
1472 |
(wg-pickel-deserialize-objects serial-objects))))) |
|
1473 |
|
|
1474 |
;; `wg-pre-window-configuration-change-hook' implementation advice |
|
1475 |
(cl-macrolet ((define-p-w-c-c-h-advice |
|
1476 |
(fn) |
|
1477 |
`(defadvice ,fn (before wg-pre-window-configuration-change-hook) |
|
1478 |
"Call `wg-update-working-wconfig-hook' before this |
|
1479 |
function to save up-to-date undo information before the |
|
1480 |
window-configuration changes." |
|
1481 |
(run-hooks 'wg-pre-window-configuration-change-hook)))) |
|
1482 |
(define-p-w-c-c-h-advice split-window) |
|
1483 |
(define-p-w-c-c-h-advice enlarge-window) |
|
1484 |
(define-p-w-c-c-h-advice delete-window) |
|
1485 |
(define-p-w-c-c-h-advice delete-other-windows) |
|
1486 |
(define-p-w-c-c-h-advice delete-windows-on) |
|
1487 |
(define-p-w-c-c-h-advice switch-to-buffer) |
|
1488 |
(define-p-w-c-c-h-advice set-window-buffer)) |
|
1489 |
|
|
1490 |
|
|
1491 |
(defadvice save-buffers-kill-emacs (around wg-freeze-wconfig) |
|
1492 |
"`save-buffers-kill-emacs' calls `list-processes' when active |
|
1493 |
processes exist, screwing up the window config right before |
|
1494 |
Workgroups saves it. This advice freezes `wg-current-wconfig' in |
|
1495 |
its correct state, prior to any window-config changes caused by |
|
1496 |
`s-b-k-e'." |
|
1497 |
(wg-with-current-wconfig nil (wg-frame-to-wconfig) |
|
1498 |
ad-do-it)) |
|
1499 |
|
|
1500 |
(defadvice select-frame (before wg-update-current-workgroup-working-wconfig) |
|
1501 |
"Update `selected-frame's current workgroup's working-wconfig. |
|
1502 |
Before selecting a new frame." |
|
1503 |
(wg-update-current-workgroup-working-wconfig)) |
|
1504 |
|
|
1505 |
(defun wg-enable-all-advice () |
|
1506 |
"Enable and activate all of Workgroups' advice." |
|
1507 |
;; switch-to-buffer |
|
1508 |
(ad-enable-advice 'switch-to-buffer 'after 'wg-auto-associate-buffer) |
|
1509 |
(ad-enable-advice 'switch-to-buffer 'before 'wg-pre-window-configuration-change-hook) |
|
1510 |
(ad-activate 'switch-to-buffer) |
|
1511 |
|
|
1512 |
;; set-window-buffer |
|
1513 |
(ad-enable-advice 'set-window-buffer 'after 'wg-auto-associate-buffer) |
|
1514 |
(ad-enable-advice 'set-window-buffer 'before 'wg-pre-window-configuration-change-hook) |
|
1515 |
(ad-activate 'set-window-buffer) |
|
1516 |
|
|
1517 |
;; split-window |
|
1518 |
(ad-enable-advice 'split-window 'before 'wg-pre-window-configuration-change-hook) |
|
1519 |
(ad-activate 'split-window) |
|
1520 |
|
|
1521 |
;; enlarge-window |
|
1522 |
(ad-enable-advice 'enlarge-window 'before 'wg-pre-window-configuration-change-hook) |
|
1523 |
(ad-activate 'enlarge-window) |
|
1524 |
|
|
1525 |
;; delete-window |
|
1526 |
(ad-enable-advice 'delete-window 'before 'wg-pre-window-configuration-change-hook) |
|
1527 |
(ad-activate 'delete-window) |
|
1528 |
|
|
1529 |
;; delete-other-windows |
|
1530 |
(ad-enable-advice 'delete-other-windows 'before 'wg-pre-window-configuration-change-hook) |
|
1531 |
(ad-activate 'delete-other-windows) |
|
1532 |
|
|
1533 |
;; delete-windows-on |
|
1534 |
(ad-enable-advice 'delete-windows-on 'before 'wg-pre-window-configuration-change-hook) |
|
1535 |
(ad-activate 'delete-windows-on) |
|
1536 |
|
|
1537 |
;; save-buffers-kill-emacs |
|
1538 |
(ad-enable-advice 'save-buffers-kill-emacs 'around 'wg-freeze-wconfig) |
|
1539 |
(ad-activate 'save-buffers-kill-emacs) |
|
1540 |
|
|
1541 |
;; select-frame |
|
1542 |
;;(ad-enable-advice 'select-frame 'before |
|
1543 |
;; 'wg-update-current-workgroup-working-wconfig) |
|
1544 |
;;(ad-activate 'select-frame) |
|
1545 |
) |
|
1546 |
|
|
1547 |
|
|
1548 |
;; disable all advice |
|
1549 |
;; (wg-disable-all-advice) |
|
1550 |
(defun wg-disable-all-advice () |
|
1551 |
"Disable and deactivate all of Workgroups' advice." |
|
1552 |
;; switch-to-buffer |
|
1553 |
(ad-disable-advice 'switch-to-buffer 'after 'wg-auto-associate-buffer) |
|
1554 |
(ad-disable-advice 'switch-to-buffer 'before 'wg-pre-window-configuration-change-hook) |
|
1555 |
(ad-deactivate 'switch-to-buffer) |
|
1556 |
|
|
1557 |
;; set-window-buffer |
|
1558 |
(ad-disable-advice 'set-window-buffer 'after 'wg-auto-associate-buffer) |
|
1559 |
(ad-disable-advice 'set-window-buffer 'before 'wg-pre-window-configuration-change-hook) |
|
1560 |
(ad-deactivate 'set-window-buffer) |
|
1561 |
|
|
1562 |
;; split-window |
|
1563 |
(ad-disable-advice 'split-window 'before 'wg-pre-window-configuration-change-hook) |
|
1564 |
(ad-deactivate 'split-window) |
|
1565 |
|
|
1566 |
;; enlarge-window |
|
1567 |
(ad-disable-advice 'enlarge-window 'before 'wg-pre-window-configuration-change-hook) |
|
1568 |
(ad-deactivate 'enlarge-window) |
|
1569 |
|
|
1570 |
;; delete-window |
|
1571 |
(ad-disable-advice 'delete-window 'before 'wg-pre-window-configuration-change-hook) |
|
1572 |
(ad-deactivate 'delete-window) |
|
1573 |
|
|
1574 |
;; delete-other-windows |
|
1575 |
(ad-disable-advice 'delete-other-windows 'before 'wg-pre-window-configuration-change-hook) |
|
1576 |
(ad-deactivate 'delete-other-windows) |
|
1577 |
|
|
1578 |
;; delete-windows-on |
|
1579 |
(ad-disable-advice 'delete-windows-on 'before 'wg-pre-window-configuration-change-hook) |
|
1580 |
(ad-deactivate 'delete-windows-on) |
|
1581 |
|
|
1582 |
;; save-buffers-kill-emacs |
|
1583 |
(ad-disable-advice 'save-buffers-kill-emacs 'around 'wg-freeze-wconfig) |
|
1584 |
(ad-deactivate 'save-buffers-kill-emacs) |
|
1585 |
|
|
1586 |
;; select-frame |
|
1587 |
;;(ad-disable-advice 'select-frame 'before |
|
1588 |
;; 'wg-update-current-workgroup-working-wconfig) |
|
1589 |
;;(ad-deactivate 'select-frame) |
|
1590 |
) |
|
1591 |
|
|
1592 |
|
|
1593 |
;; buffer auto-association advice |
|
1594 |
|
|
1595 |
(defcustom wg-buffer-auto-association-on t |
|
1596 |
"Non-nil means buffer auto-association is on. |
|
1597 |
-nil means it's off. See `wg-buffer-auto-association'." |
|
1598 |
:type 'boolean |
|
1599 |
:group 'workgroups) |
|
1600 |
|
|
1601 |
(defcustom wg-buffer-auto-association 'weak |
|
1602 |
"Specifies the behavior for auto-associating buffers with workgroups. |
|
1603 |
|
|
1604 |
When a buffer is made visible in a window it can be automatically |
|
1605 |
associated with the current workgroup in the window's frame. |
|
1606 |
This setting determines whether and how that happens. |
|
1607 |
|
|
1608 |
Allowable values: |
|
1609 |
|
|
1610 |
`weak' - weakly associate the buffer with the workgroup |
|
1611 |
`strong' - strongly associate the buffer with the workgroup |
|
1612 |
|
|
1613 |
A function (a function-symbol or a lambda) - `funcall' the function to |
|
1614 |
determine whether and how to associate the buffer with the |
|
1615 |
workgroup. The function should accept two arguments -- the |
|
1616 |
buffer and the workgroup -- and should return one of the |
|
1617 |
allowable values for this variable. |
|
1618 |
|
|
1619 |
`nil' or any other value - don't associate the buffer with the |
|
1620 |
workgroup. |
|
1621 |
|
|
1622 |
Becomes workgroup-local when set with `wg-set-workgroup-parameter'. |
|
1623 |
Becomes session-local when set with `wg-set-session-parameter'." |
|
1624 |
:type 'sexp |
|
1625 |
:group 'workgroups) |
|
1626 |
|
|
1627 |
(defcustom wg-dissociate-buffer-on-kill-buffer t |
|
1628 |
"Non-nil means dissociate buffers killed with `kill-buffer'." |
|
1629 |
:type 'boolean |
|
1630 |
:group 'workgroups) |
|
1631 |
|
|
1632 |
(defun wg-auto-associate-buffer-helper (workgroup buffer assoc) |
|
1633 |
"Associate BUFFER with WORKGROUP based on ASSOC. |
|
1634 |
See `wg-buffer-auto-association' for allowable values of ASSOC." |
|
1635 |
(cond ((memq assoc '(weak strong)) |
|
1636 |
(wg-workgroup-associate-bufobj workgroup buffer (eq assoc 'weak))) |
|
1637 |
((functionp assoc) |
|
1638 |
(wg-auto-associate-buffer-helper |
|
1639 |
workgroup buffer (funcall assoc workgroup buffer))) |
|
1640 |
(t nil))) |
|
1641 |
|
|
1642 |
(defun wg-auto-associate-buffer (buffer &optional frame) |
|
1643 |
"Conditionally associate BUFFER with the current workgroup in FRAME. |
|
1644 |
Frame defaults to `selected-frame'. See `wg-buffer-auto-association'." |
|
1645 |
(when wg-buffer-auto-association-on |
|
1646 |
(-when-let* ((wg (wg-current-workgroup t frame)) |
|
1647 |
(b (get-buffer buffer))) |
|
1648 |
(unless (or (wg-workgroup-bufobj-association-type wg buffer) |
|
1649 |
(member wg wg-deactivation-list) |
|
1650 |
(member (buffer-name b) wg-associate-blacklist) |
|
1651 |
(not (or (buffer-file-name b) |
|
1652 |
(eq (buffer-local-value 'major-mode b) 'dired-mode)))) |
|
1653 |
(wg-auto-associate-buffer-helper |
|
1654 |
wg buffer (wg-local-value 'wg-buffer-auto-association wg)))))) |
|
1655 |
|
|
1656 |
(defadvice switch-to-buffer (after wg-auto-associate-buffer) |
|
1657 |
"Automatically associate the buffer with the current workgroup." |
|
1658 |
(wg-auto-associate-buffer ad-return-value)) |
|
1659 |
|
|
1660 |
(defadvice set-window-buffer (after wg-auto-associate-buffer) |
|
1661 |
"Automatically associate the buffer with the current workgroup." |
|
1662 |
(wg-auto-associate-buffer |
|
1663 |
(ad-get-arg 1) |
|
1664 |
(window-frame (or (ad-get-arg 0) (selected-window))))) |
|
1665 |
|
|
1666 |
(defun wg-mode-line-string () |
|
1667 |
"Return the string to be displayed in the mode-line." |
|
1668 |
(let ((wg (wg-current-workgroup t)) |
|
1669 |
(wg-use-faces wg-mode-line-use-faces)) |
|
1670 |
(cond (wg (wg-fontify " " |
|
1671 |
(:brace wg-mode-line-decor-left-brace) |
|
1672 |
(:mode (wg-workgroup-name wg)) |
|
1673 |
(if wg-flag-modified |
|
1674 |
(concat |
|
1675 |
(wg-add-face :div wg-mode-line-decor-divider) |
|
1676 |
;;(if (window-dedicated-p) |
|
1677 |
;; wg-mode-line-decor-window-dedicated |
|
1678 |
;; wg-mode-line-decor-window-undedicated) |
|
1679 |
;;(wg-add-face :div wg-mode-line-decor-divider) |
|
1680 |
(if (wg-session-modified (wg-current-session)) |
|
1681 |
wg-mode-line-decor-session-modified |
|
1682 |
wg-mode-line-decor-session-unmodified) |
|
1683 |
(if (wg-workgroup-modified wg) |
|
1684 |
wg-mode-line-decor-workgroup-modified |
|
1685 |
wg-mode-line-decor-workgroup-unmodified))) |
|
1686 |
(:brace wg-mode-line-decor-right-brace))) |
|
1687 |
(t (if wg-display-nowg |
|
1688 |
(wg-fontify " " |
|
1689 |
(:brace wg-mode-line-decor-left-brace) |
|
1690 |
(:mode wg-nowg-string) |
|
1691 |
(:brace wg-mode-line-decor-right-brace)) |
|
1692 |
""))))) |
|
1693 |
|
|
1694 |
(defun wg-change-modeline () |
|
1695 |
"Add Workgroups' mode-line format to `mode-line-format'." |
|
1696 |
(unless (assq 'wg-mode-line-display-on mode-line-format) |
|
1697 |
(let ((format '(wg-mode-line-display-on (:eval (wg-mode-line-string)))) |
|
1698 |
(pos (or (cl-position 'mode-line-position mode-line-format) 10))) |
|
1699 |
(set-default 'mode-line-format (-insert-at (1+ pos) format mode-line-format)) |
|
1700 |
(force-mode-line-update)))) |
|
1701 |
|
|
1702 |
(defun wg-remove-mode-line-display () |
|
1703 |
"Remove Workgroups' mode-line format from `mode-line-format'." |
|
1704 |
(awhen (assq 'wg-mode-line-display-on mode-line-format) |
|
1705 |
(set-default 'mode-line-format (remove it mode-line-format)) |
|
1706 |
(force-mode-line-update))) |
|
1707 |
|
|
1708 |
(defun wg-add-workgroups-mode-minor-mode-entries () |
|
1709 |
"Add Workgroups' minor-mode entries. |
|
1710 |
Adds entries to `minor-mode-list', `minor-mode-alist' and |
|
1711 |
`minor-mode-map-alist'." |
|
1712 |
(cl-pushnew 'workgroups-mode minor-mode-list) |
|
1713 |
(cl-pushnew '(workgroups-mode wg-modeline-string) minor-mode-alist :test 'equal) |
|
1714 |
(setq minor-mode-map-alist |
|
1715 |
(cons (cons 'workgroups-mode (wg-make-workgroups-mode-map)) |
|
1716 |
(delete (assoc 'workgroups-mode minor-mode-map-alist) |
|
1717 |
minor-mode-map-alist)))) |
|
1718 |
|
|
1719 |
(defun wg-fill-keymap (keymap &rest binds) |
|
1720 |
"Return KEYMAP after defining in it all keybindings in BINDS." |
|
1721 |
(while binds |
|
1722 |
(define-key keymap (car binds) (cadr binds)) |
|
1723 |
(setq binds (cddr binds))) |
|
1724 |
keymap) |
|
1725 |
|
|
1726 |
(defvar wg-prefixed-map |
|
1727 |
(wg-fill-keymap |
|
1728 |
(make-sparse-keymap) |
|
1729 |
|
|
1730 |
;; workgroups |
|
1731 |
(kbd "C-c") 'wg-create-workgroup |
|
1732 |
(kbd "c") 'wg-create-workgroup |
|
1733 |
(kbd "C") 'wg-clone-workgroup |
|
1734 |
(kbd "A") 'wg-rename-workgroup |
|
1735 |
(kbd "C-'") 'wg-switch-to-workgroup |
|
1736 |
(kbd "'") 'wg-switch-to-workgroup |
|
1737 |
(kbd "C-v") 'wg-switch-to-workgroup |
|
1738 |
(kbd "v") 'wg-switch-to-workgroup |
|
1739 |
|
|
1740 |
;; session |
|
1741 |
(kbd "C-s") 'wg-save-session |
|
1742 |
(kbd "C-w") 'wg-save-session-as |
|
1743 |
(kbd "C-f") 'wg-open-session |
|
1744 |
|
|
1745 |
;; killing and yanking |
|
1746 |
(kbd "C-k") 'wg-kill-workgroup |
|
1747 |
(kbd "k") 'wg-kill-workgroup |
|
1748 |
(kbd "M-W") 'wg-kill-ring-save-base-wconfig |
|
1749 |
(kbd "M-w") 'wg-kill-ring-save-working-wconfig |
|
1750 |
(kbd "C-y") 'wg-yank-wconfig |
|
1751 |
(kbd "y") 'wg-yank-wconfig |
|
1752 |
(kbd "M-k") 'wg-kill-workgroup-and-buffers |
|
1753 |
(kbd "K") 'wg-delete-other-workgroups |
|
1754 |
|
|
1755 |
|
|
1756 |
;; workgroup switching |
|
1757 |
(kbd "C-j") 'wg-switch-to-workgroup-at-index |
|
1758 |
(kbd "j") 'wg-switch-to-workgroup-at-index |
|
1759 |
(kbd "0") 'wg-switch-to-workgroup-at-index-0 |
|
1760 |
(kbd "1") 'wg-switch-to-workgroup-at-index-1 |
|
1761 |
(kbd "2") 'wg-switch-to-workgroup-at-index-2 |
|
1762 |
(kbd "3") 'wg-switch-to-workgroup-at-index-3 |
|
1763 |
(kbd "4") 'wg-switch-to-workgroup-at-index-4 |
|
1764 |
(kbd "5") 'wg-switch-to-workgroup-at-index-5 |
|
1765 |
(kbd "6") 'wg-switch-to-workgroup-at-index-6 |
|
1766 |
(kbd "7") 'wg-switch-to-workgroup-at-index-7 |
|
1767 |
(kbd "8") 'wg-switch-to-workgroup-at-index-8 |
|
1768 |
(kbd "9") 'wg-switch-to-workgroup-at-index-9 |
|
1769 |
(kbd "C-p") 'wg-switch-to-workgroup-left |
|
1770 |
(kbd "p") 'wg-switch-to-workgroup-left |
|
1771 |
(kbd "C-n") 'wg-switch-to-workgroup-right |
|
1772 |
(kbd "n") 'wg-switch-to-workgroup-right |
|
1773 |
(kbd "C-a") 'wg-switch-to-previous-workgroup |
|
1774 |
(kbd "a") 'wg-switch-to-previous-workgroup |
|
1775 |
|
|
1776 |
|
|
1777 |
;; updating and reverting |
|
1778 |
;; wconfig undo/redo |
|
1779 |
(kbd "C-r") 'wg-revert-workgroup |
|
1780 |
(kbd "r") 'wg-revert-workgroup |
|
1781 |
(kbd "C-S-r") 'wg-revert-all-workgroups |
|
1782 |
(kbd "R") 'wg-revert-all-workgroups |
|
1783 |
(kbd "<left>") 'wg-undo-wconfig-change |
|
1784 |
(kbd "<right>") 'wg-redo-wconfig-change |
|
1785 |
(kbd "[") 'wg-undo-wconfig-change |
|
1786 |
(kbd "]") 'wg-redo-wconfig-change |
|
1787 |
(kbd "{") 'wg-undo-once-all-workgroups |
|
1788 |
(kbd "}") 'wg-redo-once-all-workgroups |
|
1789 |
|
|
1790 |
|
|
1791 |
;; wconfig save/restore |
|
1792 |
(kbd "C-d C-s") 'wg-save-wconfig |
|
1793 |
(kbd "C-d C-'") 'wg-restore-saved-wconfig |
|
1794 |
(kbd "C-d C-k") 'wg-kill-saved-wconfig |
|
1795 |
|
|
1796 |
|
|
1797 |
;; workgroup movement |
|
1798 |
(kbd "C-x") 'wg-swap-workgroups |
|
1799 |
(kbd "C-,") 'wg-offset-workgroup-left |
|
1800 |
(kbd "C-.") 'wg-offset-workgroup-right |
|
1801 |
|
|
1802 |
|
|
1803 |
;; window moving and frame reversal |
|
1804 |
(kbd "|") 'wg-reverse-frame-horizontally |
|
1805 |
(kbd "\\") 'wg-reverse-frame-vertically |
|
1806 |
(kbd "/") 'wg-reverse-frame-horizontally-and-vertically |
|
1807 |
|
|
1808 |
|
|
1809 |
;; toggling |
|
1810 |
(kbd "C-t C-m") 'wg-toggle-mode-line-display |
|
1811 |
(kbd "C-t C-d") 'wg-toggle-window-dedicated-p |
|
1812 |
|
|
1813 |
|
|
1814 |
;; misc |
|
1815 |
(kbd "!") 'wg-reset |
|
1816 |
(kbd "?") 'wg-help |
|
1817 |
|
|
1818 |
) |
|
1819 |
"The keymap that sits on `wg-prefix-key'.") |
|
1820 |
|
|
1821 |
(defun wg-make-workgroups-mode-map () |
|
1822 |
"Return Workgroups' minor-mode-map. |
|
1823 |
This map includes `wg-prefixed-map' on `wg-prefix-key', as well |
|
1824 |
as Workgroups' command remappings." |
|
1825 |
(let ((map (make-sparse-keymap))) |
|
1826 |
(define-key map wg-prefix-key |
|
1827 |
wg-prefixed-map) |
|
1828 |
(when (and (fboundp 'winner-undo) |
|
1829 |
(fboundp 'winner-redo)) |
|
1830 |
(define-key map [remap winner-undo] 'wg-undo-wconfig-change) |
|
1831 |
(define-key map [remap winner-redo] 'wg-redo-wconfig-change)) |
|
1832 |
(setq workgroups-mode-map map))) |
|
1833 |
|
|
1834 |
|
|
1835 |
(defun wg-min-size (dir) |
|
1836 |
"Return the minimum window size in split direction DIR." |
|
1837 |
(if dir wg-window-min-height wg-window-min-width)) |
|
1838 |
|
|
1839 |
(defun wg-actual-min-size (dir) |
|
1840 |
"Return the actual minimum window size in split direction DIR." |
|
1841 |
(if dir wg-actual-min-height wg-actual-min-width)) |
|
1842 |
|
|
1843 |
(defmacro wg-with-edges (w spec &rest body) |
|
1844 |
"Bind W's edge list to SPEC and eval BODY." |
|
1845 |
(declare (indent 2)) |
|
1846 |
`(wg-dbind ,spec (wg-w-edges ,w) ,@body)) |
|
1847 |
|
|
1848 |
(defmacro wg-with-bounds (wtree dir spec &rest body) |
|
1849 |
"Bind SPEC to W's bounds in DIR, and eval BODY. |
|
1850 |
\"bounds\" are a direction-independent way of dealing with edge lists." |
|
1851 |
(declare (indent 3)) |
|
1852 |
(wg-with-gensyms (dir-sym l1 t1 r1 b1) |
|
1853 |
(wg-dbind (ls1 hs1 lb1 hb1) spec |
|
1854 |
`(wg-with-edges ,wtree (,l1 ,t1 ,r1 ,b1) |
|
1855 |
(cond (,dir (let ((,ls1 ,l1) (,hs1 ,r1) (,lb1 ,t1) (,hb1 ,b1)) |
|
1856 |
,@body)) |
|
1857 |
(t (let ((,ls1 ,t1) (,hs1 ,b1) (,lb1 ,l1) (,hb1 ,r1)) |
|
1858 |
,@body))))))) |
|
1859 |
|
|
1860 |
(defun wg-set-bounds (w dir ls hs lb hb) |
|
1861 |
"Set W's edges in DIR with bounds LS HS LB and HB." |
|
1862 |
(wg-set-edges w (if dir (list ls lb hs hb) (list lb ls hb hs)))) |
|
1863 |
|
|
1864 |
(defun wg-step-edges (edges1 edges2 hstep vstep) |
|
1865 |
"Return W1's edges stepped once toward W2's by HSTEP and VSTEP." |
|
1866 |
(wg-dbind (l1 t1 r1 b1) edges1 |
|
1867 |
(wg-dbind (l2 t2 r2 b2) edges2 |
|
1868 |
(let ((left (wg-step-to l1 l2 hstep)) |
|
1869 |
(top (wg-step-to t1 t2 vstep))) |
|
1870 |
(list left top |
|
1871 |
(+ left (wg-step-to (- r1 l1) (- r2 l2) hstep)) |
|
1872 |
(+ top (wg-step-to (- b1 t1) (- b2 t2) vstep))))))) |
|
1873 |
|
|
1874 |
(defun wg-w-edge-operation (w edges op) |
|
1875 |
"Return a copy of W with its edges mapped against EDGES through OP." |
|
1876 |
(wg-set-edges w (cl-mapcar op (wg-w-edges w) edges))) |
|
1877 |
|
|
1878 |
(defun wg-first-win (w) |
|
1879 |
"Return the first actual window in W." |
|
1880 |
(if (wg-win-p w) w |
|
1881 |
(wg-first-win (car (wg-wtree-wlist w))))) |
|
1882 |
|
|
1883 |
(defun wg-last-win (w) |
|
1884 |
"Return the last actual window in W." |
|
1885 |
(if (wg-win-p w) w |
|
1886 |
(wg-last-win (-last-item (wg-wtree-wlist w))))) |
|
1887 |
|
|
1888 |
(defun wg-minify-win (w) |
|
1889 |
"Set W's edges to the smallest allowable." |
|
1890 |
(let* ((edges (wg-w-edges w)) |
|
1891 |
(left (car edges)) |
|
1892 |
(top (cadr edges))) |
|
1893 |
(wg-set-edges w (list left top |
|
1894 |
(+ left wg-actual-min-width) |
|
1895 |
(+ top wg-actual-min-height))))) |
|
1896 |
|
|
1897 |
(defun wg-minified-copy-of-last-win (w) |
|
1898 |
"Minify a copy of the last actual window in W." |
|
1899 |
(wg-minify-win (wg-copy-win (wg-last-win w)))) |
|
1900 |
|
|
1901 |
(defun wg-w-size (w &optional height) |
|
1902 |
"Return the width or height of W, calculated from its edge list." |
|
1903 |
(wg-with-edges w (l1 t1 r1 b1) |
|
1904 |
(if height (- b1 t1) (- r1 l1)))) |
|
1905 |
|
|
1906 |
(defun wg-adjust-w-size (w width-fn height-fn &optional new-left new-top) |
|
1907 |
"Adjust W's width and height with WIDTH-FN and HEIGHT-FN." |
|
1908 |
(wg-with-edges w (left top right bottom) |
|
1909 |
(let ((left (or new-left left)) (top (or new-top top))) |
|
1910 |
(wg-set-edges (wg-copy-w w) |
|
1911 |
(list left |
|
1912 |
top |
|
1913 |
(+ left (funcall width-fn (- right left))) |
|
1914 |
(+ top (funcall height-fn (- bottom top)))))))) |
|
1915 |
|
|
1916 |
(defun wg-scale-w-size (w width-scale height-scale) |
|
1917 |
"Scale W's size by WIDTH-SCALE and HEIGHT-SCALE." |
|
1918 |
(cl-labels |
|
1919 |
((wscale (width) (truncate (* width width-scale))) |
|
1920 |
(hscale (height) (truncate (* height height-scale)))) |
|
1921 |
(wg-adjust-w-size w #'wscale #'hscale))) |
|
1922 |
|
|
1923 |
(defun wg-restore-window (win) |
|
1924 |
"Restore WIN in `selected-window'." |
|
1925 |
(let ((selwin (selected-window)) |
|
1926 |
(buf (wg-find-buf-by-uid (wg-win-buf-uid win)))) |
|
1927 |
(if (not buf) |
|
1928 |
(wg-restore-default-buffer) |
|
1929 |
(when (wg-restore-buffer buf t) |
|
1930 |
|
|
1931 |
;; Restore various positions in WINDOW from their values in WIN |
|
1932 |
;; (wg-restore-window-positions win selwin) |
|
1933 |
(let ((window (or selwin (selected-window)))) |
|
1934 |
(wg-with-slots win |
|
1935 |
((win-point wg-win-point) |
|
1936 |
(win-start wg-win-start) |
|
1937 |
(win-hscroll wg-win-hscroll)) |
|
1938 |
(set-window-start window win-start t) |
|
1939 |
(set-window-hscroll window win-hscroll) |
|
1940 |
(set-window-point |
|
1941 |
window |
|
1942 |
(cond ((not wg-restore-point) win-start) |
|
1943 |
((eq win-point :max) (point-max)) |
|
1944 |
(t win-point))) |
|
1945 |
(when (>= win-start (point-max)) (recenter)))) |
|
1946 |
|
|
1947 |
(when wg-restore-window-dedicated-p |
|
1948 |
(set-window-dedicated-p selwin (wg-win-dedicated win))))) |
|
1949 |
(ignore-errors |
|
1950 |
(set-window-prev-buffers |
|
1951 |
selwin (wg-unpickel (wg-win-parameter win 'prev-buffers))) |
|
1952 |
(set-window-next-buffers |
|
1953 |
selwin (wg-unpickel (wg-win-parameter win 'next-buffers))) |
|
1954 |
))) |
|
1955 |
|
|
1956 |
|
|
1957 |
(defun wg-window-point (ewin) |
|
1958 |
"Return `point' or :max. See `wg-restore-point-max'. |
|
1959 |
EWIN should be an Emacs window object." |
|
1960 |
(let ((p (window-point ewin))) |
|
1961 |
(if (and wg-restore-point-max (= p (point-max))) :max p))) |
|
1962 |
|
|
1963 |
(defun wg-win-parameter (win parameter &optional default) |
|
1964 |
"Return WIN's value for PARAMETER. |
|
1965 |
If PARAMETER is not found, return DEFAULT which defaults to nil. |
|
1966 |
SESSION nil defaults to the current session." |
|
1967 |
(wg-aget (wg-win-parameters win) parameter default)) |
|
1968 |
|
|
1969 |
(defun wg-set-win-parameter (win parameter value) |
|
1970 |
"Set WIN's value of PARAMETER to VALUE. |
|
1971 |
SESSION nil means use the current session. |
|
1972 |
Return value." |
|
1973 |
(wg-set-parameter (wg-win-parameters win) parameter value) |
|
1974 |
value) |
|
1975 |
;; (wg-win-parameters (wg-window-to-win (selected-window))) |
|
1976 |
|
|
1977 |
(defun wg-remove-win-parameter (win parameter) |
|
1978 |
"Remove parameter PARAMETER from WIN's parameters." |
|
1979 |
(wg-asetf (wg-win-parameters win) (wg-aremove it parameter))) |
|
1980 |
|
|
1981 |
(defun wg-window-to-win (&optional window) |
|
1982 |
"Return the serialization (a wg-win) of Emacs window WINDOW." |
|
1983 |
(let ((window (or window (selected-window))) |
|
1984 |
(selected (eq window (selected-window))) |
|
1985 |
win) |
|
1986 |
(with-selected-window window |
|
1987 |
(setq win |
|
1988 |
(wg-make-win |
|
1989 |
:edges (window-edges window) |
|
1990 |
:point (wg-window-point window) |
|
1991 |
:start (window-start window) |
|
1992 |
:hscroll (window-hscroll window) |
|
1993 |
:selected selected |
|
1994 |
:minibuffer-scroll (eq window minibuffer-scroll-window) |
|
1995 |
:dedicated (window-dedicated-p window) |
|
1996 |
:buf-uid (wg-buffer-uid-or-add (window-buffer window)))) |
|
1997 |
(unless (version< emacs-version "24") |
|
1998 |
;; To solve: https://github.com/pashinin/workgroups2/issues/51 |
|
1999 |
;; shouldn't ignore here |
|
2000 |
(ignore-errors |
|
2001 |
(wg-set-win-parameter |
|
2002 |
win 'next-buffers (wg-pickel (remove nil (cl-subseq (window-next-buffers window) 0 4)))) |
|
2003 |
(wg-set-win-parameter |
|
2004 |
win 'prev-buffers (wg-pickel (remove nil (cl-subseq (window-prev-buffers window) 0 4))))))) |
|
2005 |
win)) |
|
2006 |
|
|
2007 |
(defun wg-toggle-window-dedicated-p () |
|
2008 |
"Toggle `window-dedicated-p' in `selected-window'." |
|
2009 |
(interactive) |
|
2010 |
(set-window-dedicated-p nil (not (window-dedicated-p))) |
|
2011 |
(force-mode-line-update t) |
|
2012 |
(wg-fontified-message |
|
2013 |
(:cmd "Window:") |
|
2014 |
(:cur (concat (unless (window-dedicated-p) " not") " dedicated")))) |
|
2015 |
|
|
2016 |
(defun wg-w-edges (w) |
|
2017 |
"Return W's edge list." |
|
2018 |
(cl-etypecase w |
|
2019 |
(wg-win (wg-win-edges w)) |
|
2020 |
(wg-wtree (wg-wtree-edges w)))) |
|
2021 |
|
|
2022 |
(defun wg-copy-w (w) |
|
2023 |
"Return a copy of W. W should be a wg-win or a wg-wtree." |
|
2024 |
(cl-etypecase w |
|
2025 |
(wg-win (wg-copy-win w)) |
|
2026 |
(wg-wtree (wg-copy-wtree w)))) |
|
2027 |
|
|
2028 |
(defun wg-set-edges (w edges) |
|
2029 |
"Set W's EDGES list, and return W." |
|
2030 |
(cl-etypecase w |
|
2031 |
(wg-win (setf (wg-win-edges w) edges)) |
|
2032 |
(wg-wtree (setf (wg-wtree-edges w) edges))) |
|
2033 |
w) |
|
2034 |
|
|
2035 |
(defun wg-equal-wtrees (w1 w2) |
|
2036 |
"Return t when W1 and W2 have equal structure." |
|
2037 |
(cond ((and (wg-win-p w1) (wg-win-p w2)) |
|
2038 |
(equal (wg-w-edges w1) (wg-w-edges w2))) |
|
2039 |
((and (wg-wtree-p w1) (wg-wtree-p w2)) |
|
2040 |
(and (eq (wg-wtree-dir w1) (wg-wtree-dir w2)) |
|
2041 |
(equal (wg-wtree-edges w1) (wg-wtree-edges w2)) |
|
2042 |
(cl-every #'wg-equal-wtrees |
|
2043 |
(wg-wtree-wlist w1) |
|
2044 |
(wg-wtree-wlist w2)))))) |
|
2045 |
|
|
2046 |
(defun wg-normalize-wtree (wtree) |
|
2047 |
"Clean up and return a new wtree from WTREE. |
|
2048 |
Recalculate the edge lists of all subwins, and remove subwins |
|
2049 |
outside of WTREE's bounds. If there's only one element in the |
|
2050 |
new wlist, return it instead of a new wtree." |
|
2051 |
(if (wg-win-p wtree) wtree |
|
2052 |
(wg-with-slots wtree ((dir wg-wtree-dir) |
|
2053 |
(wlist wg-wtree-wlist)) |
|
2054 |
(wg-with-bounds wtree dir (ls1 hs1 lb1 hb1) |
|
2055 |
(let* ((min-size (wg-min-size dir)) |
|
2056 |
(max (- hb1 1 min-size)) |
|
2057 |
(lastw (-last-item wlist))) |
|
2058 |
(cl-labels |
|
2059 |
((mapwl |
|
2060 |
(wl) |
|
2061 |
(wg-dbind (sw . rest) wl |
|
2062 |
(cons (wg-normalize-wtree |
|
2063 |
(wg-set-bounds |
|
2064 |
sw dir ls1 hs1 lb1 |
|
2065 |
(setq lb1 (if (eq sw lastw) hb1 |
|
2066 |
(let ((hb2 (+ lb1 (wg-w-size sw dir)))) |
|
2067 |
(if (>= hb2 max) hb1 hb2)))))) |
|
2068 |
(when (< lb1 max) (mapwl rest)))))) |
|
2069 |
(let ((new (mapwl wlist))) |
|
2070 |
(if (not (cdr new)) (car new) |
|
2071 |
(setf (wg-wtree-wlist wtree) new) |
|
2072 |
wtree)))))))) |
|
2073 |
|
|
2074 |
(defun wg-scale-wtree (wtree wscale hscale) |
|
2075 |
"Return a copy of WTREE with its dimensions scaled by WSCALE and HSCALE. |
|
2076 |
All WTREE's subwins are scaled as well." |
|
2077 |
(let ((scaled (wg-scale-w-size wtree wscale hscale))) |
|
2078 |
(if (wg-win-p wtree) scaled |
|
2079 |
(wg-asetf (wg-wtree-wlist scaled) |
|
2080 |
(wg-docar (sw it) (wg-scale-wtree sw wscale hscale))) |
|
2081 |
scaled))) |
|
2082 |
|
|
2083 |
|
|
2084 |
(defun wg-resize-frame-scale-wtree (wconfig) |
|
2085 |
"Set FRAME's size to WCONFIG's, returning a possibly scaled wtree. |
|
2086 |
If the frame size was set correctly, return WCONFIG's wtree |
|
2087 |
unchanged. If it wasn't, return a copy of WCONFIG's wtree scaled |
|
2088 |
with `wg-scale-wconfigs-wtree' to fit the frame as it exists." |
|
2089 |
(let ((frame (selected-frame))) |
|
2090 |
(wg-with-slots wconfig ((wcwidth wg-wconfig-width) |
|
2091 |
(wcheight wg-wconfig-height)) |
|
2092 |
(when window-system (set-frame-size frame wcwidth wcheight)) |
|
2093 |
(let ((fwidth (frame-parameter frame 'width)) |
|
2094 |
(fheight (frame-parameter frame 'height))) |
|
2095 |
(if (and (= wcwidth fwidth) (= wcheight fheight)) |
|
2096 |
(wg-wconfig-wtree wconfig) |
|
2097 |
(wg-scale-wconfigs-wtree wconfig fwidth fheight)))))) |
|
2098 |
|
|
2099 |
(defun wg-wtree-buf-uids (wtree) |
|
2100 |
"Return a new list of the buf uids of all wins in WTREE." |
|
2101 |
(if (not wtree) |
|
2102 |
(error "WTREE is nil in `wg-wtree-buf-uids'!")) |
|
2103 |
(wg-flatten-wtree wtree 'wg-win-buf-uid)) |
|
2104 |
|
|
2105 |
|
|
2106 |
(defun wg-wtree-unique-buf-uids (wtree) |
|
2107 |
"Return a list of the unique buf uids of all wins in WTREE." |
|
2108 |
(cl-remove-duplicates (wg-wtree-buf-uids wtree) :test 'string=)) |
|
2109 |
|
|
2110 |
|
|
2111 |
(defun wg-reset-window-tree () |
|
2112 |
"Delete all but one window in `selected-frame', and reset |
|
2113 |
various parameters of that window in preparation for restoring |
|
2114 |
a wtree." |
|
2115 |
(delete-other-windows) |
|
2116 |
(set-window-dedicated-p nil nil)) |
|
2117 |
|
|
2118 |
(defun wg-restore-window-tree-helper (w) |
|
2119 |
"Recursion helper for `wg-restore-window-tree' W." |
|
2120 |
(if (wg-wtree-p w) |
|
2121 |
(cl-loop with dir = (wg-wtree-dir w) |
|
2122 |
for (win . rest) on (wg-wtree-wlist w) |
|
2123 |
do (when rest (split-window nil (wg-w-size win dir) (not dir))) |
|
2124 |
do (wg-restore-window-tree-helper win)) |
|
2125 |
(wg-restore-window w) |
|
2126 |
(when (wg-win-selected w) |
|
2127 |
(setq wg-window-tree-selected-window (selected-window))) |
|
2128 |
(when (wg-win-minibuffer-scroll w) |
|
2129 |
(setq minibuffer-scroll-window (selected-window))) |
|
2130 |
(other-window 1))) |
|
2131 |
|
|
2132 |
(defun wg-restore-window-tree (wtree) |
|
2133 |
"Restore WTREE in `selected-frame'." |
|
2134 |
(let ((window-min-width wg-window-min-width) |
|
2135 |
(window-min-height wg-window-min-height) |
|
2136 |
(wg-window-tree-selected-window nil)) |
|
2137 |
(wg-reset-window-tree) |
|
2138 |
(wg-restore-window-tree-helper wtree) |
|
2139 |
(awhen wg-window-tree-selected-window (select-window it)))) |
|
2140 |
|
|
2141 |
(defun wg-window-tree-to-wtree (&optional window-tree) |
|
2142 |
"Return the serialization (a wg-wtree) of Emacs window tree WINDOW-TREE." |
|
2143 |
(wg-barf-on-active-minibuffer) |
|
2144 |
(unless window-tree |
|
2145 |
(setq window-tree (window-tree))) |
|
2146 |
(cl-labels |
|
2147 |
((inner (w) (if (windowp w) (wg-window-to-win w) |
|
2148 |
(wg-dbind (dir edges . wins) w |
|
2149 |
(wg-make-wtree |
|
2150 |
:dir dir |
|
2151 |
:edges edges |
|
2152 |
:wlist (mapcar #'inner wins)))))) |
|
2153 |
(let ((w (car window-tree))) |
|
2154 |
(when (and (windowp w) (window-minibuffer-p w)) |
|
2155 |
(error "Workgroups can't operate on minibuffer-only frames.")) |
|
2156 |
(inner w)))) |
|
2157 |
|
|
2158 |
|
|
2159 |
(defun wg-flatten-wtree (wtree &optional key) |
|
2160 |
"Return a new list by flattening WTREE. |
|
2161 |
KEY non returns returns a list of WTREE's wins. |
|
2162 |
KEY non-nil returns a list of the results of calling KEY on each win." |
|
2163 |
(cl-labels |
|
2164 |
((inner (w) (if (wg-win-p w) (list (if key (funcall key w) w)) |
|
2165 |
(cl-mapcan #'inner (wg-wtree-wlist w))))) |
|
2166 |
(inner wtree))) |
|
2167 |
|
|
2168 |
(defun wg-reverse-wlist (w &optional dir) |
|
2169 |
"Reverse W's wlist and those of all its sub-wtrees in direction DIR. |
|
2170 |
If DIR is nil, reverse WTREE horizontally. |
|
2171 |
If DIR is 'both, reverse WTREE both horizontally and vertically. |
|
2172 |
Otherwise, reverse WTREE vertically." |
|
2173 |
(cl-labels |
|
2174 |
((inner (w) (if (wg-win-p w) w |
|
2175 |
(wg-with-slots w ((d1 wg-wtree-dir)) |
|
2176 |
(wg-make-wtree |
|
2177 |
:dir d1 |
|
2178 |
:edges (wg-wtree-edges w) |
|
2179 |
:wlist (let ((wl2 (mapcar #'inner (wg-wtree-wlist w)))) |
|
2180 |
(if (or (eq dir 'both) (eq dir d1)) |
|
2181 |
(nreverse wl2) |
|
2182 |
wl2))))))) |
|
2183 |
(wg-normalize-wtree (inner w)))) |
|
2184 |
|
|
2185 |
(defun wg-wtree-move-window (wtree offset) |
|
2186 |
"Offset `selected-window' OFFSET places in WTREE." |
|
2187 |
(cl-labels |
|
2188 |
((inner (w) (if (wg-win-p w) w |
|
2189 |
(wg-with-slots w ((wlist wg-wtree-wlist)) |
|
2190 |
(wg-make-wtree |
|
2191 |
:dir (wg-wtree-dir w) |
|
2192 |
:edges (wg-wtree-edges w) |
|
2193 |
:wlist (aif (cl-find t wlist :key 'wg-win-selected) |
|
2194 |
(wg-cyclic-offset-elt it wlist offset) |
|
2195 |
(mapcar #'inner wlist))))))) |
|
2196 |
(wg-normalize-wtree (inner wtree)))) |
|
2197 |
|
|
2198 |
(defun wg-frame-to-wconfig (&optional frame) |
|
2199 |
"Return the serialization (a wg-wconfig) of Emacs frame FRAME. |
|
2200 |
FRAME nil defaults to `selected-frame'." |
|
2201 |
(let* ((frame (or frame (selected-frame))) |
|
2202 |
(fullscrn (frame-parameter frame 'fullscreen))) |
|
2203 |
(wg-make-wconfig |
|
2204 |
:left (frame-parameter frame 'left) |
|
2205 |
:top (frame-parameter frame 'top) |
|
2206 |
:width (frame-parameter frame 'width) |
|
2207 |
:height (frame-parameter frame 'height) |
|
2208 |
:parameters `((fullscreen . ,fullscrn)) |
|
2209 |
:vertical-scroll-bars (frame-parameter frame 'vertical-scroll-bars) |
|
2210 |
:scroll-bar-width (frame-parameter frame 'scroll-bar-width) |
|
2211 |
:wtree (wg-window-tree-to-wtree (window-tree frame)) |
|
2212 |
))) |
|
2213 |
|
|
2214 |
(defun wg-current-wconfig () |
|
2215 |
"Return the current wconfig. |
|
2216 |
If `wg-current-wconfig' is non-nil, return it. Otherwise return |
|
2217 |
`wg-frame-to-wconfig'." |
|
2218 |
(or (frame-parameter nil 'wg-current-wconfig) |
|
2219 |
(wg-frame-to-wconfig))) |
|
2220 |
|
|
2221 |
(defmacro wg-with-current-wconfig (frame wconfig &rest body) |
|
2222 |
"Eval BODY with WCONFIG current in FRAME. |
|
2223 |
FRAME nil defaults to `selected-frame'." |
|
2224 |
(declare (indent 2)) |
|
2225 |
(wg-with-gensyms (frame-sym old-value) |
|
2226 |
`(let* ((,frame-sym (or ,frame (selected-frame))) |
|
2227 |
(,old-value (frame-parameter ,frame-sym 'wg-current-wconfig))) |
|
2228 |
(unwind-protect |
|
2229 |
(progn |
|
2230 |
(set-frame-parameter ,frame-sym 'wg-current-wconfig ,wconfig) |
|
2231 |
,@body) |
|
2232 |
(when (frame-live-p ,frame-sym) |
|
2233 |
(set-frame-parameter ,frame-sym 'wg-current-wconfig ,old-value)))))) |
|
2234 |
|
|
2235 |
(defun wg-make-blank-wconfig (&optional buffer) |
|
2236 |
"Return a new blank wconfig. |
|
2237 |
BUFFER or `wg-default-buffer' is visible in the only window." |
|
2238 |
(save-window-excursion |
|
2239 |
(delete-other-windows) |
|
2240 |
(switch-to-buffer (or buffer wg-default-buffer)) |
|
2241 |
(wg-frame-to-wconfig))) |
|
2242 |
|
|
2243 |
(defun wg-wconfig-move-window (wconfig offset) |
|
2244 |
"Offset `selected-window' OFFSET places in WCONFIG." |
|
2245 |
(wg-asetf (wg-wconfig-wtree wconfig) (wg-wtree-move-window it offset)) |
|
2246 |
wconfig) |
|
2247 |
|
|
2248 |
|
|
2249 |
;;; base wconfig updating |
|
2250 |
|
|
2251 |
(defun wg-update-working-wconfig-on-delete-frame (frame) |
|
2252 |
"Update FRAME's current workgroup's working-wconfig before |
|
2253 |
FRAME is deleted, so we don't lose its state." |
|
2254 |
(wg-flag-session-modified) |
|
2255 |
(with-selected-frame frame |
|
2256 |
(wg-update-current-workgroup-working-wconfig))) |
|
2257 |
|
|
2258 |
(defun wg-update-working-wconfig-on-make-frame (frame) |
|
2259 |
"Update FRAME's current workgroup's working-wconfig before |
|
2260 |
FRAME is deleted, so we don't lose its state." |
|
2261 |
(if (> (length (frame-list)) 1) |
|
2262 |
(wg-flag-session-modified)) |
|
2263 |
;;(with-selected-frame frame |
|
2264 |
;; (wg-update-current-workgroup-working-wconfig)) |
|
2265 |
) |
|
2266 |
|
|
2267 |
(defun wg-wconfig-buf-uids (wconfig) |
|
2268 |
"Return WCONFIG's wtree's `wg-wtree-buf-uids'." |
|
2269 |
(if (not (wg-wconfig-wtree wconfig)) |
|
2270 |
(error "WTREE is nil in `wg-wconfig-buf-uids'!")) |
|
2271 |
(wg-wtree-unique-buf-uids (wg-wconfig-wtree wconfig))) |
|
2272 |
|
|
2273 |
|
|
2274 |
|
|
2275 |
|
|
2276 |
|
|
2277 |
(defun wg-wconfig-restore-frame-position (wconfig &optional frame) |
|
2278 |
"Use WCONFIG to restore FRAME's position. |
|
2279 |
If frame is nil then `selected-frame'." |
|
2280 |
(-when-let* ((left (wg-wconfig-left wconfig)) |
|
2281 |
(top (wg-wconfig-top wconfig))) |
|
2282 |
;; Check that arguments are integers |
|
2283 |
;; Problem: https://github.com/pashinin/workgroups2/issues/15 |
|
2284 |
(if (and (integerp left) |
|
2285 |
(integerp top)) |
|
2286 |
(set-frame-position frame left top)))) |
|
2287 |
|
|
2288 |
(defun wg-wconfig-restore-scroll-bars (wconfig) |
|
2289 |
"Restore `selected-frame's scroll-bar settings from WCONFIG." |
|
2290 |
(set-frame-parameter |
|
2291 |
nil 'vertical-scroll-bars (wg-wconfig-vertical-scroll-bars wconfig)) |
|
2292 |
(set-frame-parameter |
|
2293 |
nil 'scroll-bar-width (wg-wconfig-scroll-bar-width wconfig))) |
|
2294 |
|
|
2295 |
;;(defun wg-wconfig-restore-fullscreen (wconfig) |
|
2296 |
;; "Restore `selected-frame's fullscreen settings from WCONFIG." |
|
2297 |
;; (set-frame-parameter |
|
2298 |
;; nil 'fullscreen (wg-wconfig-parameters wconfig)) |
|
2299 |
;; ) |
|
2300 |
|
|
2301 |
(defun wg-scale-wconfigs-wtree (wconfig new-width new-height) |
|
2302 |
"Scale WCONFIG's wtree with NEW-WIDTH and NEW-HEIGHT. |
|
2303 |
Return a copy WCONFIG's wtree scaled with `wg-scale-wtree' by the |
|
2304 |
ratio or NEW-WIDTH to WCONFIG's width, and NEW-HEIGHT to |
|
2305 |
WCONFIG's height." |
|
2306 |
(wg-normalize-wtree |
|
2307 |
(wg-scale-wtree |
|
2308 |
(wg-wconfig-wtree wconfig) |
|
2309 |
(/ (float new-width) (wg-wconfig-width wconfig)) |
|
2310 |
(/ (float new-height) (wg-wconfig-height wconfig))))) |
|
2311 |
|
|
2312 |
(defun wg-scale-wconfig-to-frame (wconfig) |
|
2313 |
"Scale WCONFIG buffers to fit current frame size. |
|
2314 |
Return a scaled copy of WCONFIG." |
|
2315 |
(interactive) |
|
2316 |
(wg-scale-wconfigs-wtree wconfig |
|
2317 |
(frame-parameter nil 'width) |
|
2318 |
(frame-parameter nil 'height))) |
|
2319 |
|
|
2320 |
(defun wg-frame-resize-and-position (wconfig &optional frame) |
|
2321 |
"Apply WCONFIG's size and position to a FRAME." |
|
2322 |
(interactive) |
|
2323 |
(unless frame (setq frame (selected-frame))) |
|
2324 |
(let* ((params (wg-wconfig-parameters wconfig)) |
|
2325 |
fullscreen) |
|
2326 |
(set-frame-parameter frame 'fullscreen (if (assoc 'fullscreen params) |
|
2327 |
(cdr (assoc 'fullscreen params)) |
|
2328 |
nil)) |
|
2329 |
(when (and wg-restore-frame-position |
|
2330 |
(not (frame-parameter frame 'fullscreen))) |
|
2331 |
(wg-wconfig-restore-frame-position wconfig frame)) |
|
2332 |
)) |
|
2333 |
|
|
2334 |
(defun wg-restore-frame-size-position (wconfig &optional fs) |
|
2335 |
"Smart-restore of frame size and position. |
|
2336 |
|
|
2337 |
Depending on `wg-remember-frame-for-each-wg' frame parameters may |
|
2338 |
be restored for each workgroup. |
|
2339 |
|
|
2340 |
If `wg-remember-frame-for-each-wg' is nil (by default) then |
|
2341 |
current frame parameters are saved/restored to/from first |
|
2342 |
workgroup. And frame parameters for all other workgroups are just |
|
2343 |
ignored. |
|
2344 |
" |
|
2345 |
(interactive) |
|
2346 |
(let* ((params (wg-wconfig-parameters wconfig)) |
|
2347 |
fullscreen) |
|
2348 |
;; Frame maximized / fullscreen / none |
|
2349 |
(unless wg-remember-frame-for-each-wg |
|
2350 |
(setq params (wg-wconfig-parameters (wg-workgroup-working-wconfig (wg-first-workgroup))))) |
|
2351 |
(setq fullscreen (if (assoc 'fullscreen params) |
|
2352 |
(cdr (assoc 'fullscreen params)) |
|
2353 |
nil)) |
|
2354 |
(when (and fs |
|
2355 |
fullscreen |
|
2356 |
(or wg-remember-frame-for-each-wg |
|
2357 |
(null (wg-current-workgroup t)))) |
|
2358 |
(set-frame-parameter nil 'fullscreen fullscreen) |
|
2359 |
;; I had bugs restoring maximized frame: |
|
2360 |
;; Frame could be maximized but buffers are not scaled to fit it. |
|
2361 |
;; |
|
2362 |
;; Maybe because of `set-frame-parameter' takes some time to finish and is async. |
|
2363 |
;; So I tried this and it helped |
|
2364 |
(sleep-for 0 100)) |
|
2365 |
|
|
2366 |
;; Position |
|
2367 |
(when (and wg-restore-frame-position |
|
2368 |
wg-remember-frame-for-each-wg |
|
2369 |
(not (frame-parameter nil 'fullscreen))) |
|
2370 |
(wg-wconfig-restore-frame-position wconfig)) |
|
2371 |
)) |
|
2372 |
|
|
2373 |
|
|
2374 |
(defun wg-restore-frames () |
|
2375 |
"Try to recreate opened frames, take info from session's 'frame-list parameter." |
|
2376 |
(interactive) |
|
2377 |
(delete-other-frames) |
|
2378 |
(awhen (wg-current-session t) |
|
2379 |
(let ((fl (wg-session-parameter 'frame-list nil it)) |
|
2380 |
(frame (selected-frame))) |
|
2381 |
(mapc (lambda (wconfig) |
|
2382 |
(with-selected-frame (make-frame) |
|
2383 |
;;(wg-frame-resize-and-position wconfig) |
|
2384 |
;;(wg-restore-frame-size-position wconfig) |
|
2385 |
;;(wg-wconfig-restore-frame-position wconfig) |
|
2386 |
(wg-restore-wconfig wconfig) |
|
2387 |
)) fl) |
|
2388 |
(select-frame-set-input-focus frame)))) |
|
2389 |
|
|
2390 |
;; FIXME: throw a specific error if the restoration was unsuccessful |
|
2391 |
(defun wg-restore-wconfig (wconfig &optional frame) |
|
2392 |
"Restore a workgroup configuration WCONFIG in a FRAME. |
|
2393 |
Runs each time you're switching workgroups." |
|
2394 |
(unless frame (setq frame (selected-frame))) |
|
2395 |
(let ((wg-record-incorrectly-restored-bufs t) |
|
2396 |
(wg-incorrectly-restored-bufs nil) |
|
2397 |
(params (wg-wconfig-parameters wconfig)) |
|
2398 |
fullscreen) |
|
2399 |
(wg-barf-on-active-minibuffer) |
|
2400 |
(when wg-restore-scroll-bars |
|
2401 |
(wg-wconfig-restore-scroll-bars wconfig)) |
|
2402 |
|
|
2403 |
(when (null (wg-current-workgroup t)) |
|
2404 |
(set-frame-parameter frame 'fullscreen (if (assoc 'fullscreen params) |
|
2405 |
(cdr (assoc 'fullscreen params)) |
|
2406 |
nil))) |
|
2407 |
|
|
2408 |
;; Restore frame position |
|
2409 |
(when (and wg-restore-frame-position |
|
2410 |
(not (frame-parameter nil 'fullscreen)) |
|
2411 |
(null (wg-current-workgroup t))) |
|
2412 |
(wg-wconfig-restore-frame-position wconfig frame)) |
|
2413 |
|
|
2414 |
;; Restore buffers |
|
2415 |
(wg-restore-window-tree (wg-scale-wconfig-to-frame wconfig)) |
|
2416 |
|
|
2417 |
(when wg-incorrectly-restored-bufs |
|
2418 |
(message "Unable to restore these buffers: %S\ |
|
2419 |
If you want, restore them manually and try again." |
|
2420 |
(mapcar 'wg-buf-name wg-incorrectly-restored-bufs))))) |
|
2421 |
|
|
2422 |
|
|
2423 |
;;; saved wconfig commands |
|
2424 |
|
|
2425 |
(defun wg-save-wconfig () |
|
2426 |
"Save the current wconfig to the current workgroup's saved wconfigs." |
|
2427 |
(interactive) |
|
2428 |
(let* ((workgroup (wg-current-workgroup)) |
|
2429 |
(name (wg-read-saved-wconfig-name workgroup)) |
|
2430 |
(wconfig (wg-current-wconfig))) |
|
2431 |
(setf (wg-wconfig-name wconfig) name) |
|
2432 |
(wg-workgroup-save-wconfig wconfig workgroup) |
|
2433 |
(wg-fontified-message |
|
2434 |
(:cmd "Saved: ") |
|
2435 |
(:cur name)))) |
|
2436 |
|
|
2437 |
(defun wg-restore-saved-wconfig () |
|
2438 |
"Restore one of the current workgroup's saved wconfigs in `selected-frame'." |
|
2439 |
(interactive) |
|
2440 |
(let ((workgroup (wg-current-workgroup))) |
|
2441 |
(wg-restore-wconfig-undoably |
|
2442 |
(wg-workgroup-get-saved-wconfig |
|
2443 |
(wg-completing-read "Saved wconfig: " |
|
2444 |
(mapcar 'wg-wconfig-name (wg-workgroup-saved-wconfigs workgroup)) |
|
2445 |
nil t) |
|
2446 |
workgroup)))) |
|
2447 |
|
|
2448 |
(defun wg-kill-saved-wconfig () |
|
2449 |
"Kill one of the current workgroup's saved wconfigs. |
|
2450 |
Also add it to the wconfig kill-ring." |
|
2451 |
(interactive) |
|
2452 |
(let* ((workgroup (wg-current-workgroup)) |
|
2453 |
(wconfig (wg-read-saved-wconfig workgroup))) |
|
2454 |
(wg-workgroup-kill-saved-wconfig workgroup wconfig) |
|
2455 |
(wg-add-to-wconfig-kill-ring wconfig) |
|
2456 |
(wg-fontified-message |
|
2457 |
(:cmd "Deleted: ") |
|
2458 |
(:cur (wg-wconfig-name wconfig))))) |
|
2459 |
|
|
2460 |
|
|
2461 |
(defun wg-reverse-wconfig (wconfig &optional dir) |
|
2462 |
"Reverse WCONFIG's wtree's wlist in direction DIR." |
|
2463 |
(wg-asetf (wg-wconfig-wtree wconfig) (wg-reverse-wlist it dir)) |
|
2464 |
wconfig) |
|
2465 |
|
|
2466 |
|
|
2467 |
;; specialbufs |
|
2468 |
(defcustom wg-special-buffer-serdes-functions |
|
2469 |
'(wg-serialize-comint-buffer |
|
2470 |
) |
|
2471 |
"Functions providing serialization/deserialization for complex buffers. |
|
2472 |
|
|
2473 |
Use `wg-support' macro and this variable will be filled |
|
2474 |
automatically. |
|
2475 |
|
|
2476 |
An entry should be either a function symbol or a lambda, and should |
|
2477 |
accept a single Emacs buffer object as an argument. |
|
2478 |
|
|
2479 |
When a buffer is to be serialized, it is passed to each of these |
|
2480 |
functions in turn until one returns non-nil, or the list ends. A |
|
2481 |
return value of nil indicates that the function can't handle |
|
2482 |
buffers of that type. A non-nil return value indicates that it |
|
2483 |
can. The first non-nil return value becomes the buffer's special |
|
2484 |
serialization data. The return value should be a cons, with a |
|
2485 |
deserialization function (a function symbol or a lambda) as the car, |
|
2486 |
and any other serialization data as the cdr. |
|
2487 |
|
|
2488 |
When it comes time to deserialize the buffer, the deserialization |
|
2489 |
function (the car of the cons mentioned above) is passed the |
|
2490 |
wg-buf object, from which it should restore the buffer. The |
|
2491 |
special serialization data itself can be accessed |
|
2492 |
with (cdr (wg-buf-special-data <wg-buf>)). The deserialization |
|
2493 |
function must return the restored Emacs buffer object. |
|
2494 |
|
|
2495 |
See the definitions of the functions in this list for examples of |
|
2496 |
how to write your own." |
|
2497 |
:type 'alist |
|
2498 |
:group 'workgroups) |
|
2499 |
|
|
2500 |
;; Dired |
|
2501 |
(wg-support 'dired-mode 'dired |
|
2502 |
`((deserialize . ,(lambda (buffer vars) |
|
2503 |
(when (or wg-restore-remote-buffers |
|
2504 |
(not (file-remote-p default-directory))) |
|
2505 |
(let ((d (wg-get-first-existing-dir))) |
|
2506 |
(if (file-directory-p d) (dired d)))))))) |
|
2507 |
|
|
2508 |
;; `Info-mode' C-h i |
|
2509 |
(wg-support 'Info-mode 'info |
|
2510 |
`((save . (Info-current-file Info-current-node)) |
|
2511 |
(deserialize . ,(lambda (buffer vars) |
|
2512 |
;;(with-current-buffer |
|
2513 |
;; (get-buffer-create (wg-buf-name buffer)) |
|
2514 |
(aif vars |
|
2515 |
(if (fboundp 'Info-find-node) |
|
2516 |
(apply #'Info-find-node it)) |
|
2517 |
(info) |
|
2518 |
(get-buffer (wg-buf-name buffer))))))) |
|
2519 |
|
|
2520 |
;; `help-mode' |
|
2521 |
;; Bug: https://github.com/pashinin/workgroups2/issues/29 |
|
2522 |
;; bug in wg-get-value |
|
2523 |
(wg-support 'help-mode 'help-mode |
|
2524 |
`((save . (help-xref-stack-item help-xref-stack help-xref-forward-stack)) |
|
2525 |
(deserialize . ,(lambda (buffer vars) |
|
2526 |
(wg-dbind (item stack forward-stack) vars |
|
2527 |
(condition-case err |
|
2528 |
(apply (car item) (cdr item)) |
|
2529 |
(error (message "%s" err))) |
|
2530 |
(awhen (get-buffer "*Help*") |
|
2531 |
(set-buffer it) |
|
2532 |
(wg-when-boundp (help-xref-stack help-xref-forward-stack) |
|
2533 |
(setq help-xref-stack stack |
|
2534 |
help-xref-forward-stack forward-stack)))))))) |
|
2535 |
|
|
2536 |
;; ielm |
|
2537 |
(wg-support 'inferior-emacs-lisp-mode 'ielm |
|
2538 |
`((deserialize . ,(lambda (buffer vars) |
|
2539 |
(ielm) (get-buffer "*ielm*"))))) |
|
2540 |
|
|
2541 |
;; Magit status |
|
2542 |
(wg-support 'magit-status-mode 'magit |
|
2543 |
`((deserialize . ,(lambda (buffer vars) |
|
2544 |
(if (file-directory-p default-directory) |
|
2545 |
(magit-status default-directory) |
|
2546 |
(let ((d (wg-get-first-existing-dir))) |
|
2547 |
(if (file-directory-p d) (dired d)))))))) |
|
2548 |
|
|
2549 |
;; Shell |
|
2550 |
(wg-support 'shell-mode 'shell |
|
2551 |
`((deserialize . ,(lambda (buffer vars) |
|
2552 |
(shell (wg-buf-name buffer)))))) |
|
2553 |
|
|
2554 |
;; org-agenda buffer |
|
2555 |
(defun wg-get-org-agenda-view-commands () |
|
2556 |
"Return commands to restore the state of Agenda buffer. |
|
2557 |
Can be restored using \"(eval commands)\"." |
|
2558 |
(interactive) |
|
2559 |
(when (boundp 'org-agenda-buffer-name) |
|
2560 |
(if (get-buffer org-agenda-buffer-name) |
|
2561 |
(with-current-buffer org-agenda-buffer-name |
|
2562 |
(let* ((p (or (and (looking-at "\\'") (1- (point))) (point))) |
|
2563 |
(series-redo-cmd (get-text-property p 'org-series-redo-cmd))) |
|
2564 |
(if series-redo-cmd |
|
2565 |
(get-text-property p 'org-series-redo-cmd) |
|
2566 |
(get-text-property p 'org-redo-cmd))))))) |
|
2567 |
|
|
2568 |
(defun wg-run-agenda-cmd (f) |
|
2569 |
"Run commands F in Agenda buffer. |
|
2570 |
You can get these commands using `wg-get-org-agenda-view-commands'." |
|
2571 |
(when (and (boundp 'org-agenda-buffer-name) |
|
2572 |
(fboundp 'org-current-line) |
|
2573 |
(fboundp 'org-goto-line)) |
|
2574 |
(if (get-buffer org-agenda-buffer-name) |
|
2575 |
(save-window-excursion |
|
2576 |
(with-current-buffer org-agenda-buffer-name |
|
2577 |
(let* ((line (org-current-line))) |
|
2578 |
(if f (eval f)) |
|
2579 |
(org-goto-line line))))))) |
|
2580 |
|
|
2581 |
(wg-support 'org-agenda-mode 'org-agenda |
|
2582 |
'((serialize . (lambda (buffer) |
|
2583 |
(wg-get-org-agenda-view-commands))) |
|
2584 |
(deserialize . (lambda (buffer vars) |
|
2585 |
(org-agenda-list) |
|
2586 |
(awhen (get-buffer org-agenda-buffer-name) |
|
2587 |
(with-current-buffer it |
|
2588 |
(wg-run-agenda-cmd vars)) |
|
2589 |
it))))) |
|
2590 |
|
|
2591 |
;; eshell |
|
2592 |
(wg-support 'eshell-mode 'esh-mode |
|
2593 |
'((deserialize . (lambda (buffer vars) |
|
2594 |
(prog1 (eshell t) |
|
2595 |
(rename-buffer (wg-buf-name buffer) t)))))) |
|
2596 |
|
|
2597 |
;; term-mode |
|
2598 |
;; |
|
2599 |
;; This should work for `ansi-term's, too, as there doesn't seem to |
|
2600 |
;; be any difference between the two except how the name of the |
|
2601 |
;; buffer is generated. |
|
2602 |
;; |
|
2603 |
(wg-support 'term-mode 'term |
|
2604 |
`((serialize . ,(lambda (buffer) |
|
2605 |
(if (get-buffer-process buffer) |
|
2606 |
(-last-item (process-command (get-buffer-process buffer))) |
|
2607 |
"/bin/bash"))) |
|
2608 |
(deserialize . ,(lambda (buffer vars) |
|
2609 |
(cl-labels ((term-window-width () 80) |
|
2610 |
(window-height () 24)) |
|
2611 |
(prog1 (term vars) |
|
2612 |
(rename-buffer (wg-buf-name buffer) t))))))) |
|
2613 |
|
|
2614 |
;; `inferior-python-mode' |
|
2615 |
(wg-support 'inferior-python-mode 'python |
|
2616 |
`((save . (python-shell-interpreter python-shell-interpreter-args)) |
|
2617 |
(deserialize . ,(lambda (buffer vars) |
|
2618 |
(wg-dbind (pythoncmd pythonargs) vars |
|
2619 |
(run-python (concat pythoncmd " " pythonargs)) |
|
2620 |
(awhen (get-buffer (process-buffer |
|
2621 |
(python-shell-get-or-create-process))) |
|
2622 |
(with-current-buffer it (goto-char (point-max))) |
|
2623 |
it)))))) |
|
2624 |
|
|
2625 |
|
|
2626 |
;; Sage shell ;; |
|
2627 |
(wg-support 'inferior-sage-mode 'sage-mode |
|
2628 |
`((deserialize . ,(lambda (buffer vars) |
|
2629 |
(save-window-excursion |
|
2630 |
(if (boundp' sage-command) |
|
2631 |
(run-sage t sage-command t))) |
|
2632 |
(if (boundp 'sage-buffer) |
|
2633 |
(awhen sage-buffer |
|
2634 |
(set-buffer it) |
|
2635 |
(switch-to-buffer sage-buffer) |
|
2636 |
(goto-char (point-max)))))))) |
|
2637 |
|
|
2638 |
;; `inferior-ess-mode' M-x R |
|
2639 |
(wg-support 'inferior-ess-mode 'ess-inf |
|
2640 |
`((save . (inferior-ess-program)) |
|
2641 |
(deserialize . ,(lambda (buffer vars) |
|
2642 |
(wg-dbind (cmd) vars |
|
2643 |
(let ((ess-ask-about-transfile nil) |
|
2644 |
(ess-ask-for-ess-directory nil) |
|
2645 |
(ess-history-file nil)) |
|
2646 |
(R) |
|
2647 |
(get-buffer (wg-buf-name buffer)))))))) |
|
2648 |
|
|
2649 |
;; `inferior-octave-mode' |
|
2650 |
(wg-support 'inferior-octave-mode 'octave |
|
2651 |
`((deserialize . ,(lambda (buffer vars) |
|
2652 |
(prog1 (run-octave) |
|
2653 |
(rename-buffer (wg-buf-name buffer) t)))))) |
|
2654 |
|
|
2655 |
;; `prolog-inferior-mode' |
|
2656 |
(wg-support 'prolog-inferior-mode 'prolog |
|
2657 |
`((deserialize . ,(lambda (buffer vars) |
|
2658 |
(save-window-excursion |
|
2659 |
(run-prolog nil)) |
|
2660 |
(switch-to-buffer "*prolog*") |
|
2661 |
(goto-char (point-max)))))) |
|
2662 |
|
|
2663 |
;; `ensime-inf-mode' |
|
2664 |
(wg-support 'ensime-inf-mode 'ensime |
|
2665 |
`((deserialize . ,(lambda (buffer vars) |
|
2666 |
(save-window-excursion |
|
2667 |
(ensime-inf-switch)) |
|
2668 |
(when (boundp 'ensime-inf-buffer-name) |
|
2669 |
(switch-to-buffer ensime-inf-buffer-name) |
|
2670 |
(goto-char (point-max))))))) |
|
2671 |
|
|
2672 |
;; compilation-mode |
|
2673 |
;; |
|
2674 |
;; I think it's not a good idea to compile a program just to switch |
|
2675 |
;; workgroups. So just restoring a buffer name. |
|
2676 |
(wg-support 'compilation-mode 'compile |
|
2677 |
`((serialize . ,(lambda (buffer) |
|
2678 |
(if (boundp' compilation-arguments) compilation-arguments))) |
|
2679 |
(deserialize . ,(lambda (buffer vars) |
|
2680 |
(save-window-excursion |
|
2681 |
(get-buffer-create (wg-buf-name buffer))) |
|
2682 |
(with-current-buffer (wg-buf-name buffer) |
|
2683 |
(when (boundp' compilation-arguments) |
|
2684 |
(make-local-variable 'compilation-arguments) |
|
2685 |
(setq compilation-arguments vars))) |
|
2686 |
(switch-to-buffer (wg-buf-name buffer)) |
|
2687 |
(goto-char (point-max)))))) |
|
2688 |
|
|
2689 |
;; grep-mode |
|
2690 |
;; see grep.el - `compilation-start' - it is just a compilation buffer |
|
2691 |
;; local variables: |
|
2692 |
;; `compilation-arguments' == (cmd mode nil nil) |
|
2693 |
(wg-support 'grep-mode 'grep |
|
2694 |
`((serialize . ,(lambda (buffer) |
|
2695 |
(if (boundp' compilation-arguments) compilation-arguments))) |
|
2696 |
(deserialize . ,(lambda (buffer vars) |
|
2697 |
(compilation-start (car vars) (nth 1 vars)) |
|
2698 |
(switch-to-buffer "*grep*"))))) |
|
2699 |
|
|
2700 |
|
|
2701 |
;; `speedbar-mode' (has bugs) |
|
2702 |
;; only from sr-speedbar.el |
|
2703 |
;;(wg-support 'speedbar-mode 'sr-speedbar |
|
2704 |
;; `((deserialize . ,(lambda (buffer vars) |
|
2705 |
;; ;;(with-current-buffer (get-buffer-create "*SPEEDBAR*") |
|
2706 |
;; (with-no-warnings |
|
2707 |
;; (setq speedbar-buffer (get-buffer-create "*SPEEDBAR*")) |
|
2708 |
;; (setq speedbar-frame (selected-frame) |
|
2709 |
;; dframe-attached-frame (selected-frame) |
|
2710 |
;; speedbar-select-frame-method 'attached |
|
2711 |
;; speedbar-verbosity-level 0 |
|
2712 |
;; speedbar-last-selected-file nil) |
|
2713 |
;; (set-buffer speedbar-buffer) |
|
2714 |
;; (speedbar-mode) |
|
2715 |
;; (speedbar-reconfigure-keymaps) |
|
2716 |
;; (speedbar-update-contents) |
|
2717 |
;; (speedbar-set-timer 1) |
|
2718 |
;; (aif (get-buffer-window "*SPEEDBAR*") |
|
2719 |
;; (set-window-dedicated-p it t)) |
|
2720 |
;; (get-buffer-create "*SPEEDBAR*")))))) |
|
2721 |
|
|
2722 |
|
|
2723 |
(defun wg-deserialize-slime-buffer (buf) |
|
2724 |
"Deserialize `slime' buffer BUF." |
|
2725 |
(when (require 'slime nil 'noerror) |
|
2726 |
(wg-dbind (this-function args) (wg-buf-special-data buf) |
|
2727 |
(let ((default-directory (car args)) |
|
2728 |
(arguments (nth 1 args))) |
|
2729 |
(when (and (fboundp 'slime-start*) |
|
2730 |
(fboundp 'slime-process)) |
|
2731 |
(save-window-excursion |
|
2732 |
(slime-start* arguments)) |
|
2733 |
(switch-to-buffer (process-buffer (slime-process))) |
|
2734 |
(current-buffer)))))) |
|
2735 |
|
|
2736 |
;; `comint-mode' (general mode for all shells) |
|
2737 |
;; |
|
2738 |
;; It may have different shells. So we need to determine which shell is |
|
2739 |
;; now in `comint-mode' and how to restore it. |
|
2740 |
;; |
|
2741 |
;; Just executing `comint-exec' may be not enough because we can miss |
|
2742 |
;; some hooks or any other stuff that is executed when you run a |
|
2743 |
;; specific shell. |
|
2744 |
(defun wg-serialize-comint-buffer (buffer) |
|
2745 |
"Serialize comint BUFFER." |
|
2746 |
(with-current-buffer buffer |
|
2747 |
(if (fboundp 'comint-mode) |
|
2748 |
(when (eq major-mode 'comint-mode) |
|
2749 |
;; `slime-inferior-lisp-args' var is used when in `slime' |
|
2750 |
(when (and (boundp 'slime-inferior-lisp-args) |
|
2751 |
slime-inferior-lisp-args) |
|
2752 |
(list 'wg-deserialize-slime-buffer |
|
2753 |
(list default-directory slime-inferior-lisp-args) |
|
2754 |
)))))) |
|
2755 |
|
|
2756 |
;; inf-mongo |
|
2757 |
;; https://github.com/tobiassvn/inf-mongo |
|
2758 |
;; `mongo-command' - command used to start inferior mongo |
|
2759 |
(wg-support 'inf-mongo-mode 'inf-mongo |
|
2760 |
`((serialize . ,(lambda (buffer) |
|
2761 |
(if (boundp 'inf-mongo-command) inf-mongo-command))) |
|
2762 |
(deserialize . ,(lambda (buffer vars) |
|
2763 |
(save-window-excursion |
|
2764 |
(when (fboundp 'inf-mongo) |
|
2765 |
(inf-mongo vars))) |
|
2766 |
(when (get-buffer "*mongo*") |
|
2767 |
(switch-to-buffer "*mongo*") |
|
2768 |
(goto-char (point-max))))))) |
|
2769 |
|
|
2770 |
(defun wg-temporarily-rename-buffer-if-exists (buffer) |
|
2771 |
"Rename BUFFER if it exists." |
|
2772 |
(when (get-buffer buffer) |
|
2773 |
(with-current-buffer buffer |
|
2774 |
(rename-buffer "*wg--temp-buffer*" t)))) |
|
2775 |
|
|
2776 |
;; SML shell |
|
2777 |
;; Functions to serialize deserialize inferior sml buffer |
|
2778 |
;; `inf-sml-program' is the program run as inferior sml, is the |
|
2779 |
;; `inf-sml-args' are the extra parameters passed, `inf-sml-host' |
|
2780 |
;; is the host on which sml was running when serialized |
|
2781 |
(wg-support 'inferior-sml-mode 'sml-mode |
|
2782 |
`((serialize . ,(lambda (buffer) |
|
2783 |
(list (if (boundp 'sml-program-name) sml-program-name) |
|
2784 |
(if (boundp 'sml-default-arg) sml-default-arg) |
|
2785 |
(if (boundp 'sml-host-name) sml-host-name)))) |
|
2786 |
(deserialize . ,(lambda (buffer vars) |
|
2787 |
(wg-dbind (program args host) vars |
|
2788 |
(save-window-excursion |
|
2789 |
;; If a inf-sml buffer already exists rename it temporarily |
|
2790 |
;; otherwise `run-sml' will simply switch to the existing |
|
2791 |
;; buffer, however we want to create a separate buffer with |
|
2792 |
;; the serialized name |
|
2793 |
(let* ((inf-sml-buffer-name (concat "*" |
|
2794 |
(file-name-nondirectory program) |
|
2795 |
"*")) |
|
2796 |
(existing-sml-buf (wg-temporarily-rename-buffer-if-exists |
|
2797 |
inf-sml-buffer-name))) |
|
2798 |
(with-current-buffer (run-sml program args host) |
|
2799 |
;; Rename the buffer |
|
2800 |
(rename-buffer (wg-buf-name buffer) t) |
|
2801 |
|
|
2802 |
;; Now we can re-rename the previously renamed buffer |
|
2803 |
(when existing-sml-buf |
|
2804 |
(with-current-buffer existing-sml-buf |
|
2805 |
(rename-buffer inf-sml-buffer-name t)))))) |
|
2806 |
(switch-to-buffer (wg-buf-name buffer)) |
|
2807 |
(goto-char (point-max))))))) |
|
2808 |
|
|
2809 |
;; Geiser repls |
|
2810 |
;; http://www.nongnu.org/geiser/ |
|
2811 |
(wg-support 'geiser-repl-mode 'geiser |
|
2812 |
`((save . (geiser-impl--implementation)) |
|
2813 |
(deserialize . ,(lambda (buffer vars) |
|
2814 |
(when (fboundp 'run-geiser) |
|
2815 |
(wg-dbind (impl) vars |
|
2816 |
(run-geiser impl) |
|
2817 |
(goto-char (point-max)))) |
|
2818 |
(switch-to-buffer (wg-buf-name buffer)))))) |
|
2819 |
|
|
2820 |
;; w3m-mode |
|
2821 |
(wg-support 'w3m-mode 'w3m |
|
2822 |
`((save . (w3m-current-url)) |
|
2823 |
(deserialize . ,(lambda (buffer vars) |
|
2824 |
(wg-dbind (url) vars |
|
2825 |
(w3m-goto-url url)))))) |
|
2826 |
|
|
2827 |
;; notmuch |
|
2828 |
(wg-support 'notmuch-hello-mode 'notmuch |
|
2829 |
`((deserialize . ,(lambda (buffer vars) |
|
2830 |
(notmuch) |
|
2831 |
(get-buffer (wg-buf-name buffer)))))) |
|
2832 |
|
|
2833 |
;; Wanderlust modes: |
|
2834 |
;; WL - folders |
|
2835 |
;;(defun wg-deserialize-wl-folders-buffer (buf) |
|
2836 |
;; "" |
|
2837 |
;; (if (fboundp 'wl) |
|
2838 |
;; (wg-dbind (this-function) (wg-buf-special-data buf) |
|
2839 |
;; ;;(when (not (eq major-mode 'wl-folder-mode)) |
|
2840 |
;; (wl) |
|
2841 |
;; (goto-char (point-max)) |
|
2842 |
;; (current-buffer) |
|
2843 |
;; ))) |
|
2844 |
;; |
|
2845 |
;;(defun wg-serialize-wl-folders-buffer (buffer) |
|
2846 |
;; "" |
|
2847 |
;; (if (fboundp 'wl) |
|
2848 |
;; (with-current-buffer buffer |
|
2849 |
;; (when (eq major-mode 'wl-folder-mode) |
|
2850 |
;; (list 'wg-deserialize-wl-folders-buffer |
|
2851 |
;; ))))) |
|
2852 |
|
|
2853 |
;; WL - summary mode (list of mails) |
|
2854 |
;;(defun wg-deserialize-wl-summary-buffer (buf) |
|
2855 |
;; "" |
|
2856 |
;; (interactive) |
|
2857 |
;; (if (fboundp 'wl) |
|
2858 |
;; (wg-dbind (this-function param-list) (wg-buf-special-data buf) |
|
2859 |
;; (when (not (eq major-mode 'wl-summary-mode)) |
|
2860 |
;; (let ((fld-name (car param-list))) |
|
2861 |
;; ;;(switch-to-buffer "*scratch*") |
|
2862 |
;; ;;(wl) |
|
2863 |
;; ;;(wl-folder-jump-folder fld-name) |
|
2864 |
;; ;;(message fld-name) |
|
2865 |
;; ;;(goto-char (point-max)) |
|
2866 |
;; ;;(insert fld-name) |
|
2867 |
;; (current-buffer) |
|
2868 |
;; ))))) |
|
2869 |
;; |
|
2870 |
;;(defun wg-serialize-wl-summary-buffer (buffer) |
|
2871 |
;; "" |
|
2872 |
;; (if (fboundp 'wl) |
|
2873 |
;; (with-current-buffer buffer |
|
2874 |
;; (when (eq major-mode 'wl-summary-mode) |
|
2875 |
;; (list 'wg-deserialize-wl-summary-buffer |
|
2876 |
;; (wg-take-until-unreadable (list wl-summary-buffer-folder-name)) |
|
2877 |
;; ))))) |
|
2878 |
;; |
|
2879 |
;; |
|
2880 |
;;;; mime-view-mode |
|
2881 |
;; |
|
2882 |
;;(defun wg-deserialize-mime-view-buffer (buf) |
|
2883 |
;; "" |
|
2884 |
;; (wg-dbind (this-function) (wg-buf-special-data buf) |
|
2885 |
;; (when (not (eq major-mode 'mime-view-mode)) |
|
2886 |
;; ;;(wl-summary-enter-handler 3570) ; only in wl-summary-mode |
|
2887 |
;; ;;(wl-summary-enter-handler) ; only in wl-summary-mode |
|
2888 |
;; (current-buffer) |
|
2889 |
;; ))) |
|
2890 |
;; |
|
2891 |
;;(defun wg-serialize-mime-view-buffer (buffer) |
|
2892 |
;; "" |
|
2893 |
;; (with-current-buffer buffer |
|
2894 |
;; (when (eq major-mode 'mime-view-mode) |
|
2895 |
;; (list 'wg-deserialize-mime-view-buffer |
|
2896 |
;; )))) |
|
2897 |
|
|
2898 |
|
|
2899 |
;; emms-playlist-mode |
|
2900 |
;; |
|
2901 |
;; Help me on this one: |
|
2902 |
;; 1. How to start emms without any user interaction? |
|
2903 |
;; |
|
2904 |
;;(defun wg-deserialize-emms-buffer (buf) |
|
2905 |
;; "Deserialize emms-playlist buffer BUF." |
|
2906 |
;; (when (require 'emms-setup nil 'noerror) |
|
2907 |
;; (require 'emms-player-mplayer) |
|
2908 |
;; (emms-standard) |
|
2909 |
;; (emms-default-players) |
|
2910 |
;; (if (fboundp 'emms-playlist-mode) |
|
2911 |
;; (wg-dbind (this-function args) (wg-buf-special-data buf) |
|
2912 |
;; (let ((default-directory (car args))) |
|
2913 |
;; (save-window-excursion |
|
2914 |
;; ;;(emms) |
|
2915 |
;; (if (or (null emms-playlist-buffer) |
|
2916 |
;; (not (buffer-live-p emms-playlist-buffer))) |
|
2917 |
;; ;;(call-interactively 'emms-add-file) |
|
2918 |
;; (emms-source-directory "/usr/data/disk_3/Music/SORT/") |
|
2919 |
;; )) |
|
2920 |
;; ;; (emms) |
|
2921 |
;; ;;(with-current-buffer emms-playlist-buffer-name |
|
2922 |
;; ;;(emms-source-playlist-directory-tree "/usr/data/disk_3/Music/SORT/") |
|
2923 |
;; ;;(emms-source-directory "/usr/data/disk_3/Music/SORT") |
|
2924 |
;; ;;(switch-to-buffer emms-playlist-buffer-name) |
|
2925 |
;; (emms-playlist-mode-go) |
|
2926 |
;; (current-buffer) |
|
2927 |
;; ))))) |
|
2928 |
;; |
|
2929 |
;;(defun wg-serialize-emms-buffer (buffer) |
|
2930 |
;; "Serialize emms BUFFER." |
|
2931 |
;; (with-current-buffer buffer |
|
2932 |
;; (if (fboundp 'emms-playlist-mode) |
|
2933 |
;; (when (eq major-mode 'emms-playlist-mode) |
|
2934 |
;; (list 'wg-deserialize-emms-buffer |
|
2935 |
;; (wg-take-until-unreadable (list default-directory)) |
|
2936 |
;; ))))) |
|
2937 |
|
|
2938 |
|
|
2939 |
;;; buffer-local variable serdes |
|
2940 |
|
|
2941 |
(defun wg-serialize-buffer-mark-ring () |
|
2942 |
"Return a new list of the positions of the marks in `mark-ring'." |
|
2943 |
(mapcar 'marker-position mark-ring)) |
|
2944 |
|
|
2945 |
(defun wg-deserialize-buffer-mark-ring (positions) |
|
2946 |
"Set `mark-ring' to a new list of markers created from POSITIONS." |
|
2947 |
(setq mark-ring |
|
2948 |
(mapcar (lambda (pos) (set-marker (make-marker) pos)) |
|
2949 |
positions))) |
|
2950 |
|
|
2951 |
(defun wg-deserialize-buffer-major-mode (major-mode-symbol) |
|
2952 |
"Conditionally retore MAJOR-MODE-SYMBOL in `current-buffer'." |
|
2953 |
(and (fboundp major-mode-symbol) |
|
2954 |
(not (eq major-mode-symbol major-mode)) |
|
2955 |
(funcall major-mode-symbol))) |
|
2956 |
|
|
2957 |
(defun wg-deserialize-buffer-local-variables (buf) |
|
2958 |
"Restore BUF's buffer local variables in `current-buffer'." |
|
2959 |
(cl-loop for ((var . val) . rest) on (wg-buf-local-vars buf) |
|
2960 |
do (awhen (assq var wg-buffer-local-variables-alist) |
|
2961 |
(wg-dbind (var ser des) it |
|
2962 |
(if des (funcall des val) |
|
2963 |
(set var val)))))) |
|
2964 |
|
|
2965 |
(defmacro wg-workgroup-list () |
|
2966 |
"Setf'able `wg-current-session' modified slot accessor." |
|
2967 |
`(wg-session-workgroup-list (wg-current-session))) |
|
2968 |
|
|
2969 |
(defmacro wg-buf-list () |
|
2970 |
"Setf'able `wg-current-session' buf-list slot accessor." |
|
2971 |
`(wg-session-buf-list (wg-current-session))) |
|
2972 |
|
|
2973 |
(defun wg-restore-default-buffer (&optional switch) |
|
2974 |
"Return `wg-default-buffer' and maybe SWITCH to it." |
|
2975 |
(if switch |
|
2976 |
(switch-to-buffer wg-default-buffer t) |
|
2977 |
(get-buffer-create wg-default-buffer))) |
|
2978 |
|
|
2979 |
(defun wg-restore-existing-buffer (buf &optional switch) |
|
2980 |
"Return existing buffer from BUF and maybe SWITCH to it." |
|
2981 |
(-when-let (b (wg-find-buf-in-buffer-list buf (wg-buffer-list-emacs))) |
|
2982 |
(if switch (switch-to-buffer b t)) |
|
2983 |
(with-current-buffer b |
|
2984 |
(wg-set-buffer-uid-or-error (wg-buf-uid buf)) |
|
2985 |
b))) |
|
2986 |
|
|
2987 |
(defun wg-restore-file-buffer (buf &optional switch) |
|
2988 |
"Restore BUF by finding its file and maybe SWITCH to it. |
|
2989 |
Return the created buffer. |
|
2990 |
If BUF's file doesn't exist, call `wg-restore-default-buffer'" |
|
2991 |
;;(-when-let ((file-name (wg-buf-file-name buf))) |
|
2992 |
(let ((file-name (wg-buf-file-name buf))) |
|
2993 |
(when (and file-name |
|
2994 |
(or wg-restore-remote-buffers |
|
2995 |
(not (file-remote-p file-name)))) |
|
2996 |
(cond ((file-exists-p file-name) |
|
2997 |
;; jit ignore errors |
|
2998 |
;;(ignore-errors |
|
2999 |
(condition-case err |
|
3000 |
(let ((b (find-file-noselect file-name nil nil nil))) |
|
3001 |
(with-current-buffer b |
|
3002 |
(rename-buffer (wg-buf-name buf) t) |
|
3003 |
(wg-set-buffer-uid-or-error (wg-buf-uid buf)) |
|
3004 |
(when wg-restore-mark |
|
3005 |
(set-mark (wg-buf-mark buf)) |
|
3006 |
(deactivate-mark)) |
|
3007 |
(wg-deserialize-buffer-local-variables buf) |
|
3008 |
) |
|
3009 |
(if switch (switch-to-buffer b)) |
|
3010 |
b) |
|
3011 |
(error |
|
3012 |
(message "Error while restoring a file %s:\n %s" file-name (error-message-string err)) |
|
3013 |
nil))) |
|
3014 |
(t |
|
3015 |
;; try directory |
|
3016 |
(if (file-directory-p (file-name-directory file-name)) |
|
3017 |
(dired (file-name-directory file-name)) |
|
3018 |
(progn |
|
3019 |
(message "Attempt to restore nonexistent file: %S" file-name) |
|
3020 |
nil)) |
|
3021 |
))))) |
|
3022 |
|
|
3023 |
(defun wg-restore-special-buffer (buf &optional switch) |
|
3024 |
"Restore a buffer BUF with DESERIALIZER-FN and maybe SWITCH to it." |
|
3025 |
(-when-let* |
|
3026 |
((special-data (wg-buf-special-data buf)) |
|
3027 |
(buffer (save-window-excursion |
|
3028 |
(condition-case err |
|
3029 |
(funcall (car special-data) buf) |
|
3030 |
(error (message "Error deserializing %S: %S" (wg-buf-name buf) err) |
|
3031 |
nil))))) |
|
3032 |
(if switch (switch-to-buffer buffer t)) |
|
3033 |
(with-current-buffer buffer |
|
3034 |
(wg-set-buffer-uid-or-error (wg-buf-uid buf))) |
|
3035 |
buffer)) |
|
3036 |
|
|
3037 |
(defun wg-restore-buffer (buf &optional switch) |
|
3038 |
"Restore BUF, return it and maybe SWITCH to it." |
|
3039 |
(when buf |
|
3040 |
(fset 'buffer-list wg-buffer-list-original) |
|
3041 |
(prog1 |
|
3042 |
(or (wg-restore-existing-buffer buf switch) |
|
3043 |
(wg-restore-special-buffer buf switch) ;; non existent dired problem |
|
3044 |
(wg-restore-file-buffer buf switch) |
|
3045 |
(progn (wg-restore-default-buffer switch) nil)) |
|
3046 |
(if wg-mess-with-buffer-list |
|
3047 |
(fset 'buffer-list wg-buffer-list-function))))) |
|
3048 |
|
|
3049 |
|
|
3050 |
|
|
3051 |
;;; buffer object utils |
|
3052 |
|
|
3053 |
(defun wg-completing-read (prompt choices &optional pred require-match initial-input history default) |
|
3054 |
"Do a completing read. Use `ido-mode` if enabled." |
|
3055 |
(if ido-mode |
|
3056 |
(ido-completing-read prompt choices pred require-match |
|
3057 |
initial-input history default) |
|
3058 |
(completing-read prompt choices pred require-match |
|
3059 |
initial-input history default))) |
|
3060 |
|
|
3061 |
(defun wg-buffer-uid (buffer-or-name) |
|
3062 |
"Return BUFFER-OR-NAME's buffer-local value of `wg-buffer-uid'." |
|
3063 |
(buffer-local-value 'wg-buffer-uid (wg-get-buffer buffer-or-name))) |
|
3064 |
|
|
3065 |
(defun wg-bufobj-uid (bufobj) |
|
3066 |
"Return BUFOBJ's uid." |
|
3067 |
(cl-etypecase bufobj |
|
3068 |
(buffer (wg-buffer-uid bufobj)) |
|
3069 |
(wg-buf (wg-buf-uid bufobj)) |
|
3070 |
(string (wg-bufobj-uid (wg-get-buffer bufobj))))) |
|
3071 |
|
|
3072 |
(defun wg-bufobj-name (bufobj) |
|
3073 |
"Return BUFOBJ's buffer name." |
|
3074 |
(cl-etypecase bufobj |
|
3075 |
(buffer (buffer-name bufobj)) |
|
3076 |
(wg-buf (wg-buf-name bufobj)) |
|
3077 |
(string (wg-buffer-name bufobj)))) |
|
3078 |
|
|
3079 |
(defun wg-bufobj-file-name (bufobj) |
|
3080 |
"Return BUFOBJ's filename." |
|
3081 |
(cl-etypecase bufobj |
|
3082 |
(buffer (buffer-file-name bufobj)) |
|
3083 |
(wg-buf (wg-buf-file-name bufobj)) |
|
3084 |
(string (wg-bufobj-file-name (wg-get-buffer bufobj))))) |
|
3085 |
|
|
3086 |
(defun wg-buf-major-mode (buf) |
|
3087 |
"Return BUF's `major-mode'. |
|
3088 |
It's stored in BUF's local-vars list, since it's a local variable." |
|
3089 |
(wg-aget (wg-buf-local-vars buf) 'major-mode)) |
|
3090 |
|
|
3091 |
(defun wg-buffer-major-mode (bufobj) |
|
3092 |
"Return BUFOBJ's `major-mode'. |
|
3093 |
It works with Emacs buffer, Workgroups buffer object and a simple string." |
|
3094 |
(cl-etypecase bufobj |
|
3095 |
(buffer (wg-buffer-major-mode bufobj)) |
|
3096 |
(wg-buf (wg-buf-major-mode bufobj)) |
|
3097 |
(string (wg-buffer-major-mode bufobj)))) |
|
3098 |
|
|
3099 |
;; `wg-equal-bufobjs' and `wg-find-bufobj' may need to be made a lot smarter |
|
3100 |
(defun wg-equal-bufobjs (bufobj1 bufobj2) |
|
3101 |
"Return t if BUFOBJ1 is \"equal\" to BUFOBJ2." |
|
3102 |
(let ((fname1 (wg-bufobj-file-name bufobj1)) |
|
3103 |
(fname2 (wg-bufobj-file-name bufobj2))) |
|
3104 |
(cond ((and fname1 fname2) (string= fname1 fname2)) |
|
3105 |
((or fname1 fname2) nil) |
|
3106 |
((string= (wg-bufobj-name bufobj1) (wg-bufobj-name bufobj2)) t)))) |
|
3107 |
|
|
3108 |
(defun wg-find-bufobj (bufobj bufobj-list) |
|
3109 |
"Find BUFOBJ in BUFOBJ-LIST, testing with `wg-equal-bufobjs'." |
|
3110 |
(cl-find bufobj bufobj-list :test 'wg-equal-bufobjs)) |
|
3111 |
|
|
3112 |
(defun wg-find-bufobj-by-uid (uid bufobj-list) |
|
3113 |
"Find the bufobj in BUFOBJ-LIST with uid UID." |
|
3114 |
(cl-find uid bufobj-list :test 'string= :key 'wg-bufobj-uid)) |
|
3115 |
|
|
3116 |
(defun wg-find-buffer-in-buf-list (buffer-or-name buf-list) |
|
3117 |
"Find BUFFER-OR-NAME in BUF-LIST." |
|
3118 |
(aif (wg-buffer-uid buffer-or-name) |
|
3119 |
(wg-find-bufobj-by-uid it buf-list) |
|
3120 |
(wg-find-bufobj buffer-or-name buf-list))) |
|
3121 |
|
|
3122 |
(defun wg-find-buf-in-buffer-list (buf buffer-list) |
|
3123 |
"Find BUF in BUFFER-LIST." |
|
3124 |
(or (wg-find-bufobj-by-uid (wg-buf-uid buf) buffer-list) |
|
3125 |
(wg-find-bufobj buf buffer-list))) |
|
3126 |
|
|
3127 |
(defun wg-find-buf-by-uid (uid) |
|
3128 |
"Find a buf in `wg-buf-list' by UID." |
|
3129 |
(when uid |
|
3130 |
(wg-find-bufobj-by-uid uid (wg-buf-list)))) |
|
3131 |
|
|
3132 |
(defun wg-set-buffer-uid-or-error (uid &optional buffer) |
|
3133 |
"Change UID value of a BUFFER's local var `wg-buffer-uid'. |
|
3134 |
If BUFFER already has a buffer local value of `wg-buffer-uid', |
|
3135 |
and it's not equal to UID, error." |
|
3136 |
(if wg-buffer-uid |
|
3137 |
;;(if (string= wg-buffer-uid uid) uid |
|
3138 |
;; (error "uids don't match %S and %S" uid wg-buffer-uid)) |
|
3139 |
(setq wg-buffer-uid uid))) |
|
3140 |
|
|
3141 |
|
|
3142 |
(defun wg-buffer-special-data (buffer) |
|
3143 |
"Return BUFFER's auxiliary serialization, or nil." |
|
3144 |
(cl-some (lambda (fn) (funcall fn buffer)) wg-special-buffer-serdes-functions)) |
|
3145 |
|
|
3146 |
|
|
3147 |
(defun wg-serialize-buffer-local-variables () |
|
3148 |
"Return an alist of buffer-local variable symbols and their values. |
|
3149 |
See `wg-buffer-local-variables-alist' for details." |
|
3150 |
(wg-docar (entry wg-buffer-local-variables-alist) |
|
3151 |
(wg-dbind (var ser des) entry |
|
3152 |
(when (local-variable-p var) |
|
3153 |
(cons var (if ser (funcall ser) (symbol-value var))))))) |
|
3154 |
|
|
3155 |
(defun wg-buffer-to-buf (buffer) |
|
3156 |
"Return the serialization (a wg-buf) of Emacs buffer BUFFER." |
|
3157 |
(with-current-buffer buffer |
|
3158 |
(wg-make-buf |
|
3159 |
:name (buffer-name) |
|
3160 |
:file-name (buffer-file-name) |
|
3161 |
:point (point) |
|
3162 |
:mark (mark) |
|
3163 |
:local-vars (wg-serialize-buffer-local-variables) |
|
3164 |
:special-data (wg-buffer-special-data buffer)))) |
|
3165 |
|
|
3166 |
(defun wg-add-buffer-to-buf-list (buffer) |
|
3167 |
"Make a buf from BUFFER, and add it to `wg-buf-list' if necessary. |
|
3168 |
If there isn't already a buf corresponding to BUFFER in |
|
3169 |
`wg-buf-list', make one and add it. Return BUFFER's uid |
|
3170 |
in either case." |
|
3171 |
(when buffer |
|
3172 |
(with-current-buffer buffer |
|
3173 |
(setq wg-buffer-uid |
|
3174 |
(aif (wg-find-buffer-in-buf-list buffer (wg-buf-list)) |
|
3175 |
(wg-buf-uid it) |
|
3176 |
(let ((buf (wg-buffer-to-buf buffer))) |
|
3177 |
(push buf (wg-buf-list)) |
|
3178 |
(wg-buf-uid buf))))))) |
|
3179 |
|
|
3180 |
(defun wg-buffer-uid-or-add (buffer) |
|
3181 |
"Return BUFFER's uid. |
|
3182 |
If there isn't already a buf corresponding to BUFFER in |
|
3183 |
`wg-buf-list', make one and add it." |
|
3184 |
(or (wg-buffer-uid buffer) (wg-add-buffer-to-buf-list buffer))) |
|
3185 |
|
|
3186 |
(defun wg-bufobj-uid-or-add (bufobj) |
|
3187 |
"If BUFOBJ is a wg-buf, return its uid. |
|
3188 |
If BUFOBJ is a buffer or a buffer name, see `wg-buffer-uid-or-add'." |
|
3189 |
(cl-etypecase bufobj |
|
3190 |
(wg-buf (wg-buf-uid bufobj)) ;; possibly also add to `wg-buf-list' |
|
3191 |
(buffer (wg-buffer-uid-or-add bufobj)) |
|
3192 |
(string (wg-bufobj-uid-or-add (wg-get-buffer bufobj))))) |
|
3193 |
|
|
3194 |
(defun wg-reset-buffer (buffer) |
|
3195 |
"Return BUFFER. |
|
3196 |
Currently only sets BUFFER's `wg-buffer-uid' to nil." |
|
3197 |
(with-current-buffer buffer (setq wg-buffer-uid nil))) |
|
3198 |
|
|
3199 |
(defun wg-update-buffer-in-buf-list (&optional buffer) |
|
3200 |
"Update BUFFER's corresponding buf in `wg-buf-list'. |
|
3201 |
BUFFER nil defaults to `current-buffer'." |
|
3202 |
(let ((buffer (or buffer (current-buffer)))) |
|
3203 |
(-when-let* ((uid (wg-buffer-uid buffer)) |
|
3204 |
(old-buf (wg-find-buf-by-uid uid)) |
|
3205 |
(new-buf (wg-buffer-to-buf buffer))) |
|
3206 |
(setf (wg-buf-uid new-buf) (wg-buf-uid old-buf)) |
|
3207 |
(wg-asetf (wg-buf-list) (cons new-buf (remove old-buf it)))))) |
|
3208 |
|
|
3209 |
(defvar wg-just-exited-minibuffer nil |
|
3210 |
"Flag set by `minibuffer-exit-hook'. |
|
3211 |
To exempt from undoification those window-configuration changes |
|
3212 |
caused by exiting the minibuffer. This is ugly, but necessary. |
|
3213 |
It may seem like we could just null out |
|
3214 |
`wg-undoify-window-configuration-change' in |
|
3215 |
`minibuffer-exit-hook', but that also prevents undoification of |
|
3216 |
window configuration changes triggered by commands called with |
|
3217 |
`execute-extended-command' -- i.e. it's just too coarse.") |
|
3218 |
|
|
3219 |
(defcustom wg-no-confirm-on-destructive-operation nil |
|
3220 |
"Do not request confirmation before various destructive operations." |
|
3221 |
:type 'boolean |
|
3222 |
:group 'workgroups) |
|
3223 |
|
|
3224 |
(defcustom wg-minibuffer-message-timeout 0.75 |
|
3225 |
"Bound to `minibuffer-message-timeout' when messaging while the |
|
3226 |
minibuffer is active." |
|
3227 |
:type 'float |
|
3228 |
:group 'workgroups) |
|
3229 |
|
|
3230 |
(defun wg-read-object (prompt test warning &optional initial-contents keymap |
|
3231 |
read hist default-value inherit-input-method) |
|
3232 |
"PROMPT for an object that satisfies TEST. WARNING if necessary. |
|
3233 |
INITIAL-CONTENTS KEYMAP READ HIST DEFAULT-VALUE |
|
3234 |
INHERIT-INPUT-METHOD are `read-from-minibuffer's args." |
|
3235 |
(cl-labels ((read () (read-from-minibuffer |
|
3236 |
prompt initial-contents keymap read hist |
|
3237 |
default-value inherit-input-method))) |
|
3238 |
(let ((obj (read))) |
|
3239 |
(when (and (equal obj "") default-value) (setq obj default-value)) |
|
3240 |
(while (not (funcall test obj)) |
|
3241 |
(message warning) |
|
3242 |
(sit-for wg-minibuffer-message-timeout) |
|
3243 |
(setq obj (read))) |
|
3244 |
obj))) |
|
3245 |
|
|
3246 |
(defun wg-read-new-workgroup-name (&optional prompt) |
|
3247 |
"Read a non-empty name string from the minibuffer. |
|
3248 |
Print PROMPT" |
|
3249 |
(let ((default (wg-new-default-workgroup-name))) |
|
3250 |
(wg-read-object |
|
3251 |
(or prompt (format "Name (default: %S): " default)) |
|
3252 |
(lambda (new) (and (stringp new) |
|
3253 |
(not (equal new "")) |
|
3254 |
(wg-unique-workgroup-name-p new))) |
|
3255 |
"Please enter a unique, non-empty name" |
|
3256 |
nil nil nil nil default))) |
|
3257 |
|
|
3258 |
(defun wg-read-workgroup-index () |
|
3259 |
"Prompt for the index of a workgroup." |
|
3260 |
(let ((max (1- (length (wg-workgroup-list-or-error))))) |
|
3261 |
(wg-read-object |
|
3262 |
(format "%s\n\nEnter [0-%d]: " (wg-workgroup-list-display) max) |
|
3263 |
(lambda (obj) (and (integerp obj) (wg-within obj 0 max t))) |
|
3264 |
(format "Please enter an integer [%d-%d]" 0 max) |
|
3265 |
nil nil t))) |
|
3266 |
|
|
3267 |
(defun wg-minibuffer-inactive-p () |
|
3268 |
"Return t when `minibuffer-depth' is zero, nil otherwise." |
|
3269 |
(zerop (minibuffer-depth))) |
|
3270 |
|
|
3271 |
(defun wg-barf-on-active-minibuffer () |
|
3272 |
"Throw an error when the minibuffer is active." |
|
3273 |
(when (not (wg-minibuffer-inactive-p)) |
|
3274 |
(error "Exit minibuffer to use workgroups functions!"))) |
|
3275 |
|
|
3276 |
(defvar wg-deactivation-list nil |
|
3277 |
"A stack of workgroups that are currently being switched away from. |
|
3278 |
Used to avoid associating the old workgroup's buffers with the |
|
3279 |
new workgroup during a switch.") |
|
3280 |
|
|
3281 |
(defun wg-flag-session-modified (&optional session) |
|
3282 |
"Set SESSION's modified flag." |
|
3283 |
(when (and wg-flag-modified |
|
3284 |
(or session (wg-current-session t))) |
|
3285 |
(setf (wg-session-modified (or session (wg-current-session t))) t))) |
|
3286 |
|
|
3287 |
(defun wg-flag-workgroup-modified (&optional workgroup) |
|
3288 |
"Set WORKGROUP's and the current session's modified flags." |
|
3289 |
(unless workgroup |
|
3290 |
(setq workgroup (wg-get-workgroup nil t))) |
|
3291 |
(when (and wg-flag-modified workgroup) |
|
3292 |
(setf (wg-workgroup-modified workgroup) t) |
|
3293 |
(wg-flag-session-modified))) |
|
3294 |
|
|
3295 |
(defun wg-current-workgroup (&optional noerror frame) |
|
3296 |
"Return current workgroup in frame. |
|
3297 |
Error unless NOERROR, in FRAME if specified." |
|
3298 |
(or wg-current-workgroup |
|
3299 |
(aif (frame-parameter frame 'wg-current-workgroup-uid) |
|
3300 |
(wg-find-workgroup-by :uid it noerror) |
|
3301 |
(unless noerror (error "No current workgroup in this frame"))))) |
|
3302 |
|
|
3303 |
(defun wg-previous-workgroup (&optional noerror frame) |
|
3304 |
"Return the previous workgroup in FRAME, or error unless NOERROR." |
|
3305 |
(aif (frame-parameter frame 'wg-previous-workgroup-uid) |
|
3306 |
(wg-find-workgroup-by :uid it noerror) |
|
3307 |
(unless noerror (error "No previous workgroup in this frame")))) |
|
3308 |
|
|
3309 |
(defun wg-set-current-workgroup (workgroup &optional frame) |
|
3310 |
"Set the current workgroup to WORKGROUP in FRAME. |
|
3311 |
WORKGROUP should be a workgroup or nil." |
|
3312 |
(set-frame-parameter frame 'wg-current-workgroup-uid |
|
3313 |
(when workgroup (wg-workgroup-uid workgroup)))) |
|
3314 |
|
|
3315 |
(defun wg-set-previous-workgroup (workgroup &optional frame) |
|
3316 |
"Set the previous workgroup to WORKGROUP in FRAME. |
|
3317 |
WORKGROUP should be a workgroup or nil." |
|
3318 |
(set-frame-parameter frame 'wg-previous-workgroup-uid |
|
3319 |
(when workgroup (wg-workgroup-uid workgroup)))) |
|
3320 |
|
|
3321 |
(defun wg-current-workgroup-p (workgroup &optional frame) |
|
3322 |
"Return t when WORKGROUP is the current workgroup, nil otherwise." |
|
3323 |
(awhen (wg-current-workgroup t frame) |
|
3324 |
(eq workgroup it))) |
|
3325 |
|
|
3326 |
(defun wg-previous-workgroup-p (workgroup &optional frame) |
|
3327 |
"Return t when WORKGROUP is the previous workgroup, nil otherwise." |
|
3328 |
(awhen (wg-previous-workgroup t frame) |
|
3329 |
(eq workgroup it))) |
|
3330 |
|
|
3331 |
(defun wg-get-workgroup (obj &optional noerror) |
|
3332 |
"Return a workgroup from OBJ. |
|
3333 |
If OBJ is a workgroup, return it. |
|
3334 |
If OBJ is a string, return the workgroup named OBJ, or error unless NOERROR. |
|
3335 |
If OBJ is nil, return the current workgroup, or error unless NOERROR." |
|
3336 |
(cond ((wg-workgroup-p obj) obj) |
|
3337 |
((stringp obj) (wg-find-workgroup-by :name obj noerror)) |
|
3338 |
((null obj) (wg-current-workgroup noerror)) |
|
3339 |
(t (error "Can't get workgroup from type:: %S" (type-of obj))))) |
|
3340 |
|
|
3341 |
|
|
3342 |
;;; workgroup parameters |
|
3343 |
;; |
|
3344 |
;; Quick test: |
|
3345 |
;; (wg-workgroup-parameters (wg-current-workgroup)) |
|
3346 |
;; (wg-set-workgroup-parameter (wg-current-workgroup) 'test1 t) |
|
3347 |
;; (wg-workgroup-parameter (wg-current-workgroup) 'test1) |
|
3348 |
(defun wg-workgroup-parameter (workgroup parameter &optional default) |
|
3349 |
"Return WORKGROUP's value for PARAMETER. |
|
3350 |
If PARAMETER is not found, return DEFAULT which defaults to nil. |
|
3351 |
WORKGROUP should be accepted by `wg-get-workgroup'." |
|
3352 |
(wg-aget (wg-workgroup-parameters (wg-get-workgroup workgroup)) |
|
3353 |
parameter default)) |
|
3354 |
|
|
3355 |
(defun wg-set-workgroup-parameter (parameter value &optional workgroup) |
|
3356 |
"Set PARAMETER to VALUE in a WORKGROUP. |
|
3357 |
WORKGROUP should be a value accepted by `wg-get-workgroup'. |
|
3358 |
Return VALUE." |
|
3359 |
(-when-let (workgroup (wg-get-workgroup (or workgroup (wg-current-workgroup t)) t)) |
|
3360 |
(wg-set-parameter (wg-workgroup-parameters workgroup) parameter value) |
|
3361 |
(wg-flag-workgroup-modified workgroup) |
|
3362 |
value)) |
|
3363 |
|
|
3364 |
(defun wg-remove-workgroup-parameter (parameter &optional workgroup) |
|
3365 |
"Remove PARAMETER from WORKGROUP's parameters." |
|
3366 |
(-when-let (workgroup (wg-get-workgroup workgroup t)) |
|
3367 |
(wg-flag-workgroup-modified workgroup) |
|
3368 |
(wg-asetf (wg-workgroup-parameters workgroup) (wg-aremove it parameter)))) |
|
3369 |
|
|
3370 |
(defun wg-workgroup-local-value (variable &optional workgroup) |
|
3371 |
"Return the value of VARIABLE in WORKGROUP. |
|
3372 |
WORKGROUP nil defaults to the current workgroup. If there is no |
|
3373 |
current workgroup, or if VARIABLE does not have a workgroup-local |
|
3374 |
binding in WORKGROUP, resolve VARIABLE with `wg-session-local-value'." |
|
3375 |
(let ((workgroup (wg-get-workgroup workgroup t))) |
|
3376 |
(if (not workgroup) (wg-session-local-value variable) |
|
3377 |
(let* ((undefined (cl-gensym)) |
|
3378 |
(value (wg-workgroup-parameter workgroup variable undefined))) |
|
3379 |
(if (not (eq value undefined)) value |
|
3380 |
(wg-session-local-value variable)))))) |
|
3381 |
(defalias 'wg-local-value 'wg-workgroup-local-value) |
|
3382 |
|
|
3383 |
|
|
3384 |
(defun wg-workgroup-saved-wconfig-names (workgroup) |
|
3385 |
"Return a new list of the names of all WORKGROUP's saved wconfigs." |
|
3386 |
(mapcar 'wg-wconfig-name (wg-workgroup-saved-wconfigs workgroup))) |
|
3387 |
|
|
3388 |
(defun wg-workgroup-get-saved-wconfig (wconfig-or-name &optional workgroup) |
|
3389 |
"Return the wconfig by WCONFIG-OR-NAME from WORKGROUP's saved wconfigs. |
|
3390 |
WCONFIG-OR-NAME must be either a string or a wconfig. If |
|
3391 |
WCONFIG-OR-NAME is a string and there is no saved wconfig with |
|
3392 |
that name, return nil. If WCONFIG-OR-NAME is a wconfig, and it |
|
3393 |
is a member of WORKGROUP's saved wconfigs, return is as given. |
|
3394 |
Otherwise return nil." |
|
3395 |
(let ((wconfigs (wg-workgroup-saved-wconfigs (or workgroup (wg-current-workgroup))))) |
|
3396 |
(cl-etypecase wconfig-or-name |
|
3397 |
(wg-wconfig (car (memq wconfig-or-name wconfigs))) |
|
3398 |
(string (cl-find wconfig-or-name wconfigs |
|
3399 |
:key 'wg-wconfig-name |
|
3400 |
:test 'string=))))) |
|
3401 |
|
|
3402 |
(defun wg-workgroup-save-wconfig (wconfig &optional workgroup) |
|
3403 |
"Add WCONFIG to WORKGROUP's saved wconfigs. |
|
3404 |
WCONFIG must have a name. If there's already a wconfig with the |
|
3405 |
same name in WORKGROUP's saved wconfigs, replace it." |
|
3406 |
(let ((name (wg-wconfig-name wconfig))) |
|
3407 |
(unless name (error "Attempt to save a nameless wconfig")) |
|
3408 |
(setf (wg-workgroup-modified workgroup) t) |
|
3409 |
(wg-asetf (wg-workgroup-saved-wconfigs workgroup) |
|
3410 |
(cons wconfig (cl-remove name it |
|
3411 |
:key 'wg-wconfig-name |
|
3412 |
:test 'string=))))) |
|
3413 |
|
|
3414 |
(defun wg-workgroup-kill-saved-wconfig (workgroup wconfig-or-name) |
|
3415 |
"Delete WCONFIG-OR-NAME from WORKGROUP's saved wconfigs. |
|
3416 |
WCONFIG-OR-NAME is resolved with `wg-workgroup-get-saved-wconfig'." |
|
3417 |
(-when-let (wconfig (wg-workgroup-get-saved-wconfig |
|
3418 |
workgroup wconfig-or-name)) |
|
3419 |
(wg-asetf (wg-workgroup-saved-wconfigs workgroup) (remq wconfig it) |
|
3420 |
(wg-workgroup-modified workgroup) t))) |
|
3421 |
|
|
3422 |
|
|
3423 |
|
|
3424 |
(defun wg-workgroup-base-wconfig-buf-uids (workgroup) |
|
3425 |
"Return a new list of all unique buf uids in WORKGROUP's working wconfig." |
|
3426 |
(wg-wconfig-buf-uids (wg-workgroup-base-wconfig workgroup))) |
|
3427 |
|
|
3428 |
(defun wg-workgroup-saved-wconfigs-buf-uids (workgroup) |
|
3429 |
"Return a new list of all unique buf uids in WORKGROUP's base wconfig." |
|
3430 |
(cl-reduce 'wg-string-list-union |
|
3431 |
(wg-workgroup-saved-wconfigs workgroup) |
|
3432 |
:key 'wg-wconfig-buf-uids)) |
|
3433 |
|
|
3434 |
(defun wg-workgroup-all-wconfig-buf-uids (workgroup) |
|
3435 |
"Return a new list of all unique buf uids in WORKGROUP's wconfigs." |
|
3436 |
(cl-union (wg-workgroup-base-wconfig-buf-uids workgroup) |
|
3437 |
(wg-workgroup-saved-wconfigs-buf-uids workgroup) |
|
3438 |
:test 'string=)) |
|
3439 |
|
|
3440 |
(defun wg-workgroup-all-buf-uids (workgroup) |
|
3441 |
"Return a new list of all unique buf uids in WORKGROUP." |
|
3442 |
(cl-reduce 'wg-string-list-union |
|
3443 |
(list (wg-workgroup-base-wconfig-buf-uids workgroup) |
|
3444 |
(wg-workgroup-saved-wconfigs-buf-uids workgroup)))) |
|
3445 |
|
|
3446 |
(defun wg-restore-workgroup (workgroup) |
|
3447 |
"Restore WORKGROUP in `selected-frame'." |
|
3448 |
(let (wg-flag-modified) |
|
3449 |
(wg-restore-wconfig-undoably (wg-workgroup-working-wconfig workgroup) t))) |
|
3450 |
|
|
3451 |
(defun wg-workgroup-list-or-error (&optional noerror) |
|
3452 |
"Return the value of `wg-current-session's :workgroup-list slot. |
|
3453 |
Or scream unless NOERROR." |
|
3454 |
(aif (wg-current-session noerror) |
|
3455 |
(or (wg-session-workgroup-list it) |
|
3456 |
(unless noerror (error "No workgroups are defined."))) |
|
3457 |
(unless noerror (error "Current session is nil. No workgroups are defined")))) |
|
3458 |
|
|
3459 |
(defun wg-find-workgroup-by (slotkey value &optional noerror) |
|
3460 |
"Return the workgroup on which ACCESSOR returns VALUE or error." |
|
3461 |
(let ((accessor (cl-ecase slotkey |
|
3462 |
(:name 'wg-workgroup-name) |
|
3463 |
(:uid 'wg-workgroup-uid)))) |
|
3464 |
(or (cl-find value (wg-workgroup-list-or-error noerror) :test 'equal :key accessor) |
|
3465 |
(unless noerror |
|
3466 |
(error "There are no workgroups with a %S of %S" |
|
3467 |
accessor value))))) |
|
3468 |
|
|
3469 |
(defun wg-cyclic-nth-from-workgroup (workgroup &optional n) |
|
3470 |
"Return the workgroup N places from WORKGROUP in `wg-workgroup-list'." |
|
3471 |
(wg-cyclic-nth-from-elt workgroup (wg-workgroup-list-or-error) (or n 1))) |
|
3472 |
|
|
3473 |
(defun wg-workgroup-names (&optional noerror) |
|
3474 |
"Return a list of workgroup names or scream unless NOERROR." |
|
3475 |
(mapcar 'wg-workgroup-name (wg-workgroup-list-or-error noerror))) |
|
3476 |
|
|
3477 |
(defun wg-read-workgroup-name (&optional require-match) |
|
3478 |
"Read a workgroup name from `wg-workgroup-names'. |
|
3479 |
REQUIRE-MATCH to match." |
|
3480 |
(wg-completing-read "Workgroup: " (wg-workgroup-names) nil require-match nil nil |
|
3481 |
(awhen (wg-current-workgroup t) (wg-workgroup-name it)))) |
|
3482 |
|
|
3483 |
(defun wg-new-default-workgroup-name () |
|
3484 |
"Return a new, unique, default workgroup name." |
|
3485 |
(let ((names (wg-workgroup-names t)) (index -1) result) |
|
3486 |
(while (not result) |
|
3487 |
(let ((new-name (format "wg%s" (cl-incf index)))) |
|
3488 |
(unless (member new-name names) |
|
3489 |
(setq result new-name)))) |
|
3490 |
result)) |
|
3491 |
|
|
3492 |
(defun wg-unique-workgroup-name-p (new-name) |
|
3493 |
"Return t if NEW-NAME is unique in `wg-workgroup-list', nil otherwise." |
|
3494 |
(cl-every (lambda (existing-name) (not (equal new-name existing-name))) |
|
3495 |
(wg-workgroup-names t))) |
|
3496 |
|
|
3497 |
(defun wg-read-saved-wconfig-name (workgroup &optional prompt require-match) |
|
3498 |
"Read the name of a saved wconfig, completing on the names of |
|
3499 |
WORKGROUP's saved wconfigs." |
|
3500 |
(wg-completing-read (or prompt "Saved wconfig name: ") |
|
3501 |
(wg-workgroup-saved-wconfig-names workgroup) |
|
3502 |
nil require-match)) |
|
3503 |
|
|
3504 |
(defun wg-read-saved-wconfig (workgroup) |
|
3505 |
"Read the name of and return one of WORKGROUP's saved wconfigs." |
|
3506 |
(wg-workgroup-get-saved-wconfig |
|
3507 |
workgroup (wg-read-saved-wconfig-name workgroup nil t))) |
|
3508 |
|
|
3509 |
|
|
3510 |
;;; workgroup-list reorganization commands |
|
3511 |
|
|
3512 |
(defun wg-swap-workgroups () |
|
3513 |
"Swap the previous and current workgroups." |
|
3514 |
(interactive) |
|
3515 |
(wg-swap-workgroups-in-workgroup-list |
|
3516 |
(wg-current-workgroup) (wg-previous-workgroup)) |
|
3517 |
(wg-fontified-message |
|
3518 |
(:cmd "Swapped: ") |
|
3519 |
(wg-workgroup-list-display))) |
|
3520 |
|
|
3521 |
(defun wg-offset-workgroup-left (&optional workgroup n) |
|
3522 |
"Offset WORKGROUP leftward in `wg-workgroup-list' cyclically." |
|
3523 |
(interactive (list nil current-prefix-arg)) |
|
3524 |
(wg-cyclic-offset-workgroup (wg-get-workgroup workgroup) (or n -1)) |
|
3525 |
(wg-fontified-message |
|
3526 |
(:cmd "Offset left: ") |
|
3527 |
(wg-workgroup-list-display))) |
|
3528 |
|
|
3529 |
(defun wg-offset-workgroup-right (&optional workgroup n) |
|
3530 |
"Offset WORKGROUP rightward in `wg-workgroup-list' cyclically." |
|
3531 |
(interactive (list nil current-prefix-arg)) |
|
3532 |
(wg-cyclic-offset-workgroup (wg-get-workgroup workgroup) (or n 1)) |
|
3533 |
(wg-fontified-message |
|
3534 |
(:cmd "Offset right: ") |
|
3535 |
(wg-workgroup-list-display))) |
|
3536 |
|
|
3537 |
|
|
3538 |
;;; undo/redo commands |
|
3539 |
|
|
3540 |
(defun wg-undo-wconfig-change (&optional workgroup) |
|
3541 |
"Undo a change to the current workgroup's window-configuration." |
|
3542 |
(interactive) |
|
3543 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
3544 |
(undid? (wg-workgroup-offset-position-in-undo-list workgroup 1))) |
|
3545 |
(wg-fontified-message |
|
3546 |
(:cmd "Undo") |
|
3547 |
(:cur (if undid? "" " No more undo info"))))) |
|
3548 |
|
|
3549 |
(defun wg-redo-wconfig-change (&optional workgroup) |
|
3550 |
"Redo a change to the current workgroup's window-configuration." |
|
3551 |
(interactive) |
|
3552 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
3553 |
(redid? (wg-workgroup-offset-position-in-undo-list workgroup -1))) |
|
3554 |
(wg-fontified-message |
|
3555 |
(:cmd "Redo") |
|
3556 |
(:cur (if redid? "" " No more redo info"))))) |
|
3557 |
|
|
3558 |
(defun wg-undo-once-all-workgroups () |
|
3559 |
"Do what the name says. Useful for instance when you |
|
3560 |
accidentally call `wg-revert-all-workgroups' and want to return |
|
3561 |
all workgroups to their un-reverted state." |
|
3562 |
(interactive) |
|
3563 |
(mapc 'wg-undo-wconfig-change (wg-workgroup-list-or-error)) |
|
3564 |
(wg-message "Undid once on all workgroups.")) |
|
3565 |
|
|
3566 |
(defun wg-redo-once-all-workgroups () |
|
3567 |
"Do what the name says. Probably useless. Included for |
|
3568 |
symetry with `wg-undo-once-all-workgroups'." |
|
3569 |
(interactive) |
|
3570 |
(mapc 'wg-redo-wconfig-change (wg-workgroup-list-or-error)) |
|
3571 |
(wg-message "Redid once on all workgroups.")) |
|
3572 |
|
|
3573 |
|
|
3574 |
|
|
3575 |
;;; window-tree commands |
|
3576 |
;; |
|
3577 |
;; TODO: These are half-hearted. Clean them up; allow specification of the |
|
3578 |
;; window-tree depth at which to operate; add complex window creation commands; |
|
3579 |
;; and add window splitting, deletion and locking commands. |
|
3580 |
|
|
3581 |
(defun wg-reverse-frame-horizontally (&optional workgroup) |
|
3582 |
"Reverse the order of all horizontally split wtrees." |
|
3583 |
(interactive) |
|
3584 |
(wg-restore-wconfig-undoably |
|
3585 |
(wg-reverse-wconfig |
|
3586 |
(wg-workgroup-working-wconfig |
|
3587 |
(wg-get-workgroup workgroup))))) |
|
3588 |
|
|
3589 |
(defun wg-reverse-frame-vertically (&optional workgroup) |
|
3590 |
"Reverse the order of all vertically split wtrees." |
|
3591 |
(interactive) |
|
3592 |
(wg-restore-wconfig-undoably |
|
3593 |
(wg-reverse-wconfig |
|
3594 |
(wg-workgroup-working-wconfig |
|
3595 |
(wg-get-workgroup workgroup)) |
|
3596 |
t))) |
|
3597 |
|
|
3598 |
(defun wg-reverse-frame-horizontally-and-vertically (&optional workgroup) |
|
3599 |
"Reverse the order of all wtrees." |
|
3600 |
(interactive) |
|
3601 |
(wg-restore-wconfig-undoably |
|
3602 |
(wg-reverse-wconfig |
|
3603 |
(wg-workgroup-working-wconfig |
|
3604 |
(wg-get-workgroup workgroup)) |
|
3605 |
'both))) |
|
3606 |
|
|
3607 |
(defun wg-rename-workgroup (newname &optional workgroup) |
|
3608 |
"Set NEWNAME to WORKGROUP's name." |
|
3609 |
(interactive (list (wg-read-new-workgroup-name "New name: ") nil)) |
|
3610 |
(-when-let (workgroup (wg-get-workgroup workgroup)) |
|
3611 |
(let* ((oldname (wg-workgroup-name workgroup))) |
|
3612 |
(setf (wg-workgroup-name workgroup) newname) |
|
3613 |
(wg-flag-workgroup-modified workgroup) |
|
3614 |
(wg-fontified-message |
|
3615 |
(:cmd "Renamed: ") |
|
3616 |
(:cur oldname) |
|
3617 |
(:msg " to ") |
|
3618 |
(:cur (wg-workgroup-name workgroup)))))) |
|
3619 |
|
|
3620 |
(defun wg-reset (&optional force) |
|
3621 |
"Reset Workgroups. |
|
3622 |
Resets all frame parameters, buffer-local vars, the current |
|
3623 |
Workgroups session object, etc." |
|
3624 |
(interactive "P") |
|
3625 |
(unless (or force wg-no-confirm-on-destructive-operation |
|
3626 |
(y-or-n-p "Really reset Workgroups? ")) |
|
3627 |
(error "Canceled")) |
|
3628 |
(wg-reset-internal) |
|
3629 |
(wg-fontified-message (:cmd "Reset: ") (:msg "Workgroups"))) |
|
3630 |
|
|
3631 |
(defun wg-query-and-save-if-modified () |
|
3632 |
"Query for save when `wg-modified-p'." |
|
3633 |
(or (not (wg-modified-p)) |
|
3634 |
(when (y-or-n-p "Save modified workgroups? ") |
|
3635 |
(wg-save-session)))) |
|
3636 |
|
|
3637 |
(defun wg-create-workgroup (name &optional blank) |
|
3638 |
"Create and add a workgroup named NAME. |
|
3639 |
Optional argument BLANK non-nil (set interactively with a prefix |
|
3640 |
arg) means use a blank, one window window-config. Otherwise use |
|
3641 |
the current window-configuration. Keep in mind that even though |
|
3642 |
the current window-config may be used, other parameters of the |
|
3643 |
current workgroup are not copied to the created workgroup. For |
|
3644 |
that, use `wg-clone-workgroup'." |
|
3645 |
(interactive (list (wg-read-new-workgroup-name) current-prefix-arg)) |
|
3646 |
(wg-switch-to-workgroup (wg-make-and-add-workgroup name blank)) |
|
3647 |
(wg-fontified-message |
|
3648 |
(:cmd "Created: ") (:cur name) " " (wg-workgroup-list-display))) |
|
3649 |
|
|
3650 |
(defun wg-clone-workgroup (workgroup name) |
|
3651 |
"Create and add a clone of WORKGROUP named NAME. |
|
3652 |
Keep in mind that only WORKGROUP's top level alist structure is |
|
3653 |
copied, so destructive operations on the keys or values of |
|
3654 |
WORKGROUP will be reflected in the clone, and vice-versa. Be |
|
3655 |
safe -- don't mutate them." |
|
3656 |
(interactive (list nil (wg-read-new-workgroup-name))) |
|
3657 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
3658 |
(clone (wg-copy-workgroup workgroup))) |
|
3659 |
(setf (wg-workgroup-name clone) name |
|
3660 |
(wg-workgroup-uid clone) (wg-generate-uid)) |
|
3661 |
(when (wg-check-and-add-workgroup clone) |
|
3662 |
(wg-flag-workgroup-modified clone)) |
|
3663 |
(wg-set-workgroup-working-wconfig |
|
3664 |
clone (wg-workgroup-working-wconfig workgroup)) |
|
3665 |
(wg-switch-to-workgroup clone) |
|
3666 |
(wg-fontified-message |
|
3667 |
(:cmd "Cloned: ") |
|
3668 |
(:cur (wg-workgroup-name workgroup)) |
|
3669 |
(:msg " to ") |
|
3670 |
(:cur name) " " |
|
3671 |
(wg-workgroup-list-display)))) |
|
3672 |
|
|
3673 |
(defun wg-switch-to-workgroup (workgroup &optional noerror) |
|
3674 |
"Switch to WORKGROUP. |
|
3675 |
NOERROR means fail silently." |
|
3676 |
(interactive (list (wg-read-workgroup-name))) |
|
3677 |
(fset 'buffer-list wg-buffer-list-original) |
|
3678 |
(let ((workgroup (wg-get-workgroup-create workgroup)) |
|
3679 |
(current (wg-current-workgroup t))) |
|
3680 |
(when (and (eq workgroup current) (not noerror)) |
|
3681 |
(error "Already on: %s" (wg-workgroup-name current))) |
|
3682 |
(when current (push current wg-deactivation-list)) |
|
3683 |
(unwind-protect |
|
3684 |
(progn |
|
3685 |
;; Before switch |
|
3686 |
(run-hooks 'wg-before-switch-to-workgroup-hook) |
|
3687 |
;; Save info about some hard-to-work-with libraries |
|
3688 |
(let (wg-flag-modified) |
|
3689 |
(wg-set-workgroup-parameter 'ecb (and (boundp 'ecb-minor-mode) |
|
3690 |
ecb-minor-mode))) |
|
3691 |
;;(wg-set-workgroup-parameter (wg-current-workgroup t) 'ecb-win-config (ecb-current-window-configuration)) |
|
3692 |
;; (type-of (ecb-current-window-configuration)) |
|
3693 |
;; (type-of (car (ecb-current-window-configuration))) |
|
3694 |
;; (type-of (car (nthcdr 3 (ecb-current-window-configuration)))) |
|
3695 |
;; (wg-pickelable-or-error (ecb-current-window-configuration)) |
|
3696 |
;;(ecb-current-window-configuration) |
|
3697 |
;;) |
|
3698 |
|
|
3699 |
;; Before switching - turn off ECB |
|
3700 |
;; https://github.com/pashinin/workgroups2/issues/34 |
|
3701 |
(if (and (boundp 'ecb-minor-mode) |
|
3702 |
(boundp 'ecb-frame) |
|
3703 |
(fboundp 'ecb-deactivate) |
|
3704 |
ecb-minor-mode |
|
3705 |
(equal ecb-frame (selected-frame))) |
|
3706 |
(let ((ecb-split-edit-window-after-start 'before-deactivation)) |
|
3707 |
(ecb-deactivate))) |
|
3708 |
|
|
3709 |
;; Switch |
|
3710 |
(wg-restore-workgroup workgroup) |
|
3711 |
(wg-set-previous-workgroup current) |
|
3712 |
(wg-set-current-workgroup workgroup) |
|
3713 |
|
|
3714 |
;; After switch |
|
3715 |
;; Save "last-workgroup" to the session params |
|
3716 |
(let (wg-flag-modified) |
|
3717 |
(awhen (wg-current-workgroup t) |
|
3718 |
(wg-set-session-parameter 'last-workgroup (wg-workgroup-name it))) |
|
3719 |
(awhen (wg-previous-workgroup t) |
|
3720 |
(wg-set-session-parameter 'prev-workgroup (wg-workgroup-name it)))) |
|
3721 |
|
|
3722 |
;; If a workgroup had ECB - turn it on |
|
3723 |
(if (and (boundp 'ecb-minor-mode) |
|
3724 |
(not ecb-minor-mode) |
|
3725 |
(fboundp 'ecb-activate) |
|
3726 |
(wg-workgroup-parameter (wg-current-workgroup t) 'ecb nil)) |
|
3727 |
(let ((ecb-split-edit-window-after-start 'before-deactivation)) |
|
3728 |
(ecb-activate))) |
|
3729 |
;;(ecb-last-window-config-before-deactivation |
|
3730 |
;; (wg-workgroup-parameter (wg-current-workgroup t) 'ecb-win-config nil))) |
|
3731 |
|
|
3732 |
;; `sr-speedbar' |
|
3733 |
;; if *SPEEDBAR* buffer is visible - set some variables |
|
3734 |
(let* ((buffers (mapcar 'window-buffer (window-list))) |
|
3735 |
(buffer-names (mapcar 'buffer-name buffers))) |
|
3736 |
(when (and (featurep 'sr-speedbar) |
|
3737 |
(member sr-speedbar-buffer-name buffer-names)) |
|
3738 |
(setq sr-speedbar-window (get-buffer-window sr-speedbar-buffer-name)))) |
|
3739 |
|
|
3740 |
;; Finally |
|
3741 |
(if wg-mess-with-buffer-list |
|
3742 |
(fset 'buffer-list wg-buffer-list-function)) |
|
3743 |
(wg-fontified-message (:cmd "Switched: ") (wg-workgroup-name (wg-current-workgroup t))) |
|
3744 |
(run-hooks 'wg-after-switch-to-workgroup-hook)) |
|
3745 |
(when current (pop wg-deactivation-list))))) |
|
3746 |
|
|
3747 |
(defun wg-switch-to-workgroup-at-index (index) |
|
3748 |
"Switch to the workgroup at INDEX in `wg-workgroup-list'." |
|
3749 |
(interactive (list (or current-prefix-arg (wg-read-workgroup-index)))) |
|
3750 |
(let ((wl (wg-workgroup-list-or-error))) |
|
3751 |
(wg-switch-to-workgroup |
|
3752 |
(or (nth index wl) (error "There are only %d workgroups" (length wl)))))) |
|
3753 |
|
|
3754 |
(cl-macrolet |
|
3755 |
((define-range-of-switch-to-workgroup-at-index (num) |
|
3756 |
`(progn |
|
3757 |
,@(wg-docar (i (wg-range 0 num)) |
|
3758 |
`(defun ,(intern (format "wg-switch-to-workgroup-at-index-%d" i)) () |
|
3759 |
,(format "Switch to the workgroup at index %d." i) |
|
3760 |
(interactive) |
|
3761 |
(wg-switch-to-workgroup-at-index ,i)))))) |
|
3762 |
(define-range-of-switch-to-workgroup-at-index 10)) |
|
3763 |
|
|
3764 |
(defun wg-switch-to-cyclic-nth-from-workgroup (workgroup n) |
|
3765 |
"Switch N workgroups cyclically from WORKGROUP in `wg-workgroup-list.'" |
|
3766 |
(let ((workgroup-list (wg-workgroup-list-or-error)) |
|
3767 |
(workgroup (wg-get-workgroup workgroup t))) |
|
3768 |
(wg-switch-to-workgroup |
|
3769 |
(cond ((not workgroup) (car workgroup-list)) |
|
3770 |
((= 1 (length workgroup-list)) (error "There's only one workgroup")) |
|
3771 |
(t (wg-cyclic-nth-from-workgroup workgroup n)))))) |
|
3772 |
|
|
3773 |
(defun wg-switch-to-workgroup-left (&optional workgroup n) |
|
3774 |
"Switch to WORKGROUP that is (- N) places away from WORKGROUP in `wg-workgroup-list'. |
|
3775 |
Use `current-prefix-arg' for N if non-nil. Otherwise N defaults to 1." |
|
3776 |
(interactive (list nil current-prefix-arg)) |
|
3777 |
(wg-switch-to-cyclic-nth-from-workgroup workgroup (- (or n 1)))) |
|
3778 |
|
|
3779 |
(defun wg-switch-to-workgroup-right (&optional workgroup n) |
|
3780 |
"Switch to the workgroup N places from WORKGROUP in `wg-workgroup-list'. |
|
3781 |
Use `current-prefix-arg' for N if non-nil. Otherwise N defaults to 1." |
|
3782 |
(interactive (list nil current-prefix-arg)) |
|
3783 |
(wg-switch-to-cyclic-nth-from-workgroup workgroup (or n 1))) |
|
3784 |
|
|
3785 |
(defun wg-switch-to-previous-workgroup () |
|
3786 |
"Switch to the previous workgroup." |
|
3787 |
(interactive) |
|
3788 |
(wg-switch-to-workgroup (wg-previous-workgroup))) |
|
3789 |
|
|
3790 |
(defun wg-wconfig-kill-ring () |
|
3791 |
"Return `wg-wconfig-kill-ring', creating it first if necessary." |
|
3792 |
(or wg-wconfig-kill-ring |
|
3793 |
(setq wg-wconfig-kill-ring (make-ring wg-wconfig-kill-ring-max)))) |
|
3794 |
|
|
3795 |
(defun wg-add-to-wconfig-kill-ring (wconfig) |
|
3796 |
"Add WCONFIG to `wg-wconfig-kill-ring'." |
|
3797 |
(ring-insert (wg-wconfig-kill-ring) wconfig)) |
|
3798 |
|
|
3799 |
(defun wg-kill-workgroup (&optional workgroup) |
|
3800 |
"Kill WORKGROUP, saving its working-wconfig to the kill ring." |
|
3801 |
(interactive) |
|
3802 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
3803 |
(to (or (wg-previous-workgroup t) |
|
3804 |
(wg-cyclic-nth-from-workgroup workgroup)))) |
|
3805 |
(wg-add-to-wconfig-kill-ring (wg-workgroup-working-wconfig workgroup)) |
|
3806 |
(wg-delete-workgroup workgroup) |
|
3807 |
(if (eq workgroup to) (wg-restore-wconfig (wg-make-blank-wconfig)) |
|
3808 |
(wg-switch-to-workgroup to)) |
|
3809 |
(wg-fontified-message |
|
3810 |
(:cmd "Killed: ") |
|
3811 |
(:cur (wg-workgroup-name workgroup)) " " |
|
3812 |
(wg-workgroup-list-display)))) |
|
3813 |
|
|
3814 |
(defun wg-kill-ring-save-base-wconfig (&optional workgroup) |
|
3815 |
"Save WORKGROUP's base wconfig to the kill ring." |
|
3816 |
(interactive) |
|
3817 |
(let ((workgroup (wg-get-workgroup workgroup))) |
|
3818 |
(wg-add-to-wconfig-kill-ring (wg-workgroup-base-wconfig workgroup)) |
|
3819 |
(wg-fontified-message |
|
3820 |
(:cmd "Saved: ") (:cur (wg-workgroup-name workgroup)) |
|
3821 |
(:cur "'s ") (:msg "base wconfig to the kill ring")))) |
|
3822 |
|
|
3823 |
(defun wg-kill-ring-save-working-wconfig (&optional workgroup) |
|
3824 |
"Save WORKGROUP's working-wconfig to `wg-wconfig-kill-ring'." |
|
3825 |
(interactive) |
|
3826 |
(let ((workgroup (wg-get-workgroup workgroup))) |
|
3827 |
(wg-add-to-wconfig-kill-ring (wg-workgroup-working-wconfig workgroup)) |
|
3828 |
(wg-fontified-message |
|
3829 |
(:cmd "Saved: ") (:cur (wg-workgroup-name workgroup)) |
|
3830 |
(:cur "'s ") (:msg "working-wconfig to the kill ring")))) |
|
3831 |
|
|
3832 |
(defun wg-yank-wconfig () |
|
3833 |
"Restore a wconfig from `wg-wconfig-kill-ring'. |
|
3834 |
Successive yanks restore wconfigs sequentially from the kill |
|
3835 |
ring, starting at the front." |
|
3836 |
(interactive) |
|
3837 |
(when (zerop (ring-length (wg-wconfig-kill-ring))) |
|
3838 |
(error "The kill-ring is empty")) |
|
3839 |
(let ((pos (if (not (eq real-last-command 'wg-yank-wconfig)) 0 |
|
3840 |
(1+ (or (get 'wg-yank-wconfig :position) 0))))) |
|
3841 |
(put 'wg-yank-wconfig :position pos) |
|
3842 |
(wg-restore-wconfig-undoably (ring-ref (wg-wconfig-kill-ring) pos)) |
|
3843 |
(wg-fontified-message |
|
3844 |
(:cmd "Yanked: ") |
|
3845 |
(:msg (format "%S" pos)) " " |
|
3846 |
(wg-workgroup-list-display)))) |
|
3847 |
|
|
3848 |
(defun wg-kill-workgroup-and-buffers (&optional workgroup) |
|
3849 |
"Kill WORKGROUP and the buffers in its working-wconfig." |
|
3850 |
(interactive) |
|
3851 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
3852 |
(bufs (save-window-excursion |
|
3853 |
(wg-restore-workgroup workgroup) |
|
3854 |
(mapcar #'window-buffer (window-list))))) |
|
3855 |
(wg-kill-workgroup workgroup) |
|
3856 |
(mapc #'kill-buffer bufs) |
|
3857 |
(wg-fontified-message |
|
3858 |
(:cmd "Killed: ") |
|
3859 |
(:cur (wg-workgroup-name workgroup)) |
|
3860 |
(:msg " and its buffers ") "\n" |
|
3861 |
(wg-workgroup-list-display)))) |
|
3862 |
|
|
3863 |
(defun wg-delete-other-workgroups (&optional workgroup) |
|
3864 |
"Delete all workgroups but WORKGROUP." |
|
3865 |
(interactive) |
|
3866 |
(let ((workgroup (wg-get-workgroup workgroup))) |
|
3867 |
(unless (or wg-no-confirm-on-destructive-operation |
|
3868 |
(y-or-n-p "Really delete all other workgroups? ")) |
|
3869 |
(error "Cancelled")) |
|
3870 |
(dolist (w (wg-workgroup-list-or-error)) |
|
3871 |
(unless (eq w workgroup) |
|
3872 |
(wg-delete-workgroup w))) |
|
3873 |
(unless (wg-current-workgroup-p workgroup) |
|
3874 |
(wg-switch-to-workgroup workgroup)) |
|
3875 |
(wg-fontified-message |
|
3876 |
(:cmd "Deleted: ") |
|
3877 |
(:msg "All workgroups but ") |
|
3878 |
(:cur (wg-workgroup-name workgroup))))) |
|
3879 |
|
|
3880 |
(defun wg-revert-workgroup (&optional workgroup) |
|
3881 |
"Restore WORKGROUP's window configuration to its state at the last save." |
|
3882 |
(interactive) |
|
3883 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
3884 |
(base-wconfig (wg-workgroup-base-wconfig workgroup))) |
|
3885 |
(if (wg-current-workgroup-p workgroup) |
|
3886 |
(wg-restore-wconfig-undoably base-wconfig) |
|
3887 |
(wg-add-wconfig-to-undo-list workgroup base-wconfig)) |
|
3888 |
(wg-fontified-message |
|
3889 |
(:cmd "Reverted: ") |
|
3890 |
(:cur (wg-workgroup-name workgroup))))) |
|
3891 |
|
|
3892 |
(defun wg-revert-all-workgroups () |
|
3893 |
"Revert all workgroups to their base wconfigs. |
|
3894 |
Only workgroups' working-wconfigs in `selected-frame' are |
|
3895 |
reverted." |
|
3896 |
(interactive) |
|
3897 |
(mapc #'wg-revert-workgroup (wg-workgroup-list-or-error)) |
|
3898 |
(wg-fontified-message |
|
3899 |
(:cmd "Reverted: ") |
|
3900 |
(:msg "All"))) |
|
3901 |
|
|
3902 |
(defun wg-workgroup-state-table (&optional frame) |
|
3903 |
"Return FRAME's workgroup table, creating it first if necessary." |
|
3904 |
(or (frame-parameter frame 'wg-workgroup-state-table) |
|
3905 |
(let ((wtree (make-hash-table :test 'equal))) |
|
3906 |
(set-frame-parameter frame 'wg-workgroup-state-table wtree) |
|
3907 |
wtree))) |
|
3908 |
|
|
3909 |
(defun wg-get-workgroup-state (workgroup &optional frame) |
|
3910 |
"Return WORKGROUP's state table in a FRAME." |
|
3911 |
(let ((uid (wg-workgroup-uid workgroup)) |
|
3912 |
(state-table (wg-workgroup-state-table frame))) |
|
3913 |
(or (gethash uid state-table) |
|
3914 |
(let ((wgs (wg-make-workgroup-state |
|
3915 |
:undo-pointer 0 |
|
3916 |
:undo-list |
|
3917 |
(list (or (wg-workgroup-selected-frame-wconfig workgroup) |
|
3918 |
(wg-workgroup-base-wconfig workgroup)))))) |
|
3919 |
(puthash uid wgs state-table) |
|
3920 |
wgs)))) |
|
3921 |
|
|
3922 |
(defmacro wg-with-undo (workgroup spec &rest body) |
|
3923 |
"Bind WORKGROUP's undo state to SPEC and eval BODY." |
|
3924 |
(declare (indent 2)) |
|
3925 |
(wg-dbind (state undo-pointer undo-list) spec |
|
3926 |
`(let* ((,state (wg-get-workgroup-state ,workgroup)) |
|
3927 |
(,undo-pointer (wg-workgroup-state-undo-pointer ,state)) |
|
3928 |
(,undo-list (wg-workgroup-state-undo-list ,state))) |
|
3929 |
,@body))) |
|
3930 |
|
|
3931 |
(defun wg-flag-just-exited-minibuffer () |
|
3932 |
"Added to `minibuffer-exit-hook'." |
|
3933 |
(setq wg-just-exited-minibuffer t)) |
|
3934 |
|
|
3935 |
(defun wg-flag-window-configuration-changed () |
|
3936 |
"Set `wg-window-configuration-changed' to t. |
|
3937 |
But only if not the minibuffer was just exited. Added to |
|
3938 |
`window-configuration-change-hook'." |
|
3939 |
(if wg-just-exited-minibuffer |
|
3940 |
(setq wg-just-exited-minibuffer nil) |
|
3941 |
(progn |
|
3942 |
(wg-flag-workgroup-modified) |
|
3943 |
(setq wg-window-configuration-changed t)))) |
|
3944 |
|
|
3945 |
(defun wg-unflag-undoify-window-configuration-change () |
|
3946 |
"Set `wg-undoify-window-configuration-change' to nil, exempting |
|
3947 |
from undoification any window-configuration changes caused by the |
|
3948 |
current command." |
|
3949 |
(setq wg-undoify-window-configuration-change nil)) |
|
3950 |
|
|
3951 |
(defun wg-set-workgroup-working-wconfig (workgroup wconfig) |
|
3952 |
"Set the working-wconfig of WORKGROUP to WCONFIG." |
|
3953 |
(wg-flag-workgroup-modified workgroup) |
|
3954 |
(setf (wg-workgroup-selected-frame-wconfig workgroup) wconfig) |
|
3955 |
(wg-with-undo workgroup (state undo-pointer undo-list) |
|
3956 |
(setcar (nthcdr undo-pointer undo-list) wconfig))) |
|
3957 |
|
|
3958 |
(defun wg-add-wconfig-to-undo-list (workgroup wconfig) |
|
3959 |
"Add WCONFIG to WORKGROUP's undo list, truncating its future if necessary." |
|
3960 |
(wg-with-undo workgroup (state undo-pointer undo-list) |
|
3961 |
(let ((undo-list (cons nil (nthcdr undo-pointer undo-list)))) |
|
3962 |
(awhen (nthcdr wg-wconfig-undo-list-max undo-list) (setcdr it nil)) |
|
3963 |
(setf (wg-workgroup-state-undo-list state) undo-list)) |
|
3964 |
(setf (wg-workgroup-state-undo-pointer state) 0)) |
|
3965 |
(wg-set-workgroup-working-wconfig workgroup wconfig)) |
|
3966 |
|
|
3967 |
(defun wg-workgroup-working-wconfig (workgroup &optional noupdate) |
|
3968 |
"Return WORKGROUP's working-wconfig. |
|
3969 |
If WORKGROUP is the current workgroup in `selected-frame', and |
|
3970 |
NOUPDATE is nil, set its working wconfig in `selected-frame' to |
|
3971 |
`wg-current-wconfig' and return the updated wconfig. Otherwise |
|
3972 |
return WORKGROUP's current undo state." |
|
3973 |
(if (and (not noupdate) (wg-current-workgroup-p workgroup)) |
|
3974 |
(wg-set-workgroup-working-wconfig workgroup (wg-current-wconfig)) |
|
3975 |
(wg-with-undo workgroup (state undo-pointer undo-list) |
|
3976 |
(nth undo-pointer undo-list)))) |
|
3977 |
|
|
3978 |
(defun wg-update-current-workgroup-working-wconfig () |
|
3979 |
"Update `selected-frame's current workgroup's working-wconfig with `wg-current-wconfig'." |
|
3980 |
(awhen (wg-current-workgroup t) |
|
3981 |
(wg-set-workgroup-working-wconfig it (wg-current-wconfig)))) |
|
3982 |
|
|
3983 |
(defun wg-restore-wconfig-undoably (wconfig &optional noundo) |
|
3984 |
"Restore WCONFIG in `selected-frame', saving undo information. |
|
3985 |
Skip undo when NOUNDO." |
|
3986 |
(when noundo (wg-unflag-undoify-window-configuration-change)) |
|
3987 |
(wg-update-current-workgroup-working-wconfig) |
|
3988 |
(wg-restore-wconfig wconfig)) |
|
3989 |
|
|
3990 |
(defun wg-workgroup-offset-position-in-undo-list (workgroup increment) |
|
3991 |
"Increment WORKGROUP's undo-pointer by INCREMENT. |
|
3992 |
Also restore the wconfig at the incremented undo-pointer if |
|
3993 |
WORKGROUP is current." |
|
3994 |
(wg-with-undo workgroup (state undo-pointer undo-list) |
|
3995 |
(let ((new-pointer (+ undo-pointer increment))) |
|
3996 |
(when (wg-within new-pointer 0 (length undo-list)) |
|
3997 |
(when (wg-current-workgroup-p workgroup) |
|
3998 |
(wg-restore-wconfig-undoably (nth new-pointer undo-list) t)) |
|
3999 |
(setf (wg-workgroup-state-undo-pointer state) new-pointer))))) |
|
4000 |
|
|
4001 |
(defun wg-undoify-window-configuration-change () |
|
4002 |
"Conditionally `wg-add-wconfig-to-undo-list'. |
|
4003 |
Added to `post-command-hook'." |
|
4004 |
(when (and wg-window-configuration-changed ;; When the window config has changed, |
|
4005 |
wg-undoify-window-configuration-change ;; and undoification is still on for the current command |
|
4006 |
(wg-minibuffer-inactive-p)) ;; and the change didn't occur while the minibuffer is active, |
|
4007 |
(-when-let (workgroup (wg-current-workgroup t)) ;; and there's a current workgroup, |
|
4008 |
;; add the current wconfig to that workgroup's undo list: |
|
4009 |
(wg-add-wconfig-to-undo-list workgroup (wg-current-wconfig)))) |
|
4010 |
;; Reset all flags no matter what: |
|
4011 |
(setq wg-window-configuration-changed nil |
|
4012 |
wg-undoify-window-configuration-change t |
|
4013 |
wg-already-updated-working-wconfig nil)) |
|
4014 |
|
|
4015 |
(defun wg-update-working-wconfig-hook () |
|
4016 |
"Used in before advice on all functions that trigger `window-configuration-change-hook'. |
|
4017 |
To save up to date undo info before the change." |
|
4018 |
(when (and (not wg-already-updated-working-wconfig) |
|
4019 |
(wg-minibuffer-inactive-p)) |
|
4020 |
(wg-update-current-workgroup-working-wconfig) |
|
4021 |
(setq wg-already-updated-working-wconfig t))) |
|
4022 |
|
|
4023 |
(defun wg-workgroup-gc-buf-uids (workgroup) |
|
4024 |
"Remove buf uids from WORKGROUP that have no referent in `wg-buf-list'." |
|
4025 |
(wg-asetf (wg-workgroup-strong-buf-uids workgroup) |
|
4026 |
(cl-remove-if-not 'wg-find-buf-by-uid it) |
|
4027 |
(wg-workgroup-weak-buf-uids workgroup) |
|
4028 |
(cl-remove-if-not 'wg-find-buf-by-uid it))) |
|
4029 |
|
|
4030 |
(defun wg-display-internal (elt-fn list) |
|
4031 |
"Return display string built by calling ELT-FN on each element of LIST." |
|
4032 |
(let ((div (wg-add-face :div wg-list-display-decor-divider)) |
|
4033 |
(wwidth (window-width (minibuffer-window))) |
|
4034 |
(i -1) |
|
4035 |
(str)) |
|
4036 |
(setq str |
|
4037 |
(wg-fontify |
|
4038 |
(:brace wg-list-display-decor-left-brace) |
|
4039 |
(if (not list) (funcall elt-fn nil nil) |
|
4040 |
(wg-doconcat (elt list div) (funcall elt-fn elt (cl-incf i)))) |
|
4041 |
(:brace wg-list-display-decor-right-brace))) |
|
4042 |
;; (subseq str 0 wwidth) |
|
4043 |
)) |
|
4044 |
|
|
4045 |
(defun wg-workgroup-list-display (&optional workgroup-list) |
|
4046 |
"Return the WORKGROUP-LIST display string. |
|
4047 |
The string contains the names of all workgroups in `wg-workgroup-list', |
|
4048 |
decorated with faces, dividers and strings identifying the |
|
4049 |
current and previous workgroups." |
|
4050 |
(if (wg-current-session t) |
|
4051 |
(wg-display-internal 'wg-workgroup-display |
|
4052 |
(or workgroup-list (wg-workgroup-list))))) |
|
4053 |
|
|
4054 |
(defun wg-create-first-wg () |
|
4055 |
"Create a first workgroup if needed." |
|
4056 |
(when (and workgroups-mode |
|
4057 |
wg-session-load-on-start |
|
4058 |
(= (length (wg-workgroup-list)) 0)) |
|
4059 |
(wg-create-workgroup wg-first-wg-name) |
|
4060 |
(wg-mark-everything-unmodified))) |
|
4061 |
|
|
4062 |
(defun wg-pickel-workgroup-parameters (workgroup) |
|
4063 |
"Return a copy of WORKGROUP after pickeling its parameters. |
|
4064 |
If WORKGROUP's parameters are non-nil, otherwise return |
|
4065 |
WORKGROUP." |
|
4066 |
(if (not (wg-workgroup-parameters workgroup)) workgroup |
|
4067 |
(let ((copy (wg-copy-workgroup workgroup))) |
|
4068 |
(wg-asetf (wg-workgroup-parameters copy) (wg-pickel it)) |
|
4069 |
copy))) |
|
4070 |
|
|
4071 |
(defun wg-unpickel-workgroup-parameters (workgroup) |
|
4072 |
"If WORKGROUP's parameters are non-nil, return a copy of |
|
4073 |
WORKGROUP after unpickeling its parameters. Otherwise return |
|
4074 |
WORKGROUP." |
|
4075 |
(if (not (wg-workgroup-parameters workgroup)) workgroup |
|
4076 |
(let ((copy (wg-copy-workgroup workgroup))) |
|
4077 |
(wg-asetf (wg-workgroup-parameters copy) (wg-unpickel it)) |
|
4078 |
copy))) |
|
4079 |
|
|
4080 |
(defun wg-delete-workgroup (workgroup) |
|
4081 |
"Remove WORKGROUP from `wg-workgroup-list'. |
|
4082 |
Also delete all references to it by `wg-workgroup-state-table', |
|
4083 |
`wg-current-workgroup' and `wg-previous-workgroup'." |
|
4084 |
(dolist (frame (frame-list)) |
|
4085 |
(remhash (wg-workgroup-uid workgroup) (wg-workgroup-state-table frame)) |
|
4086 |
(when (wg-current-workgroup-p workgroup frame) |
|
4087 |
(wg-set-current-workgroup nil frame)) |
|
4088 |
(when (wg-previous-workgroup-p workgroup frame) |
|
4089 |
(wg-set-previous-workgroup nil frame))) |
|
4090 |
(setf (wg-workgroup-list) (remove workgroup (wg-workgroup-list-or-error))) |
|
4091 |
(wg-flag-session-modified) |
|
4092 |
workgroup) |
|
4093 |
|
|
4094 |
(defun wg-add-workgroup (workgroup &optional index) |
|
4095 |
"Add WORKGROUP to `wg-workgroup-list' at INDEX or the end. |
|
4096 |
If a workgroup with the same name exists, overwrite it." |
|
4097 |
(awhen (wg-find-workgroup-by :name (wg-workgroup-name workgroup) t) |
|
4098 |
(unless index (setq index (cl-position it (wg-workgroup-list-or-error)))) |
|
4099 |
(wg-delete-workgroup it)) |
|
4100 |
(wg-asetf (wg-workgroup-list) |
|
4101 |
(wg-insert-before workgroup it (or index (length it)))) |
|
4102 |
(wg-flag-session-modified) |
|
4103 |
workgroup) |
|
4104 |
|
|
4105 |
(defun wg-check-and-add-workgroup (workgroup) |
|
4106 |
"Add WORKGROUP to `wg-workgroup-list'. |
|
4107 |
Ask to overwrite if a workgroup with the same name exists." |
|
4108 |
(let ((name (wg-workgroup-name workgroup)) |
|
4109 |
(uid (wg-workgroup-uid workgroup))) |
|
4110 |
(when (wg-find-workgroup-by :uid uid t) |
|
4111 |
(error "A workgroup with uid %S already exists" uid)) |
|
4112 |
(when (wg-find-workgroup-by :name name t) |
|
4113 |
(unless (or wg-no-confirm-on-destructive-operation |
|
4114 |
(y-or-n-p (format "%S exists. Overwrite? " name))) |
|
4115 |
(error "Cancelled")))) |
|
4116 |
(wg-add-workgroup workgroup)) |
|
4117 |
|
|
4118 |
(defun wg-make-and-add-workgroup (name &optional blank) |
|
4119 |
"Create a workgroup named NAME with current `window-tree'. |
|
4120 |
If BLANK - then just scratch buffer. |
|
4121 |
Add it with `wg-check-and-add-workgroup'." |
|
4122 |
(wg-check-and-add-workgroup |
|
4123 |
(wg-make-workgroup |
|
4124 |
:name name |
|
4125 |
:base-wconfig (if blank (wg-make-blank-wconfig) |
|
4126 |
(wg-current-wconfig))))) |
|
4127 |
|
|
4128 |
(defun wg-get-workgroup-create (workgroup) |
|
4129 |
"Return the workgroup specified by WORKGROUP, creating a new one if needed. |
|
4130 |
If `wg-get-workgroup' on WORKGROUP returns a workgroup, return it. |
|
4131 |
Otherwise, if WORKGROUP is a string, create a new workgroup with |
|
4132 |
that name and return it. Otherwise error." |
|
4133 |
(or (wg-get-workgroup workgroup t) |
|
4134 |
(if (stringp workgroup) |
|
4135 |
(wg-make-and-add-workgroup workgroup) |
|
4136 |
(wg-get-workgroup workgroup)))) ; Call this again for its error message |
|
4137 |
|
|
4138 |
(defun wg-cyclic-offset-workgroup (workgroup n) |
|
4139 |
"Offset WORKGROUP's position in `wg-workgroup-list' by N." |
|
4140 |
(let ((workgroup-list (wg-workgroup-list-or-error))) |
|
4141 |
(unless (member workgroup workgroup-list) |
|
4142 |
(error "Workgroup isn't present in `wg-workgroup-list'.")) |
|
4143 |
(setf (wg-workgroup-list) (wg-cyclic-offset-elt workgroup workgroup-list n) |
|
4144 |
(wg-session-modified (wg-current-session)) t))) |
|
4145 |
|
|
4146 |
(defun wg-swap-workgroups-in-workgroup-list (workgroup1 workgroup2) |
|
4147 |
"Swap the positions of WORKGROUP1 and WORKGROUP2 in `wg-workgroup-list'." |
|
4148 |
(let ((workgroup-list (wg-workgroup-list-or-error))) |
|
4149 |
(when (eq workgroup1 workgroup2) |
|
4150 |
(error "Can't swap a workgroup with itself")) |
|
4151 |
(unless (and (memq workgroup1 workgroup-list) |
|
4152 |
(memq workgroup2 workgroup-list)) |
|
4153 |
(error "Both workgroups aren't present in `wg-workgroup-list'.")) |
|
4154 |
(setf (wg-workgroup-list) (wg-util-swap workgroup1 workgroup2 workgroup-list) |
|
4155 |
(wg-session-modified (wg-current-session)) t))) |
|
4156 |
|
|
4157 |
(defun wg-session-uids-consistent-p () |
|
4158 |
"Return t if there are no duplicate bufs or buf uids in the wrong places. |
|
4159 |
nil otherwise." |
|
4160 |
(and (not (wg-dups-p (wg-buf-list) :key 'wg-buf-uid :test 'string=)) |
|
4161 |
(not (wg-dups-p (wg-workgroup-list) :key 'wg-workgroup-uid :test 'string=)))) |
|
4162 |
|
|
4163 |
(defun wg-open-session (filename) |
|
4164 |
"Load a session visiting FILENAME, creating one if none already exists." |
|
4165 |
(interactive "FFind session file: ") |
|
4166 |
(cond ((file-exists-p filename) |
|
4167 |
;; TODO: handle errors when reading object |
|
4168 |
(let ((session (read (f-read-text filename)))) |
|
4169 |
(unless (wg-session-p session) |
|
4170 |
(error "%S is not a Workgroups session file." filename)) |
|
4171 |
(setf (wg-session-file-name session) filename) |
|
4172 |
(wg-reset-internal (wg-unpickel-session-parameters session))) |
|
4173 |
|
|
4174 |
(if wg-control-frames (wg-restore-frames)) |
|
4175 |
|
|
4176 |
(awhen (wg-workgroup-list) |
|
4177 |
(if (and wg-open-this-wg |
|
4178 |
(member wg-open-this-wg (wg-workgroup-names))) |
|
4179 |
(wg-switch-to-workgroup wg-open-this-wg) |
|
4180 |
(if (and wg-load-last-workgroup |
|
4181 |
(member (wg-session-parameter 'last-workgroup) (wg-workgroup-names))) |
|
4182 |
(wg-switch-to-workgroup (wg-session-parameter 'last-workgroup)) |
|
4183 |
(wg-switch-to-workgroup (car it)))) |
|
4184 |
(awhen (wg-session-parameter 'prev-workgroup) |
|
4185 |
(when (and (member it (wg-workgroup-names)) |
|
4186 |
(wg-get-workgroup it t)) |
|
4187 |
(wg-set-previous-workgroup (wg-get-workgroup it t))))) |
|
4188 |
(wg-fontified-message (:cmd "Loaded: ") (:file filename)) |
|
4189 |
(wg-mark-everything-unmodified)) |
|
4190 |
(t |
|
4191 |
(wg-query-and-save-if-modified) |
|
4192 |
(wg-reset-internal (wg-make-session :file-name filename)) |
|
4193 |
(wg-fontified-message (:cmd "(New Workgroups session file)"))))) |
|
4194 |
(defalias 'wg-find-session-file 'wg-open-session) |
|
4195 |
|
|
4196 |
(defun wg-write-sexp-to-file (sexp file) |
|
4197 |
"Write the printable representation of SEXP to FILE." |
|
4198 |
(with-temp-buffer |
|
4199 |
(let ((print-level nil) (print-length nil)) |
|
4200 |
(insert (format "%S" sexp))) |
|
4201 |
(write-file file))) |
|
4202 |
|
|
4203 |
;; FIXME: Duplicate buf names probably shouldn't be allowed. An unrelated error |
|
4204 |
;; causes two *scratch* buffers to be present, triggering the "uids don't match" |
|
4205 |
;; error. Write something to remove bufs with duplicate names. |
|
4206 |
(defun wg-perform-session-maintenance () |
|
4207 |
"Perform various maintenance operations on the current Workgroups session." |
|
4208 |
(wg-update-current-workgroup-working-wconfig) |
|
4209 |
|
|
4210 |
;; Update every workgroup's base wconfig with `wg-workgroup-update-base-wconfig' |
|
4211 |
(dolist (workgroup (wg-workgroup-list)) |
|
4212 |
(awhen (wg-workgroup-selected-frame-wconfig workgroup) |
|
4213 |
(setf (wg-workgroup-base-wconfig workgroup) it |
|
4214 |
(wg-workgroup-selected-frame-wconfig workgroup) nil))) |
|
4215 |
|
|
4216 |
;; Garbage collection |
|
4217 |
|
|
4218 |
;; Commenting this will cause a constantly growing session file: |
|
4219 |
;; (tried to comment this block to solve https://github.com/pashinin/workgroups2/issues/48) |
|
4220 |
(let ((all-buf-uids (wg-all-buf-uids))) |
|
4221 |
(wg-asetf (wg-buf-list) |
|
4222 |
(cl-remove-if-not (lambda (uid) (member uid all-buf-uids)) it |
|
4223 |
:key 'wg-buf-uid))) |
|
4224 |
|
|
4225 |
(mapc 'wg-workgroup-gc-buf-uids (wg-workgroup-list)) ; Remove buf uids that have no referent in `wg-buf-list' |
|
4226 |
(mapc 'wg-update-buffer-in-buf-list (wg-buffer-list-emacs))) |
|
4227 |
|
|
4228 |
(defun wg-save-session-as (filename &optional confirm) |
|
4229 |
"Write the current session into file FILENAME. |
|
4230 |
This makes the session visit that file, and marks it as not modified. |
|
4231 |
|
|
4232 |
If optional second arg CONFIRM is non-nil, this function asks for |
|
4233 |
confirmation before overwriting an existing file. Interactively, |
|
4234 |
confirmation is required unless you supply a prefix argument." |
|
4235 |
(interactive (list (read-file-name "Save session as: ") |
|
4236 |
(not current-prefix-arg))) |
|
4237 |
(when (and confirm (file-exists-p filename)) |
|
4238 |
(unless (y-or-n-p (format "File `%s' exists; overwrite? " filename)) |
|
4239 |
(error "Cancelled"))) |
|
4240 |
(unless (file-writable-p filename) |
|
4241 |
(error "File %s can't be written to" filename)) |
|
4242 |
(wg-perform-session-maintenance) |
|
4243 |
(setf (wg-session-file-name (wg-current-session)) filename) |
|
4244 |
(setf (wg-session-version (wg-current-session)) wg-version) |
|
4245 |
|
|
4246 |
;; Save opened frames as a session parameter "frame-list". |
|
4247 |
;; Exclude `selected-frame' and daemon one (if any). |
|
4248 |
;; http://stackoverflow.com/questions/21151992/why-emacs-as-daemon-gives-1-more-frame-than-is-opened |
|
4249 |
(if wg-control-frames |
|
4250 |
(let ((fl (frame-list))) |
|
4251 |
;; TODO: remove using dash |
|
4252 |
(mapc (lambda (frame) |
|
4253 |
(if (string-equal "initial_terminal" (terminal-name frame)) |
|
4254 |
(delete frame fl))) fl) |
|
4255 |
(setq fl (delete (selected-frame) fl)) |
|
4256 |
(let (wg-flag-modified) |
|
4257 |
(wg-set-session-parameter 'frame-list (mapcar 'wg-frame-to-wconfig fl))))) |
|
4258 |
(wg-write-sexp-to-file (wg-pickel-all-session-parameters) filename) |
|
4259 |
(wg-fontified-message (:cmd "Wrote: ") (:file filename)) |
|
4260 |
(wg-mark-everything-unmodified)) |
|
4261 |
(defalias 'wg-write-session-file 'wg-save-session-as) |
|
4262 |
|
|
4263 |
(defun wg-get-session-file () |
|
4264 |
"Return the filename in which to save the session." |
|
4265 |
(or (aif (wg-current-session t) (wg-session-file-name it)) |
|
4266 |
wg-session-file)) |
|
4267 |
;;(read-file-name (format "Save session as [%s]: " wg-session-file)) |
|
4268 |
|
|
4269 |
(defun wg-save-session (&optional force) |
|
4270 |
"Save the current Workgroups session if it's been modified. |
|
4271 |
When FORCE - save session regardless of whether it's been modified." |
|
4272 |
(interactive "P") |
|
4273 |
;;(if (and (not (wg-modified-p)) (not force)) |
|
4274 |
;; (wg-message "(The session is unmodified)") |
|
4275 |
(wg-save-session-as (wg-get-session-file))) |
|
4276 |
|
|
4277 |
(defun wg-reset-internal (&optional session) |
|
4278 |
"Reset Workgroups, setting `wg-current-session' to SESSION. |
|
4279 |
Resets all frame parameters, buffer-local vars, current session |
|
4280 |
object, etc. SESSION nil defaults to a new, blank session." |
|
4281 |
(mapc 'wg-reset-frame (frame-list)) |
|
4282 |
(mapc 'wg-reset-buffer (wg-buffer-list-emacs)) |
|
4283 |
(setq wg-wconfig-kill-ring nil) |
|
4284 |
(setq wg-current-session (or session (wg-make-session)))) |
|
4285 |
|
|
4286 |
(defun wg-all-buf-uids (&optional session buffer-list) |
|
4287 |
"Return the union of all SESSION buf-uids and BUFFER-LIST uids." |
|
4288 |
(cl-union (cl-reduce 'wg-string-list-union ; (wg-session-all-buf-uids session) |
|
4289 |
(wg-session-workgroup-list (or session (wg-current-session))) |
|
4290 |
:key 'wg-workgroup-all-buf-uids) |
|
4291 |
;; (wg-buffer-list-all-uids buffer-list) |
|
4292 |
(delq nil (mapcar 'wg-buffer-uid (or buffer-list (wg-buffer-list-emacs)))) |
|
4293 |
:test 'string=)) |
|
4294 |
|
|
4295 |
(defun wg-modified-p () |
|
4296 |
"Return t when the current session or any of its workgroups are modified." |
|
4297 |
(aif (wg-current-session t) |
|
4298 |
(or (wg-session-modified it) |
|
4299 |
(cl-some 'wg-workgroup-modified (wg-workgroup-list))))) |
|
4300 |
|
|
4301 |
(defun wg-mark-everything-unmodified () |
|
4302 |
"Mark the session and all workgroups as unmodified." |
|
4303 |
(let (wg-undoify-window-configuration-change) ; to skip WG's `post-command-hook' that marks "modified" again |
|
4304 |
(-when-let (session (wg-current-session t)) |
|
4305 |
(setf (wg-session-modified session) nil)) |
|
4306 |
(dolist (workgroup (wg-workgroup-list)) |
|
4307 |
(setf (wg-workgroup-modified workgroup) nil)))) |
|
4308 |
|
|
4309 |
(defun wg-session-parameter (parameter &optional default session) |
|
4310 |
"Return session's value for PARAMETER. |
|
4311 |
If PARAMETER is not found, return DEFAULT which defaults to nil. |
|
4312 |
SESSION nil defaults to the current session." |
|
4313 |
(wg-aget (wg-session-parameters (or session (wg-current-session))) |
|
4314 |
parameter default)) |
|
4315 |
|
|
4316 |
(defun wg-set-session-parameter (parameter value &optional session) |
|
4317 |
"Set PARAMETER to VALUE in SESSION. |
|
4318 |
SESSION nil means use the current session. Return value." |
|
4319 |
(-when-let (session (or session (wg-current-session t))) |
|
4320 |
(wg-set-parameter (wg-session-parameters session) parameter value) |
|
4321 |
(wg-flag-session-modified session) |
|
4322 |
value)) |
|
4323 |
|
|
4324 |
(defun wg-remove-session-parameter (parameter &optional session) |
|
4325 |
"Remove parameter PARAMETER from SESSION's parameters." |
|
4326 |
(let ((session (or session (wg-current-session)))) |
|
4327 |
(wg-asetf (wg-session-parameters session) (wg-aremove it parameter)) |
|
4328 |
(wg-flag-session-modified session))) |
|
4329 |
|
|
4330 |
(defun wg-session-local-value (variable &optional session) |
|
4331 |
"Return the value of VARIABLE in SESSION. |
|
4332 |
SESSION nil defaults to the current session. If VARIABLE does |
|
4333 |
not have a session-local binding in SESSION, the value is |
|
4334 |
resolved by Emacs." |
|
4335 |
(let* ((undefined (cl-gensym)) |
|
4336 |
(value (wg-session-parameter variable undefined session))) |
|
4337 |
(if (not (eq value undefined)) value |
|
4338 |
(symbol-value variable)))) |
|
4339 |
|
|
4340 |
(defun wg-reset-frame (frame) |
|
4341 |
"Reset Workgroups' `frame-parameters' in FRAME to nil." |
|
4342 |
(set-frame-parameter frame 'wg-workgroup-state-table nil) |
|
4343 |
(set-frame-parameter frame 'wg-current-workgroup-uid nil) |
|
4344 |
(set-frame-parameter frame 'wg-previous-workgroup-uid nil)) |
|
4345 |
|
|
4346 |
(defun wg-save-session-on-exit (behavior) |
|
4347 |
"Perform session-saving operations based on BEHAVIOR." |
|
4348 |
(cl-case behavior |
|
4349 |
(ask (wg-query-and-save-if-modified)) |
|
4350 |
(save (wg-save-session)))) |
|
4351 |
|
|
4352 |
(defun wg-reload-session () |
|
4353 |
"Reload current workgroups session." |
|
4354 |
(interactive) |
|
4355 |
(let* ((file (wg-get-session-file)) |
|
4356 |
(exists (file-exists-p file))) |
|
4357 |
(condition-case err |
|
4358 |
(wg-open-session file) |
|
4359 |
(progn |
|
4360 |
(wg-create-first-wg) |
|
4361 |
(message "Error loading session-file: %s" err)))) |
|
4362 |
;; TODO: print what exactly happened |
|
4363 |
(wg-create-first-wg)) |
|
4364 |
|
|
4365 |
(defun wg-save-session-on-emacs-exit () |
|
4366 |
"Call `wg-save-session-on-exit' with `wg-emacs-exit-save-behavior'. |
|
4367 |
Added to `kill-emacs-query-functions'." |
|
4368 |
(wg-save-session-on-exit wg-emacs-exit-save-behavior) t) |
|
4369 |
|
|
4370 |
(defun wg-save-session-on-workgroups-mode-exit () |
|
4371 |
"Call `wg-save-session-on-exit' with `wg-workgroups-mode-exit-save-behavior'. |
|
4372 |
Called when `workgroups-mode' is turned off." |
|
4373 |
(wg-save-session-on-exit wg-workgroups-mode-exit-save-behavior) t) |
|
4374 |
|
|
4375 |
(defun wg-pickel-all-session-parameters (&optional session) |
|
4376 |
"Return a copy of SESSION after pickeling its parameters. |
|
4377 |
And the parameters of all its workgroups." |
|
4378 |
(let ((copy (wg-copy-session (or session (wg-current-session))))) |
|
4379 |
(when (wg-session-parameters copy) |
|
4380 |
(wg-asetf (wg-session-parameters copy) (wg-pickel it))) |
|
4381 |
(wg-asetf (wg-session-workgroup-list copy) |
|
4382 |
(cl-mapcar 'wg-pickel-workgroup-parameters it)) |
|
4383 |
copy)) |
|
4384 |
|
|
4385 |
(defun wg-unpickel-session-parameters (session) |
|
4386 |
"Return a copy of SESSION after unpickeling its parameters. |
|
4387 |
And the parameters of all its workgroups." |
|
4388 |
(let ((copy (wg-copy-session session))) |
|
4389 |
(when (wg-session-parameters copy) |
|
4390 |
(wg-asetf (wg-session-parameters copy) (wg-unpickel it))) |
|
4391 |
(wg-asetf (wg-session-workgroup-list copy) |
|
4392 |
(cl-mapcar 'wg-unpickel-workgroup-parameters it)) |
|
4393 |
copy)) |
|
4394 |
|
|
4395 |
(defvar wg-buffer-workgroup nil |
|
4396 |
"Associating each buffer with the workgroup. |
|
4397 |
In which it most recently appeared.") |
|
4398 |
(make-variable-buffer-local 'wg-buffer-workgroup) |
|
4399 |
|
|
4400 |
(defun wg-workgroup-associated-buf-uids (&optional workgroup) |
|
4401 |
"Return a new list containing all of WORKGROUP's associated buf uids." |
|
4402 |
(awhen (or workgroup (wg-current-workgroup t)) |
|
4403 |
(append (wg-workgroup-strong-buf-uids it) |
|
4404 |
(wg-workgroup-weak-buf-uids it)))) |
|
4405 |
|
|
4406 |
(defun wg-workgroup-associated-bufs (workgroup) |
|
4407 |
"Return a new list containing all of WORKGROUP's associated bufs." |
|
4408 |
(delete nil (mapcar 'wg-find-buf-by-uid |
|
4409 |
(wg-workgroup-associated-buf-uids workgroup)))) |
|
4410 |
|
|
4411 |
(defun wg-workgroup-associated-buffers (workgroup) |
|
4412 |
"Return a new list containing all of WORKGROUP's associated buffer objects." |
|
4413 |
(delete nil (mapcar 'wg-restore-buffer |
|
4414 |
(wg-workgroup-associated-bufs workgroup)))) |
|
4415 |
|
|
4416 |
(defun wg-workgroup-strongly-associate-bufobj (workgroup bufobj) |
|
4417 |
"Strongly associate BUFOBJ with WORKGROUP." |
|
4418 |
(let* ((uid (wg-bufobj-uid-or-add bufobj)) |
|
4419 |
(remp (wg-removef-p uid (wg-workgroup-weak-buf-uids workgroup) |
|
4420 |
:test 'string=)) |
|
4421 |
(addp (wg-pushnew-p uid (wg-workgroup-strong-buf-uids workgroup) |
|
4422 |
:test 'string=))) |
|
4423 |
(when (or remp addp) |
|
4424 |
(wg-flag-workgroup-modified workgroup) |
|
4425 |
bufobj))) |
|
4426 |
|
|
4427 |
(defun wg-workgroup-weakly-associate-bufobj (workgroup bufobj) |
|
4428 |
"Weakly associate BUFOBJ with WORKGROUP." |
|
4429 |
(let* ((uid (wg-bufobj-uid-or-add bufobj)) |
|
4430 |
(remp (wg-removef-p uid (wg-workgroup-strong-buf-uids workgroup) |
|
4431 |
:test 'string=)) |
|
4432 |
(addp (wg-pushnew-p uid (wg-workgroup-weak-buf-uids workgroup) |
|
4433 |
:test 'string=))) |
|
4434 |
(when (or remp addp) |
|
4435 |
(wg-flag-workgroup-modified workgroup) |
|
4436 |
bufobj))) |
|
4437 |
|
|
4438 |
(defun wg-workgroup-associate-bufobj (workgroup bufobj &optional weak) |
|
4439 |
"Associate BUFOBJ with WORKGROUP. |
|
4440 |
WEAK non-nil means weakly associate it. Otherwise strongly associate it." |
|
4441 |
(if weak (wg-workgroup-weakly-associate-bufobj workgroup bufobj) |
|
4442 |
(wg-workgroup-strongly-associate-bufobj workgroup bufobj))) |
|
4443 |
|
|
4444 |
(defun wg-workgroup-dissociate-bufobj (workgroup bufobj) |
|
4445 |
"Dissociate BUFOBJ from WORKGROUP." |
|
4446 |
(let* ((uid (wg-bufobj-uid-or-add bufobj)) |
|
4447 |
(rem1p (wg-removef-p uid (wg-workgroup-strong-buf-uids workgroup) |
|
4448 |
:test 'string=)) |
|
4449 |
(rem2p (wg-removef-p uid (wg-workgroup-weak-buf-uids workgroup) |
|
4450 |
:test 'string=))) |
|
4451 |
(when (or rem1p rem2p) |
|
4452 |
(wg-flag-workgroup-modified workgroup) |
|
4453 |
bufobj))) |
|
4454 |
|
|
4455 |
(defun wg-workgroup-dissociate-weakly-associated-buffers (workgroup) |
|
4456 |
"Dissociate from WORKGROUP all weakly associated buffers." |
|
4457 |
(when (wg-workgroup-weak-buf-uids workgroup) |
|
4458 |
(wg-flag-workgroup-modified workgroup) |
|
4459 |
(setf (wg-workgroup-weak-buf-uids workgroup) nil))) |
|
4460 |
|
|
4461 |
(defun wg-workgroup-dissociate-strongly-associated-buffers (workgroup) |
|
4462 |
"Dissociate from WORKGROUP all strongly associated buffers." |
|
4463 |
(when (wg-workgroup-strong-buf-uids workgroup) |
|
4464 |
(wg-flag-workgroup-modified workgroup) |
|
4465 |
(setf (wg-workgroup-strong-buf-uids workgroup) nil))) |
|
4466 |
|
|
4467 |
(defun wg-workgroup-dissociate-all-buffers (workgroup) |
|
4468 |
"Dissociate from WORKGROUP all its associated buffers." |
|
4469 |
(wg-workgroup-dissociate-weakly-associated-buffers workgroup) |
|
4470 |
(wg-workgroup-dissociate-strongly-associated-buffers workgroup)) |
|
4471 |
|
|
4472 |
(defun wg-auto-dissociate-buffer-hook () |
|
4473 |
"`kill-buffer-hook' that automatically dissociates buffers from workgroups." |
|
4474 |
(when wg-dissociate-buffer-on-kill-buffer |
|
4475 |
(awhen (wg-current-workgroup t) |
|
4476 |
(wg-workgroup-dissociate-bufobj it (current-buffer))))) |
|
4477 |
|
|
4478 |
(defun wg-associate-buffer-with-workgroup (&optional workgroup buffer weak) |
|
4479 |
"Associate BUFFER with WORKGROUP. |
|
4480 |
WEAK non-nil means weakly associate BUFFER." |
|
4481 |
(interactive (list nil nil current-prefix-arg)) |
|
4482 |
(let* ((workgroup (wg-get-workgroup workgroup)) |
|
4483 |
(buffer (or buffer (current-buffer))) |
|
4484 |
(bname (buffer-name buffer)) |
|
4485 |
(wgname (wg-workgroup-name workgroup))) |
|
4486 |
(if (wg-workgroup-associate-bufobj workgroup buffer weak) |
|
4487 |
(wg-message "%s-associated %S with %s" |
|
4488 |
(if weak "Weakly" "Strongly") bname wgname) |
|
4489 |
(wg-message "%S is already associated with %s" bname wgname)))) |
|
4490 |
|
|
4491 |
(defun wg-associate-visible-buffers-with-workgroup (&optional workgroup weak) |
|
4492 |
"Associate all buffers visible in `selected-frame' with WORKGROUP. |
|
4493 |
WEAK non-nil means weakly associate them. Otherwise strongly |
|
4494 |
associate them." |
|
4495 |
(interactive (list nil current-prefix-arg)) |
|
4496 |
(let ((workgroup (wg-get-workgroup workgroup)) |
|
4497 |
(buffers (mapcar 'window-buffer (window-list)))) |
|
4498 |
(dolist (buffer buffers) |
|
4499 |
(wg-workgroup-associate-bufobj workgroup buffer weak)) |
|
4500 |
(wg-fontified-message |
|
4501 |
(:cmd (format "%s associated: " (if weak "Weakly" "Strongly"))) |
|
4502 |
(wg-display-internal 'wg-buffer-display buffers)))) |
|
4503 |
|
|
4504 |
(defun wg-dissociate-buffer-from-workgroup (&optional workgroup buffer) |
|
4505 |
"Dissociate BUFFER from WORKGROUP." |
|
4506 |
(interactive (list nil nil)) |
|
4507 |
(let ((workgroup (wg-get-workgroup workgroup)) |
|
4508 |
(buffer (or buffer (current-buffer)))) |
|
4509 |
(wg-message |
|
4510 |
(if (wg-workgroup-dissociate-bufobj workgroup buffer) |
|
4511 |
"Dissociated %S from %s" "%S isn't associated with %s") |
|
4512 |
(wg-buffer-name buffer) |
|
4513 |
(wg-workgroup-name workgroup)))) |
|
4514 |
|
|
4515 |
(defun wg-associate-buffers (workgroup window-or-emacs-window-tree) |
|
4516 |
"Associate the buffers visible in window elements of |
|
4517 |
WINDOW-OR-EMACS-WINDOW-TREE with the given WORKGROUP. |
|
4518 |
WINDOW-OR-EMACS-WINDOW-TREE must be either a window or a tree of |
|
4519 |
the form produced by `(car (window-tree))'." |
|
4520 |
(if (windowp window-or-emacs-window-tree) |
|
4521 |
(with-current-buffer (window-buffer window-or-emacs-window-tree) |
|
4522 |
(setq wg-buffer-workgroup workgroup)) |
|
4523 |
(dolist (w (cddr window-or-emacs-window-tree)) |
|
4524 |
(when w (wg-associate-buffers workgroup w))))) |
|
4525 |
|
|
4526 |
(defun wg-workgroup-bufobj-association-type (workgroup bufobj) |
|
4527 |
"Return BUFOBJ's association-type in WORKGROUP, or nil if unassociated." |
|
4528 |
(let ((uid (wg-bufobj-uid-or-add bufobj))) |
|
4529 |
(or (and (member uid (wg-workgroup-strong-buf-uids workgroup)) 'strong) |
|
4530 |
(and (member uid (wg-workgroup-weak-buf-uids workgroup)) 'weak)))) |
|
4531 |
|
|
4532 |
(defun wg-associate-frame-buffers () |
|
4533 |
"Associate visible buffers with the current workgroup. |
|
4534 |
Unless it is currently being deactivated." |
|
4535 |
(awhen (wg-current-workgroup :noerror) |
|
4536 |
(unless (member it wg-deactivation-list) |
|
4537 |
(wg-associate-buffers it (car (window-tree)))))) |
|
4538 |
|
|
4539 |
(defun wg-add-or-remove-workgroups-hooks (remove) |
|
4540 |
"Add or remove all of Workgroups' hooks, depending on REMOVE." |
|
4541 |
(wg-add-or-remove-hooks |
|
4542 |
remove |
|
4543 |
'kill-emacs-query-functions 'wg-save-session-on-emacs-exit |
|
4544 |
'delete-frame-hook 'wg-update-working-wconfig-on-delete-frame |
|
4545 |
'after-make-frame-functions 'wg-update-working-wconfig-on-make-frame |
|
4546 |
'wg-pre-window-configuration-change-hook 'wg-update-working-wconfig-hook |
|
4547 |
'window-configuration-change-hook 'wg-flag-window-configuration-changed |
|
4548 |
'post-command-hook 'wg-undoify-window-configuration-change |
|
4549 |
'minibuffer-exit-hook 'wg-flag-just-exited-minibuffer |
|
4550 |
'kill-buffer-hook 'wg-update-buffer-in-buf-list |
|
4551 |
'kill-buffer-hook 'wg-auto-dissociate-buffer-hook |
|
4552 |
;;'window-configuration-change-hook 'wg-associate-frame-buffers |
|
4553 |
)) |
|
4554 |
|
|
4555 |
;;;###autoload |
|
4556 |
(defun workgroups-mode (&optional arg) |
|
4557 |
"Turn `workgroups-mode' on and off. |
|
4558 |
ARG is nil - toggle |
|
4559 |
ARG >= 1 - turn on |
|
4560 |
ARG == 0 - turn off |
|
4561 |
ARG is anything else, turn on `workgroups-mode'." |
|
4562 |
(interactive (list current-prefix-arg)) |
|
4563 |
(setq workgroups-mode |
|
4564 |
(cond ((not arg) (not workgroups-mode)) |
|
4565 |
((integerp arg) (if (> arg 0) t nil)) |
|
4566 |
(t))) |
|
4567 |
(cond |
|
4568 |
(workgroups-mode |
|
4569 |
(if (boundp 'desktop-restore-frames) |
|
4570 |
(setq desktop-restore-frames nil)) |
|
4571 |
(wg-reset-internal) ; creates a new `wg-current-session' |
|
4572 |
(wg-add-workgroups-mode-minor-mode-entries) |
|
4573 |
(wg-enable-all-advice) |
|
4574 |
(wg-add-or-remove-workgroups-hooks nil) |
|
4575 |
(wg-change-modeline) |
|
4576 |
|
|
4577 |
;; some sr-speedbar hooks can harm |
|
4578 |
(when (featurep 'sr-speedbar) |
|
4579 |
(ad-disable-advice 'delete-other-windows 'around 'sr-speedbar-delete-other-window-advice) |
|
4580 |
(ad-disable-advice 'delete-window 'before 'sr-speedbar-delete-window-advice)) |
|
4581 |
|
|
4582 |
;; Load session |
|
4583 |
(when (and wg-session-load-on-start |
|
4584 |
(file-exists-p wg-session-file)) |
|
4585 |
(condition-case err |
|
4586 |
(wg-open-session wg-session-file) |
|
4587 |
(error (message "Error finding `wg-session-file': %s" err)))) |
|
4588 |
(run-hooks 'workgroups-mode-hook)) |
|
4589 |
(t |
|
4590 |
(wg-save-session-on-workgroups-mode-exit) |
|
4591 |
(wg-disable-all-advice) |
|
4592 |
(wg-add-or-remove-workgroups-hooks t) |
|
4593 |
(wg-remove-mode-line-display) |
|
4594 |
(run-hooks 'workgroups-mode-exit-hook))) |
|
4595 |
(wg-fontified-message |
|
4596 |
(:cmd "Workgroups Mode: ") (:msg (if workgroups-mode "on" "off"))) |
|
4597 |
(wg-create-first-wg) |
|
4598 |
workgroups-mode) |
|
4599 |
|
|
4600 |
;;;###autoload |
|
4601 |
(defun wg-help () |
|
4602 |
"Just call `apropos-command' on \"^wg-\". |
|
4603 |
There used to be a bunch of help-buffer construction stuff here, |
|
4604 |
including a `wg-help' variable that basically duplicated every |
|
4605 |
command's docstring; But why, when there's `apropos-command'?" |
|
4606 |
(interactive) |
|
4607 |
(apropos-command "^wg-")) |
|
4608 |
|
|
4609 |
;; Remove after some time |
|
4610 |
(defalias 'wg-switch-to-buffer 'switch-to-buffer) |
|
4611 |
|
|
4612 |
(provide 'workgroups2) |
|
4613 |
;;; workgroups2.el ends here |