From 2644d35db2a01b9bf1c693f04ca64761b17aa39b Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Sat, 11 Aug 2012 19:14:35 +0430 Subject: [PATCH] org, ido, workgrounps added --- TODO | 32 +- conf/dotemacs | 7 + conf/dotkuso | 24 +- conf/emacs.d/workgroups.el | 2247 +++++++++++++++++++++++++++++++++++ conf/emacs.d/workgroups.elc | Bin 0 -> 89188 bytes src/plugins/django.el | 2 +- src/plugins/nodejs.el | 2 +- 7 files changed, 2289 insertions(+), 25 deletions(-) create mode 100644 conf/emacs.d/workgroups.el create mode 100644 conf/emacs.d/workgroups.elc diff --git a/TODO b/TODO index 58aa8ad..88bb1ea 100644 --- a/TODO +++ b/TODO @@ -1,21 +1,17 @@ --- High Priority -- -[ ] define prefix key map for language plugins -[ ] define a prefix keymap for kuso-mode so some plugins can use that safely +* High Priority + define prefix key map for language plugins + define a prefix keymap for kuso-mode so some plugins can use that safely --- Normal Priority -- -[ ] Remove unkown filetypes from filelist in load-dir -[ ] Add some option for user to choose between liceenses -[ ] Review license templates -[ ] Build the debian folder nad required files for deb packages -[ ] Escape project name for a unix name "shell-quote-argument" -[X] Add a config file for user to put his/her configuration there - ("Done with custom variables") -[ ] Disable EDE and other unused menu -[ ] Allow templates file to store in subdirectories so new project can +* Normal Priority + Remove unkown filetypes from filelist in load-dir + Add some option for user to choose between liceenses + Review license templates + Build the debian folder nad required files for deb packages + Escape project name for a unix name "shell-quote-argument" + Disable EDE and other unused menu + Allow templates file to store in subdirectories so new project can have subdirectories -[ ] Improve modes.el file documentation -[ ] add the license name into kernel module c file -[ ] make sure cplugin keymap load with cplugin + Improve modes.el file documentation + add the license name into kernel module c file + make sure cplugin keymap load with cplugin --- Useful modes -- -* org mode diff --git a/conf/dotemacs b/conf/dotemacs index 83e267b..fe01958 100644 --- a/conf/dotemacs +++ b/conf/dotemacs @@ -143,3 +143,10 @@ (setq x-select-enable-clipboard t) (column-number-mode t) (global-linum-mode) + + +;; org-mode ------------------------------------------------------ +(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode)) +(add-hook 'org-mode-hook 'turn-on-font-lock) ; not needed when(global-set-key "\C-cl" 'org-store-link) +(global-set-key "\C-ca" 'org-agenda) +(global-set-key "\C-cb" 'org-iswitchb) diff --git a/conf/dotkuso b/conf/dotkuso index 8fd8313..2039dbb 100644 --- a/conf/dotkuso +++ b/conf/dotkuso @@ -42,9 +42,9 @@ ;; If there is more than one, they won't work right. '(c-plugin nil) ;'(color-theme-selection "Arjen" nil (color-theme_seldefcustom)) - '(developer-email "lxsameer@gnu.org") - '(developer-name "Sameer Rahmani") - '(kuso-workspace "/home/lxsameer/src/") + '(developer-email "--EMAIL--") + '(developer-name "--FULLNAME--") + '(kuso-workspace "--WORKSPACE--") '(face-font-family-alternatives (quote (("courier" "Monospace" "fixed") ("courier" "CMU Typewriter Text" "fixed") ("Sans Serif" "helv" "helvetica" "arial" "fixed") ("helv" "helvetica" "arial" "fixed")))) '(inhibit-startup-screen t) '(rng-nxml-auto-validate-flag nil)) @@ -67,7 +67,7 @@ (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) - (list "/home/lxsameer/.kuso.d/pyemacs.sh" (list local-file)))) + (list "--ADDR--/pyemacs.sh" (list local-file)))) (add-to-list 'flymake-allowed-file-name-masks '("\\.py$" flymake-pyflakes-init))) @@ -131,7 +131,7 @@ (other . "linux"))) ;; KUSO configuration ------------------------------------------------------------ -(load-file "/home/lxsameer/src/kuso-ide/src/kuso-ide.el") +(load-file "--KUSOHOME--/src/kuso-ide.el") (kuso-mode) ;; General configuration --------------------------------------------------------- @@ -143,3 +143,17 @@ (setq x-select-enable-clipboard t) (column-number-mode t) (global-linum-mode) + +;; IDO config ---------------------------------------------------------------- +(require 'ido) +(ido-mode t) + +;; Workgroups ---------------------------------------------------------------- +(require 'workgroups) + + +;; org-mode ------------------------------------------------------ +(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode)) +(add-hook 'org-mode-hook 'turn-on-font-lock) ; not needed when(global-set-key "\C-cl" 'org-store-link) +(global-set-key "\C-ca" 'org-agenda) +(global-set-key "\C-cb" 'org-iswitchb) diff --git a/conf/emacs.d/workgroups.el b/conf/emacs.d/workgroups.el new file mode 100644 index 0000000..4dccd65 --- /dev/null +++ b/conf/emacs.d/workgroups.el @@ -0,0 +1,2247 @@ +;;; workgroups.el --- workgroups for windows (for Emacs) + +;; Copyright (C) 2010 tlh + +;; File: workgroups.el +;; Author: tlh +;; Created: 2010-07-22 +;; Version: 0.2.0 +;; Keywords: session management window-configuration persistence + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of +;; the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be +;; useful, but WITHOUT ANY WARRANTY; without even the implied +;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +;; PURPOSE. See the GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public +;; License along with this program; if not, write to the Free +;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +;; MA 02111-1307 USA + +;;; Commentary: +;; +;; See the file README.md in `workgroups.el's directory +;; +;;; Installation: +;; +;;; Usage: +;; + +;;; Symbol naming conventions: +;; +;; W always refers to a Workgroups window or window tree. +;; +;; WT always refers to a Workgroups window tree. +;; +;; SW always refers to a sub-window or sub-window-tree of a wtree. +;; +;; WL always refers to the window list of a wtree. +;; +;; LN, TN, RN and BN always refer to the LEFT, TOP, RIGHT and BOTTOM edges of an +;; edge list, where N is a differentiating integer. +;; +;; LS, HS, LB and HB always refer to the LOW-SIDE, HIGH-SIDE, LOW-BOUND and +;; HIGH-BOUND of a bounds list. See `wg-with-bounds'. +;; + + +;;; Code: + +(require 'cl) + + +;;; consts + +(defconst wg-version "0.2.0" + "Current version of workgroups.") + +(defconst wg-persisted-workgroups-tag 'workgroups + "This should be the car of any list of persisted workgroups.") + + +;;; customization + +(defgroup workgroups nil + "Workgroup for Windows -- Emacs session manager" + :group 'convenience + :version wg-version) + +(defcustom workgroups-mode-hook nil + "Hook run when workgroups-mode is turned on." + :type 'hook + :group 'workgroups) + +;; FIXME: This complicates loading and byte-comp too much +(defcustom wg-prefix-key (kbd "C-z") + "Workgroups' prefix key." + :type 'string + :group 'workgroups + :set (lambda (sym val) + (custom-set-default sym val) + (when (and (boundp 'workgroups-mode) workgroups-mode) + (wg-set-prefix-key)) + val)) + +(defcustom wg-switch-hook nil + "Hook run by `wg-switch-to-workgroup'." + :type 'hook + :group 'workgroups) + +(defcustom wg-no-confirm nil + "Non-nil means don't request confirmation before various +destructive operations, like `wg-reset'. This doesn't modify +query-for-save behavior. Use +`wg-query-for-save-on-workgroups-mode-exit' and +`wg-query-for-save-on-emacs-exit' for that." + :type 'boolean + :group 'workgroups) + +(defcustom wg-mode-line-on t + "Toggles Workgroups' mode-line display." + :type 'boolean + :group 'workgroups + :set (lambda (sym val) + (custom-set-default sym val) + (force-mode-line-update))) + +(defcustom wg-kill-ring-size 20 + "Maximum length of the `wg-kill-ring'." + :type 'integer + :group 'workgroups) + +(defcustom wg-warning-timeout 0.7 + "Seconds to display minibuffer warning messages." + :type 'float + :group 'workgroups) + + +;; save and load customization + +(defcustom wg-switch-on-load t + "Non-nil means switch to the first workgroup in a file when it's loaded." + :type 'boolean + :group 'workgroups) + +(defcustom wg-query-for-save-on-emacs-exit t + "Non-nil means query to save changes before exiting Emacs. +Exiting workgroups removes its `kill-emacs-query-functions' hook, +so if you set this to nil, you may want to set +`wg-query-for-save-on-workgroups-exit' to t." + :type 'boolean + :group 'workgroups) + +(defcustom wg-query-for-save-on-workgroups-mode-exit t + "Non-nil means query to save changes before exiting `workgroups-mode'. +Exiting workgroups removes its `kill-emacs-query-functions' hook, +which is why this variable exists." + :type 'boolean + :group 'workgroups) + + +;; workgroup restoration customization + +(defcustom wg-default-buffer "*scratch*" + "Buffer switched to when a blank workgroup is created. +Also used when a window's buffer can't be restored." + :type 'string + :group 'workgroups) + +(defcustom wg-restore-position nil + "Non-nil means restore frame position on workgroup restore." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-scroll-bars t + "Non-nil means restore scroll-bar settings on workgroup restore." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-fringes t + "Non-nil means restore fringe settings on workgroup restore." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-margins t + "Non-nil means restore margin settings on workgroup restore." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-mbs-window t + "Non-nil means restore `minibuffer-scroll-window' on workgroup restore." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-point t + "Non-nil means restore `point' on workgroup restore. +This is included mainly so point restoration can be suspended +during `wg-morph' -- you probably want this on." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-point-max t + "Controls point restoration when point is at `point-max'. +If `point' is at `point-max' when a wconfig is created, put +`point' back at `point-max' when the wconfig is restored, even if +`point-max' has increased in the meantime. This is useful +in (say) irc buffers where `point-max' is constantly increasing." + :type 'boolean + :group 'workgroups) + +(defcustom wg-restore-dedicated t + "Non-nil means restore `window-dedicated-p' on workgroup restore." + :type 'boolean + :group 'workgroups) + + +;; morph customization + +(defcustom wg-morph-on t + "Non-nil means use `wg-morph' when restoring wconfigs." + :type 'boolean + :group 'workgroups) + +(defcustom wg-morph-hsteps 9 + "Columns/iteration to step window edges during `wg-morph'. +Values lower than 1 are invalid." + :type 'integer + :group 'workgroups) + +(defcustom wg-morph-vsteps 3 + "Rows/iteration to step window edges during `wg-morph'. +Values lower than 1 are invalid." + :type 'integer + :group 'workgroups) + +(defcustom wg-morph-terminal-hsteps 3 + "Used instead of `wg-morph-hsteps' in terminal frames. +If nil, `wg-morph-hsteps' is used." + :type 'integer + :group 'workgroups) + +(defcustom wg-morph-terminal-vsteps 1 + "Used instead of `wg-morph-vsteps' in terminal frames. +If nil, `wg-morph-vsteps' is used." + :type 'integer + :group 'workgroups) + +(defcustom wg-morph-sit-for-seconds 0 + "Seconds to `sit-for' between `wg-morph' iterations. +Should probably be zero unless `redisplay' is *really* fast on +your machine, and `wg-morph-hsteps' and `wg-morph-vsteps' are +already set as low as possible." + :type 'float + :group 'workgroups) + +(defcustom wg-morph-truncate-partial-width-windows t + "Bound to `truncate-partial-width-windows' during `wg-morph'. +Non-nil, this prevents weird-looking continuation line behavior, +and can speed up morphing a little. Lines jump back to their +wrapped status when `wg-morph' is complete." + :type 'boolean + :group 'workgroups) + + +;; display customization + +(defcustom wg-use-faces t + "Nil means don't use faces in various displays." + :type 'boolean + :group 'workgroups) + +(defcustom wg-mode-line-left-brace "(" + "String to the left of the mode-line display." + :type 'string + :group 'workgroups) + +(defcustom wg-mode-line-right-brace ")" + "String to the right of the mode-line display." + :type 'string + :group 'workgroups) + +(defcustom wg-mode-line-divider ":" + "String between workgroup position and name in the mode-line display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-left-brace "( " + "String to the left of the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-right-brace " )" + "String to the right of the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-divider " | " + "String between workgroup names in the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-current-workgroup-left-decor "-<{ " + "String to the left of the current workgroup name in the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-current-workgroup-right-decor " }>-" + "String to the right of the current workgroup name in the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-previous-workgroup-left-decor "*" + "String to the left of the previous workgroup name in the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-display-previous-workgroup-right-decor "*" + "String to the right of the previous workgroup name in the list display." + :type 'string + :group 'workgroups) + +(defcustom wg-time-format "%H:%M:%S %A, %B %d %Y" + "Format string for time display. Passed to `format-time-string'." + :type 'string + :group 'workgroups) + +(defcustom wg-display-battery t + "Non-nil means include `battery', when available, in the time display." + :type 'boolean + :group 'workgroups) + + +;;; vars + +(defvar wg-file nil + "Current workgroups file.") + +(defvar wg-list nil + "List of currently defined workgroups.") + +(defvar wg-frame-table (make-hash-table) + "Hash table keyed on frame, storing each frame's state.") + +(defvar wg-dirty nil + "Non-nil when there are unsaved changes.") + +(defvar wg-kill-ring nil + "Ring of killed or kill-ring-saved wconfigs.") + +(defvar wg-window-min-width 2 + "Bound to `window-min-width' when restoring wtrees. ") + +(defvar wg-window-min-height 1 + "Bound to `window-min-height' when restoring wtrees.") + +(defvar wg-window-min-pad 2 + "Added to `wg-window-min-foo' to produce the actual minimum window size.") + +(defvar wg-actual-min-width (+ wg-window-min-width wg-window-min-pad) + "Actual minimum window width when creating windows.") + +(defvar wg-actual-min-height (+ wg-window-min-height wg-window-min-pad) + "Actual minimum window height when creating windows.") + +(defvar wg-min-edges `(0 0 ,wg-actual-min-width ,wg-actual-min-height) + "Smallest allowable edge list of windows created by Workgroups.") + +(defvar wg-null-edges '(0 0 0 0) + "Null edge list.") + +(defvar wg-morph-max-steps 200 + "Maximum `wg-morph' iterations before forcing exit.") + +(defvar wg-morph-no-error t + "Non-nil means ignore errors during `wg-morph'. +The error message is sent to *messages* instead. This was added +when `wg-morph' was unstable, so that the screen wouldn't be left +in an inconsistent state. It's unnecessary now, as `wg-morph' is +stable, but is left here for the time being.") + +(defvar wg-last-message nil + "Holds the last message Workgroups sent to the echo area.") + +(defvar wg-selected-window nil + "Used during wconfig restoration to hold the selected window.") + +(defvar wg-face-abbrevs nil + "Assoc list mapping face abbreviations to face names.") + + +;;; faces + +(defmacro wg-defface (face key spec doc &rest args) + "`defface' wrapper adding a lookup key used by `wg-fontify'." + (declare (indent 2)) + `(progn + (pushnew (cons ,key ',face) wg-face-abbrevs :test #'equal) + (defface ,face ,spec ,doc ,@args))) + +(wg-defface wg-current-workgroup-face :cur + '((((class color)) (:foreground "white"))) + "Face used for the name of the current workgroup in the list display." + :group 'workgroups) + +(wg-defface wg-previous-workgroup-face :prev + '((((class color)) (:foreground "light sky blue"))) + "Face used for the name of the previous workgroup in the list display." + :group 'workgroups) + +(wg-defface wg-other-workgroup-face :other + '((((class color)) (:foreground "light slate grey"))) + "Face used for the names of other workgroups in the list display." + :group 'workgroups) + +(wg-defface wg-command-face :cmd + '((((class color)) (:foreground "aquamarine"))) + "Face used for command/operation strings." + :group 'workgroups) + +(wg-defface wg-divider-face :div + '((((class color)) (:foreground "light slate blue"))) + "Face used for dividers." + :group 'workgroups) + +(wg-defface wg-brace-face :brace + '((((class color)) (:foreground "light slate blue"))) + "Face used for left and right braces." + :group 'workgroups) + +(wg-defface wg-message-face :msg + '((((class color)) (:foreground "light sky blue"))) + "Face used for messages." + :group 'workgroups) + +(wg-defface wg-mode-line-face :mode + '((((class color)) (:foreground "light sky blue"))) + "Face used for workgroup position and name in the mode-line display." + :group 'workgroups) + +(wg-defface wg-filename-face :file + '((((class color)) (:foreground "light sky blue"))) + "Face used for filenames." + :group 'workgroups) + +(wg-defface wg-frame-face :frame + '((((class color)) (:foreground "white"))) + "Face used for frame names." + :group 'workgroups) + + +;;; utils + + +;; functions used in macros: +(eval-and-compile + + (defun wg-take (list n) + "Return a list of the first N elts in LIST." + (butlast list (- (length list) n))) + + (defun wg-partition (list n &optional step) + "Return list of N-length sublists of LIST, offset by STEP. +Iterative to prevent stack overflow." + (let (acc) + (while list + (push (wg-take list n) acc) + (setq list (nthcdr (or step n) list))) + (nreverse acc))) + ) + +(defmacro wg-with-gensyms (syms &rest body) + "Bind all symbols in SYMS to `gensym's, and eval BODY." + (declare (indent 1)) + `(let (,@(mapcar (lambda (sym) `(,sym (gensym))) syms)) ,@body)) + +(defmacro wg-dbind (args expr &rest body) + "Abbreviation of `destructuring-bind'." + (declare (indent 2)) + `(destructuring-bind ,args ,expr ,@body)) + +(defmacro wg-dohash (spec &rest body) + "do-style wrapper for `maphash'." + (declare (indent 1)) + (wg-dbind (key val table &optional return) spec + `(progn (maphash (lambda (,key ,val) ,@body) ,table) ,return))) + +(defmacro wg-doconcat (spec &rest body) + "do-style wrapper for `mapconcat'." + (declare (indent 1)) + (wg-dbind (elt seq &optional sep) spec + `(mapconcat (lambda (,elt) ,@body) ,seq (or ,sep "")))) + +(defmacro wg-docar (spec &rest body) + "do-style wrapper for `mapcar'." + (declare (indent 1)) + `(mapcar (lambda (,(car spec)) ,@body) ,(cadr spec))) + +(defmacro wg-get-some (spec &rest body) + "do-style wrapper for `some'. +Returns the elt itself, rather than the return value of the form." + (declare (indent 1)) + (wg-dbind (sym list) spec + `(some (lambda (,sym) (when (progn ,@body) ,sym)) ,list))) + +(defmacro wg-when-let (binds &rest body) + "Like `let*', but only eval BODY when all BINDS are non-nil." + (declare (indent 1)) + (wg-dbind (bind . binds) binds + (when (consp bind) + `(let (,bind) + (when ,(car bind) + ,(if (not binds) `(progn ,@body) + `(wg-when-let ,binds ,@body))))))) + +(defmacro wg-until (test &rest body) + "`while' not." + (declare (indent 1)) + `(while (not ,test) ,@body)) + +(defmacro wg-aif (test then &rest else) + "Anaphoric `if'." + (declare (indent 2)) + `(let ((it ,test)) (if it ,then ,@else))) + +(defmacro wg-awhen (test &rest body) + "Anaphoric `when'." + (declare (indent 1)) + `(wg-aif ,test (progn ,@body))) + +(defmacro wg-aand (&rest args) + "Anaphoric `and'." + (declare (indent defun)) + (cond ((null args) t) + ((null (cdr args)) (car args)) + (t `(aif ,(car args) (aand ,@(cdr args)))))) + +(defun wg-step-to (n m step) + "Increment or decrement N toward M by STEP. +Return M when the difference between N and M is less than STEP." + (cond ((= n m) n) + ((< n m) (min (+ n step) m)) + ((> n m) (max (- n step) m)))) + +(defun wg-within (num lo hi &optional hi-inclusive) + "Return t when NUM is within bounds LO and HI. +HI-INCLUSIVE non-nil means the HI bound is inclusive." + (and (>= num lo) (if hi-inclusive (<= num hi) (< num hi)))) + +(defun wg-last1 (list) + "Return the last element of LIST." + (car (last list))) + +(defun wg-leave (list n) + "Return a list of the last N elts in LIST." + (nthcdr (- (length list) n) list)) + +(defun wg-rnth (n list) + "Return the Nth element of LIST, counting from the end." + (nth (- (length list) n 1) list)) + +(defun wg-insert-elt (elt list &optional pos) + "Insert ELT into LIST at POS or the end." + (let* ((len (length list)) (pos (or pos len))) + (when (wg-within pos 0 len t) + (append (wg-take list pos) (cons elt (nthcdr pos list)))))) + +(defun wg-move-elt (elt list pos) + "Move ELT to position POS in LIST." + (when (member elt list) + (wg-insert-elt elt (remove elt list) pos))) + +(defun wg-cyclic-offset-elt (elt list n) + "Cyclically offset ELT's position in LIST by N." + (wg-when-let ((pos (position elt list))) + (wg-move-elt elt list (mod (+ n pos) (length list))))) + +(defun wg-cyclic-nth-from-elt (elt list n) + "Return the elt in LIST N places cyclically from ELT. +If ELT is not present is LIST, return nil." + (wg-when-let ((pos (position elt list))) + (nth (mod (+ pos n) (length list)) list))) + +(defun wg-util-swap (elt1 elt2 list) + "Return a copy of LIST with ELT1 and ELT2 swapped. +Return nil when ELT1 and ELT2 aren't both present." + (wg-when-let ((p1 (position elt1 list)) + (p2 (position elt2 list))) + (wg-move-elt elt1 (wg-move-elt elt2 list p1) p2))) + +(defun wg-aget (alist key) + "Return the value of KEY in ALIST. Uses `assq'." + (cdr (assq key alist))) + +(defun wg-acopy (alist) + "Return a copy of ALIST's toplevel list structure." + (wg-docar (kvp alist) (cons (car kvp) (cdr kvp)))) + +(defun wg-aset (alist key val) + "Set KEY's value to VAL in ALIST. +If KEY already exists in ALIST, destructively set its value. +Otherwise, cons a new key-value-pair onto ALIST." + (wg-aif (assq key alist) (progn (setcdr it val) alist) + (cons (cons key val) alist))) + +(defun wg-aput (alist &rest key-value-pairs) + "Add all KEY-VALUE-PAIRS to a copy of ALIST, and return the copy." + (flet ((rec (alist kvps) (if (not kvps) alist + (wg-dbind (k v . rest) kvps + (wg-aset (rec alist rest) k v))))) + (rec (wg-acopy alist) key-value-pairs))) + +(defun wg-get-alist (key val alist-list) + "Return the first alist in ALIST-LIST containing KEY and VAL." + (catch 'res + (dolist (alist alist-list) + (when (equal val (cdr (assoc key alist))) + (throw 'res alist))))) + +(defmacro wg-abind (alist binds &rest body) + "Bind values in ALIST to symbols in BINDS, then eval BODY. +If an elt of BINDS is a symbol, use it as both the bound variable +and the key in ALIST. If it is a cons, use the car as the bound +variable, and the cadr as the key." + (declare (indent 2)) + (wg-with-gensyms (asym) + `(let* ((,asym ,alist) + ,@(wg-docar (bind binds) + (let ((c (consp bind))) + `(,(if c (car bind) bind) + (wg-aget ,asym ',(if c (cadr bind) bind)))))) + ,@body))) + +(defmacro wg-fill-keymap (keymap &rest binds) + "Return KEYMAP after defining in it all keybindings in BINDS." + (declare (indent 1)) + (wg-with-gensyms (km) + `(let ((,km ,keymap)) + ,@(wg-docar (b (wg-partition binds 2)) + `(define-key ,km (kbd ,(car b)) ,(cadr b))) + ,km))) + +(defun wg-write-sexp-to-file (sexp file) + "Write the printable representation of SEXP to FILE." + (with-temp-buffer + (let (print-level print-length) + (insert (format "%S" sexp)) + (write-file file)))) + +(defun wg-read-sexp-from-file (file) + "Read and return an sexp from FILE." + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (read (current-buffer)))) + +(defun wg-read-object (prompt test warning &rest args) + "PROMPT for an object that satisfies TEST, WARNING if necessary. +ARGS are `read-from-minibuffer's args, after PROMPT." + (let ((obj (apply #'read-from-minibuffer prompt args))) + (wg-until (funcall test obj) + (message warning) + (sit-for wg-warning-timeout) + (setq obj (apply #'read-from-minibuffer prompt args))) + obj)) + + +;;; workgroups utils + +(defun wg-type-of (obj) + "Return workgroups' object type of OBJ." + (wg-aget obj 'type)) + +(defun wg-type-p (type obj) + "Return t if OBJ is of type TYPE, nil otherwise." + (and (consp obj) (eq type (wg-type-of obj)))) + +(defun wg-type-check (type obj &optional noerror) + "Throw an error if OBJ is not of type TYPE." + (or (wg-type-p type obj) + (unless noerror + (error "%s is not of type %s" obj type)))) + +(defun wg-cyclic-nth-from-frame (&optional n frame) + "Return the frame N places away from FRAME in `frame-list' cyclically. +N defaults to 1, and FRAME defaults to `selected-frame'." + (wg-cyclic-nth-from-elt + (or frame (selected-frame)) (frame-list) (or n 1))) + +(defun wg-add-face (facekey str) + "Return a copy of STR fontified according to FACEKEY. +FACEKEY must be a key in `wg-face-abbrevs'." + (let ((face (wg-aget wg-face-abbrevs facekey)) + (str (copy-seq str))) + (unless face (error "No face with key %s" facekey)) + (if (not wg-use-faces) str + (put-text-property 0 (length str) 'face face str) + str))) + +(defmacro wg-fontify (&rest specs) + "A small fontification DSL. *WRITEME*" + (declare (indent defun)) + `(concat + ,@(wg-docar (spec specs) + (typecase spec + (cons (if (keywordp (car spec)) + `(wg-add-face + ,(car spec) + ,(if (stringp (cadr spec)) + (cadr spec) + `(format "%s" ,(cadr spec)))) + `(progn ,spec))) + (string `(progn ,spec)) + (atom `(format "%s" ,spec)))))) + +(defun wg-error-on-active-minibuffer () + "Throw an error when the minibuffer is active." + (when (active-minibuffer-window) + (error "Workgroup operations aren't permitted while the \ +minibuffer is active."))) + + +;;; type predicates + +(defun wg-window-p (obj) + "Return t if OBJ is a Workgroups window, nil otherwise." + (wg-type-p 'window obj)) + +(defun wg-wtree-p (obj) + "Return t if OBJ is a Workgroups window tree, nil otherwise." + (wg-type-p 'wtree obj)) + +(defun wg-wconfig-p (obj) + "Return t if OBJ is a Workgroups window config, nil otherwise." + (wg-type-p 'wconfig obj)) + +(defun wg-workgroup-p (obj) + "Return t if OBJ is a workgroup, nil otherwise." + (wg-type-p 'workgroup obj)) + + +;; window config utils + +;; Accessors for common fields: +(defun wg-dir (w) (wg-aget w 'dir)) +(defun wg-edges (w) (wg-aget w 'edges)) +(defun wg-wlist (w) (wg-aget w 'wlist)) +(defun wg-wtree (w) (wg-aget w 'wtree)) + +(defun wg-min-size (dir) + "Return the minimum window size in split direction DIR." + (if dir wg-window-min-height wg-window-min-width)) + +(defun wg-actual-min-size (dir) + "Return the actual minimum window size in split direction DIR." + (if dir wg-actual-min-height wg-actual-min-width)) + +(defmacro wg-with-edges (w spec &rest body) + "Bind W's edge list to SPEC and eval BODY." + (declare (indent 2)) + `(wg-dbind ,spec (wg-edges ,w) ,@body)) + +(defun wg-put-edges (w left top right bottom) + "Return a copy of W with an edge list of LEFT TOP RIGHT and BOTTOM." + (wg-aput w 'edges (list left top right bottom))) + +(defmacro wg-with-bounds (w dir spec &rest body) + "Bind SPEC to W's bounds in DIR, and eval BODY. +\"Bounds\" are a direction-independent way of dealing with edge lists." + (declare (indent 3)) + (wg-with-gensyms (dir-sym l1 t1 r1 b1) + (wg-dbind (ls1 hs1 lb1 hb1) spec + `(wg-with-edges ,w (,l1 ,t1 ,r1 ,b1) + (cond (,dir (let ((,ls1 ,l1) (,hs1 ,r1) (,lb1 ,t1) (,hb1 ,b1)) + ,@body)) + (t (let ((,ls1 ,t1) (,hs1 ,b1) (,lb1 ,l1) (,hb1 ,r1)) + ,@body))))))) + +(defun wg-put-bounds (w dir ls hs lb hb) + "Set W's edges in DIR with bounds LS HS LB and HB." + (if dir (wg-put-edges w ls lb hs hb) (wg-put-edges w lb ls hb hs))) + +(defun wg-step-edges (edges1 edges2 hstep vstep) + "Return W1's edges stepped once toward W2's by HSTEP and VSTEP." + (wg-dbind (l1 t1 r1 b1) edges1 + (wg-dbind (l2 t2 r2 b2) edges2 + (let ((left (wg-step-to l1 l2 hstep)) + (top (wg-step-to t1 t2 vstep))) + (list left top + (+ left (wg-step-to (- r1 l1) (- r2 l2) hstep)) + (+ top (wg-step-to (- b1 t1) (- b2 t2) vstep))))))) + +(defun wg-w-edge-operation (w edges op) + "Return a copy of W with its edges mapped against EDGES through OP." + (wg-aput w 'edges (mapcar* op (wg-aget w 'edges) edges))) + +(defun wg-first-win (w) + "Return the first actual window in W." + (if (wg-window-p w) w (wg-first-win (car (wg-wlist w))))) + +(defun wg-last-win (w) + "Return the last actual window in W." + (if (wg-window-p w) w (wg-last-win (wg-last1 (wg-wlist w))))) + +(defun wg-minify-win (w) + "Return a copy of W with the smallest allowable dimensions." + (let* ((edges (wg-edges w)) + (left (car edges)) + (top (cadr edges))) + (wg-put-edges w left top + (+ left wg-actual-min-width) + (+ top wg-actual-min-height)))) + +(defun wg-minify-last-win (w) + "Minify the last actual window in W." + (wg-minify-win (wg-last-win w))) + +(defun wg-wsize (w &optional height) + "Return the width or height of W, calculated from its edge list." + (wg-with-edges w (l1 t1 r1 b1) + (if height (- b1 t1) (- r1 l1)))) + +(defun wg-adjust-wsize (w width-fn height-fn &optional new-left new-top) + "Adjust W's width and height with WIDTH-FN and HEIGHT-FN." + (wg-with-edges w (left top right bottom) + (let ((left (or new-left left)) (top (or new-top top))) + (wg-put-edges w left top + (+ left (funcall width-fn (- right left))) + (+ top (funcall height-fn (- bottom top))))))) + +(defun wg-scale-wsize (w width-scale height-scale) + "Scale W's size by WIDTH-SCALE and HEIGHT-SCALE." + (flet ((wscale (width) (truncate (* width width-scale))) + (hscale (height) (truncate (* height height-scale)))) + (wg-adjust-wsize w #'wscale #'hscale))) + +(defun wg-equal-wtrees (w1 w2) + "Return t when W1 and W2 have equal structure." + (cond ((and (wg-window-p w1) (wg-window-p w2)) + (equal (wg-edges w1) (wg-edges w2))) + ((and (wg-wtree-p w1) (wg-wtree-p w2)) + (and (eq (wg-dir w1) (wg-dir w2)) + (equal (wg-edges w1) (wg-edges w2)) + (every #'wg-equal-wtrees (wg-wlist w1) (wg-wlist w2)))))) + +;; FIXME: Require a minimum size to fix wscaling +(defun wg-normalize-wtree (wtree) + "Clean up and return a new wtree from WTREE. +Recalculate the edge lists of all subwins, and remove subwins +outside of WTREE's bounds. If there's only one element in the +new wlist, return it instead of a new wtree." + (if (wg-window-p wtree) wtree + (wg-abind wtree (dir wlist) + (wg-with-bounds wtree dir (ls1 hs1 lb1 hb1) + (let* ((min-size (wg-min-size dir)) + (max (- hb1 1 min-size)) + (lastw (wg-last1 wlist))) + (flet ((mapwl + (wl) + (wg-dbind (sw . rest) wl + (cons (wg-normalize-wtree + (wg-put-bounds + sw dir ls1 hs1 lb1 + (setq lb1 (if (eq sw lastw) hb1 + (let ((hb2 (+ lb1 (wg-wsize sw dir)))) + (if (>= hb2 max) hb1 hb2)))))) + (when (< lb1 max) (mapwl rest)))))) + (let ((new (mapwl wlist))) + (if (cdr new) (wg-aput wtree 'wlist new) + (car new))))))))) + +(defun wg-scale-wtree (wtree wscale hscale) + "Return a copy of WTREE with its dimensions scaled by WSCALE and HSCALE. +All WTREE's subwins are scaled as well." + (let ((scaled (wg-scale-wsize wtree wscale hscale))) + (if (wg-window-p wtree) scaled + (wg-aput scaled + 'wlist (wg-docar (sw (wg-wlist scaled)) + (wg-scale-wtree sw wscale hscale)))))) + +(defun wg-scale-wconfigs-wtree (wconfig new-width new-height) + "Scale WCONFIG's wtree with NEW-WIDTH and NEW-HEIGHT. +Return a copy WCONFIG's wtree scaled with `wg-scale-wtree' by the +ratio or NEW-WIDTH to WCONFIG's width, and NEW-HEIGHT to +WCONFIG's height." + (wg-normalize-wtree + (wg-scale-wtree + (wg-wtree wconfig) + (/ (float new-width) (wg-aget wconfig 'width)) + (/ (float new-height) (wg-aget wconfig 'height))))) + +(defun w-set-frame-size-and-scale-wtree (wconfig &optional frame) + "Set FRAME's size to WCONFIG's, returning a possibly scaled wtree. +If the frame size was set correctly, return WCONFIG's wtree +unchanged. If it wasn't, return a copy of WCONFIG's wtree scaled +with `wg-scale-wconfigs-wtree' to fit the frame as it exists." + (let ((frame (or frame (selected-frame)))) + (wg-abind wconfig ((wcwidth width) (wcheight height)) + (when window-system (set-frame-size frame wcwidth wcheight)) + (let ((fwidth (frame-parameter frame 'width)) + (fheight (frame-parameter frame 'height))) + (if (and (= wcwidth fwidth) (= wcheight fheight)) + (wg-wtree wconfig) + (wg-scale-wconfigs-wtree wconfig fwidth fheight)))))) + +(defun wg-reverse-wlist (w &optional dir) + "Reverse W's wlist and those of all its sub-wtrees in direction DIR. +If DIR is nil, reverse WTREE horizontally. +If DIR is 'both, reverse WTREE both horizontally and vertically. +Otherwise, reverse WTREE vertically." + (flet ((inner (w) (if (wg-window-p w) w + (wg-abind w ((d1 dir) edges wlist) + (wg-make-wtree + d1 edges + (let ((wl2 (mapcar #'inner wlist))) + (if (or (eq dir 'both) + (and (not dir) (not d1)) + (and dir d1)) + (nreverse wl2) wl2))))))) + (wg-normalize-wtree (inner w)))) + +(defun wg-reverse-wconfig (&optional dir wconfig) + "Reverse WCONFIG's wtree's wlist in direction DIR." + (let ((wc (or wconfig (wg-make-wconfig)))) + (wg-aput wc 'wtree (wg-reverse-wlist (wg-aget wc 'wtree) dir)))) + +(defun wg-wtree-move-window (wtree offset) + "Offset `selected-window' OFFSET places in WTREE." + (flet ((inner + (w) + (if (wg-window-p w) w + (wg-abind w ((d1 dir) edges wlist) + (wg-make-wtree + d1 edges + (wg-aif (wg-get-some (sw wlist) (wg-aget sw 'selwin)) + (wg-cyclic-offset-elt it wlist offset) + (mapcar #'inner wlist))))))) + (wg-normalize-wtree (inner wtree)))) + +(defun wg-wconfig-move-window (offset &optional wconfig) + "Offset `selected-window' OFFSET places in WCONFIG." + (let ((wc (or wconfig (wg-make-wconfig)))) + (wg-aput wc 'wtree (wg-wtree-move-window (wg-aget wc 'wtree) offset)))) + + +;;; wconfig making + +(defun wg-window-point (ewin) + "Return `point' or :max. See `wg-restore-point-max'. +EWIN should be an Emacs window object." + (let ((p (window-point ewin))) + (if (and wg-restore-point-max (= p (point-max))) :max p))) + +(defun wg-ewin->window (ewin) + "Return a new workgroups window from EWIN. +EWIN should be an Emacs window object." + (with-current-buffer (window-buffer ewin) + `((type . window) + (edges . ,(window-edges ewin)) + (bname . ,(buffer-name)) + (fname . ,(buffer-file-name)) + (point . ,(wg-window-point ewin)) + (mark . ,(mark)) + (markx . ,mark-active) + (wstart . ,(window-start ewin)) + (hscroll . ,(window-hscroll ewin)) + (sbars . ,(window-scroll-bars ewin)) + (margins . ,(window-margins ewin)) + (fringes . ,(window-fringes ewin)) + (selwin . ,(eq ewin (selected-window))) + (mbswin . ,(eq ewin minibuffer-scroll-window)) + (dedicated . ,(window-dedicated-p ewin))))) + +(defun wg-make-wtree (dir edges wlist) + "Return a new Workgroups wtree from DIR EDGES and WLIST." + `((type . wtree) + (dir . ,dir) + (edges . ,edges) + (wlist . ,wlist))) + +(defun wg-ewtree->wtree (&optional ewtree) + "Return a new Workgroups wtree from EWTREE or `window-tree'. +If specified, EWTREE should be an Emacs `window-tree'." + (wg-error-on-active-minibuffer) + (flet ((inner (ewt) (if (windowp ewt) (wg-ewin->window ewt) + (wg-dbind (dir edges . wins) ewt + (wg-make-wtree + dir edges (mapcar #'inner wins)))))) + (let ((ewt (car (or ewtree (window-tree))))) + (when (and (windowp ewt) (window-minibuffer-p ewt)) + (error "Workgroups can't operate on minibuffer-only frames.")) + (inner ewt)))) + +(defun wg-make-wconfig () + "Return a new Workgroups window config from `selected-frame'." + (message nil) + `((type . wconfig) + (left . ,(frame-parameter nil 'left)) + (top . ,(frame-parameter nil 'top)) + (width . ,(frame-parameter nil 'width)) + (height . ,(frame-parameter nil 'height)) + (sbars . ,(frame-parameter nil 'vertical-scroll-bars)) + (sbwid . ,(frame-parameter nil 'scroll-bar-width)) + (wtree . ,(wg-ewtree->wtree)))) + +(defun wg-make-blank-wconfig (&optional buffer) + "Return a new blank wconfig. +BUFFER or `wg-default-buffer' is visible in the only window." + (save-window-excursion + (delete-other-windows) + (switch-to-buffer (or buffer wg-default-buffer)) + (wg-make-wconfig))) + + +;;; wconfig restoring + +(defun wg-switch-to-window-buffer (win) + "Switch to a buffer determined from WIN's fname and bname. +Return the buffer if it was found, nil otherwise." + (wg-abind win (fname bname) + (cond ((and fname (file-exists-p fname)) + (find-file fname) + (rename-buffer bname) + (current-buffer)) + ((wg-awhen (get-buffer bname) (switch-to-buffer it))) + (t (switch-to-buffer wg-default-buffer) nil)))) + +(defun wg-restore-window (win) + "Restore WIN in `selected-window'." + (wg-abind win (point mark markx wstart hscroll sbars + fringes margins selwin mbswin dedicated) + (let ((sw (selected-window))) + (when selwin (setq wg-selected-window sw)) + (when (wg-switch-to-window-buffer win) + (when (and wg-restore-mbs-window mbswin) + (setq minibuffer-scroll-window sw)) + (when wg-restore-scroll-bars + (set-window-scroll-bars + sw (nth 0 sbars) (nth 2 sbars) (nth 3 sbars))) + (when wg-restore-fringes + (apply #'set-window-fringes sw fringes)) + (when wg-restore-margins + (set-window-margins sw (car margins) (cdr margins))) + (when wg-restore-dedicated + (set-window-dedicated-p sw dedicated)) + (set-window-hscroll sw hscroll) + (set-mark mark) + (unless markx (deactivate-mark)) + (let ((pm (point-max))) + (set-window-start sw wstart t) + (goto-char (cond ((not wg-restore-point) wstart) + ((eq point :max) pm) + (t point))) + (when (>= wstart pm) (recenter))))))) + +(defun wg-restore-wtree (wtree) + "Restore WTREE in `selected-frame'." + (flet ((inner (w) (if (wg-wtree-p w) + (wg-abind w ((d dir) wlist) + (let ((lastw (wg-last1 wlist))) + (dolist (sw wlist) + (unless (eq sw lastw) + (split-window nil (wg-wsize sw d) (not d))) + (inner sw)))) + (wg-restore-window w) + (other-window 1)))) + (let ((window-min-width wg-window-min-width) + (window-min-height wg-window-min-height)) + (delete-other-windows) + (set-window-dedicated-p nil nil) + (setq wg-selected-window nil) + (inner wtree) + (wg-awhen wg-selected-window (select-window it))))) + +(defun wg-restore-wconfig (wconfig) + "Restore WCONFIG in `selected-frame'." + (wg-error-on-active-minibuffer) + (let ((frame (selected-frame)) wtree) + (wg-abind wconfig (left top sbars sbwid) + (setq wtree (w-set-frame-size-and-scale-wtree wconfig frame)) + (when (and wg-restore-position left top) + (set-frame-position frame left top)) + (when (and wg-morph-on after-init-time) + (wg-morph (wg-ewtree->wtree) wtree wg-morph-no-error)) + (wg-restore-wtree wtree) + (when wg-restore-scroll-bars + (set-frame-parameter frame 'vertical-scroll-bars sbars) + (set-frame-parameter frame 'scroll-bar-width sbwid))))) + +(defun wg-restore-blank-wconfig () + "Restore a new blank wconfig in `selected-frame'." + (wg-restore-wconfig (wg-make-blank-wconfig))) + + +;;; morph + +(defun wg-morph-step-edges (w1 w2) + "Step W1's edges toward W2's by `wg-morph-hsteps' and `wg-morph-vsteps'." + (wg-step-edges (wg-edges w1) (wg-edges w2) + wg-morph-hsteps wg-morph-vsteps)) + +(defun wg-morph-determine-steps (gui-steps &optional term-steps) + (max 1 (if (and (not window-system) term-steps) term-steps gui-steps))) + +(defun wg-morph-match-wlist (wt1 wt2) + "Return a wlist by matching WT1's wlist to WT2's. +When wlist1's and wlist2's lengths are equal, return wlist1. +When wlist1 is shorter than wlist2, add a window at the front of wlist1. +When wlist1 is longer than wlist2, package up wlist1's excess windows +into a wtree, so it's the same length as wlist2." + (let* ((wl1 (wg-wlist wt1)) (l1 (length wl1)) (d1 (wg-dir wt1)) + (wl2 (wg-wlist wt2)) (l2 (length wl2))) + (cond ((= l1 l2) wl1) + ((< l1 l2) + (cons (wg-minify-last-win (wg-rnth (1+ l1) wl2)) + (if (< (wg-wsize (car wl1) d1) + (* 2 (wg-actual-min-size d1))) + wl1 + (cons (wg-w-edge-operation (car wl1) wg-min-edges #'-) + (cdr wl1))))) + ((> l1 l2) + (append (wg-take wl1 (1- l2)) + (list (wg-make-wtree d1 wg-null-edges + (nthcdr (1- l2) wl1)))))))) + +(defun wg-morph-win->win (w1 w2 &optional swap) + "Return a copy of W1 with its edges stepped once toward W2. +When SWAP is non-nil, return a copy of W2 instead." + (wg-aput (if swap w2 w1) 'edges (wg-morph-step-edges w1 w2))) + +(defun wg-morph-win->wtree (win wt) + "Return a new wtree with WIN's edges and WT's last two windows." + (wg-make-wtree + (wg-dir wt) + (wg-morph-step-edges win wt) + (let ((wg-morph-hsteps 2) (wg-morph-vsteps 2)) + (wg-docar (w (wg-leave (wg-wlist wt) 2)) + (wg-morph-win->win (wg-minify-last-win w) w))))) + +(defun wg-morph-wtree->win (wt win &optional noswap) + "Grow the first window of WT and its subtrees one step toward WIN. +This eventually wipes WT's components, leaving only a window. +Swap WT's first actual window for WIN, unless NOSWAP is non-nil." + (if (wg-window-p wt) (wg-morph-win->win wt win (not noswap)) + (wg-make-wtree + (wg-dir wt) + (wg-morph-step-edges wt win) + (wg-dbind (fwin . wins) (wg-wlist wt) + (cons (wg-morph-wtree->win fwin win noswap) + (wg-docar (sw wins) + (if (wg-window-p sw) sw + (wg-morph-wtree->win sw win t)))))))) + +(defun wg-morph-wtree->wtree (wt1 wt2) + "Return a new wtree morphed one step toward WT2 from WT1. +Mutually recursive with `wg-morph-dispatch' to traverse the +structures of WT1 and WT2 looking for discrepancies." + (let ((d1 (wg-dir wt1)) (d2 (wg-dir wt2))) + (wg-make-wtree + d2 (wg-morph-step-edges wt1 wt2) + (if (not (eq (wg-dir wt1) (wg-dir wt2))) + (list (wg-minify-last-win wt2) wt1) + (mapcar* #'wg-morph-dispatch + (wg-morph-match-wlist wt1 wt2) + (wg-wlist wt2)))))) + +(defun wg-morph-dispatch (w1 w2) + "Return a wtree morphed one step toward W2 from W1. +Dispatches on each possible combination of types." + (cond ((and (wg-window-p w1) (wg-window-p w2)) + (wg-morph-win->win w1 w2 t)) + ((and (wg-wtree-p w1) (wg-wtree-p w2)) + (wg-morph-wtree->wtree w1 w2)) + ((and (wg-window-p w1) (wg-wtree-p w2)) + (wg-morph-win->wtree w1 w2)) + ((and (wg-wtree-p w1) (wg-window-p w2)) + (wg-morph-wtree->win w1 w2)))) + +(defun wg-morph (from to &optional noerror) + "Morph from wtree FROM to wtree TO. +Assumes both FROM and TO fit in `selected-frame'." + (let ((wg-morph-hsteps + (wg-morph-determine-steps wg-morph-hsteps wg-morph-terminal-hsteps)) + (wg-morph-vsteps + (wg-morph-determine-steps wg-morph-vsteps wg-morph-terminal-vsteps)) + (wg-restore-scroll-bars nil) + (wg-restore-fringes nil) + (wg-restore-margins nil) + (wg-restore-point nil) + (truncate-partial-width-windows + wg-morph-truncate-partial-width-windows) + (watchdog 0)) + (condition-case err + (wg-until (wg-equal-wtrees from to) + (when (> (incf watchdog) wg-morph-max-steps) + (error "`wg-morph-max-steps' exceeded")) + (setq from (wg-normalize-wtree (wg-morph-dispatch from to))) + (wg-restore-wtree from) + (redisplay) + (unless (zerop wg-morph-sit-for-seconds) + (sit-for wg-morph-sit-for-seconds t))) + (error (if noerror (message "%S" err) (error "%S" err)))))) + + +;;; global error wrappers + +(defun wg-file (&optional noerror) + "Return `wg-file' or error." + (or wg-file + (unless noerror + (error "Workgroups isn't visiting a file")))) + +(defun wg-list (&optional noerror) + "Return `wg-list' or error." + (or wg-list + (unless noerror + (error "No workgroups are defined.")))) + +(defun wg-get-workgroup (key val &optional noerror) + "Return the workgroup whose KEY equals VAL or error." + (or (wg-get-alist key val (wg-list noerror)) + (unless noerror + (error "There is no workgroup with an %S of %S" key val)))) + + +;;; frame-table ops + +(defmacro wg-with-frame-state (frame state &rest body) + "Bind FRAME and STATE and eval BODY. +FRAME is bound to `selected-frame', and STATE is bound to FRAME's +value in `wg-frame-table'." + (declare (indent 2)) + `(let* ((,frame (selected-frame)) + (,state (or (gethash ,frame wg-frame-table) + (puthash ,frame (make-hash-table) + wg-frame-table)))) + ,@body)) + +(defun wg-frame-val (key) + "Return KEY's value in `selected-frame's state in `wg-frame-table'." + (wg-with-frame-state frame state + (gethash key state))) + +(defun wg-set-frame-val (key val) + "Set KEY to VAL in `selected-frame's state in `wg-frame-table'." + (wg-with-frame-state frame state + (puthash key val state))) + +(defun wg-delete-frame-key (key) + "Remove KEY from `selected-frame's state in `wg-frame-table'." + (wg-with-frame-state frame state + (remhash key state))) + +(defun wg-delete-frame (frame) + "Remove FRAME from `wg-frame-table'." + (remhash frame wg-frame-table)) + + +;;; workgroup property ops + +(defun wg-get-workgroup-prop (prop workgroup) + "Return PROP's value in WORKGROUP." + (wg-type-check 'workgroup workgroup) + (wg-aget workgroup prop)) + +(defun wg-set-workgroup-prop (prop val workgroup &optional nodirty) + "Set PROP to VAL in WORKGROUP, setting `wg-dirty' unless NODIRTY." + (wg-type-check 'workgroup workgroup) + (setcdr (assq prop workgroup) val) + (unless nodirty (setq wg-dirty t))) + +(defun wg-uid (workgroup) + "Return WORKGROUP's uid." + (wg-get-workgroup-prop 'uid workgroup)) + +(defun wg-set-uid (workgroup uid) + "Set the uid of WORKGROUP to UID." + (wg-set-workgroup-prop 'uid uid workgroup)) + +(defun wg-uids (&optional noerror) + "Return a list of workgroups uids." + (mapcar 'wg-uid (wg-list noerror))) + +(defun wg-new-uid () + "Return a uid greater than any in `wg-list'." + (let ((uids (wg-uids t)) (new -1)) + (dolist (uid uids (1+ new)) + (setq new (max uid new))))) + +(defun wg-name (workgroup) + "Return the name of WORKGROUP." + (wg-get-workgroup-prop 'name workgroup)) + +(defun wg-set-name (workgroup name) + "Set the name of WORKGROUP to NAME." + (wg-set-workgroup-prop 'name name workgroup)) + +(defun wg-names (&optional noerror) + "Return a list of workgroup names." + (mapcar 'wg-name (wg-list noerror))) + + +;;; current and previous workgroup ops + +(defun wg-get-frame-workgroup (key &optional noerror) + "Return the workgroup under KEY in `wg-frame-table'." + (or (wg-frame-val key) + (unless noerror + (error "There's no %s in the frame" key)))) + +(defun wg-current-workgroup (&optional noerror) + "Return the current workgroup." + (wg-get-frame-workgroup 'current-workgroup noerror)) + +(defun wg-set-current-workgroup (workgroup) + "Set the current workgroup to WORKGROUP." + (wg-set-frame-val 'current-workgroup workgroup)) + +(defun wg-previous-workgroup (&optional noerror) + "Return the previous workgroup." + (wg-get-frame-workgroup 'previous-workgroup noerror)) + +(defun wg-set-previous-workgroup (workgroup) + "Set the previous workgroup to WORKGROUP." + (wg-set-frame-val 'previous-workgroup workgroup)) + + +;;; base and working configs + +(defun wg-set-base-config (workgroup config) + "Set the base config of WORKGROUP to CONFIG." + (wg-set-workgroup-prop 'wconfig config workgroup)) + +(defun wg-base-config (workgroup) + "Return the base config of WORKGROUP." + (wg-get-workgroup-prop 'wconfig workgroup)) + +(defun wg-set-working-config (workgroup config) + "Set the working config of WORKGROUP to CONFIG." + (wg-set-frame-val (wg-uid workgroup) config)) + +(defun wg-update-working-config (workgroup) + "Set WORKGROUP's working config to the current window config." + (wg-set-working-config workgroup (wg-make-wconfig))) + +(defun wg-working-config (workgroup) + "Return the working config of WORKGROUP. +If WORKGROUP is the current workgroup, update it first." + (when (eq workgroup (wg-current-workgroup t)) + (wg-update-working-config workgroup)) + (or (wg-frame-val (wg-uid workgroup)) + (wg-base-config workgroup))) + + +;;; workgroup making and restoring + +(defun wg-make-workgroup (uid name wconfig) + "Return a new workgroup from UID, NAME and WCONFIG." + `((type . workgroup) + (uid . ,uid) + (name . ,name) + (wconfig . ,wconfig))) + +(defun wg-make-default-workgroup (name) + "Return a new workgroup named NAME with wconfig `wg-make-wconfig'." + (wg-make-workgroup nil name (wg-make-wconfig))) + +(defun wg-make-blank-workgroup (name &optional buffer) + "Return a new blank workgroup named NAME, optionally viewing BUFFER." + (wg-make-workgroup nil name (wg-make-blank-wconfig buffer))) + +(defun wg-restore-workgroup (workgroup &optional base) + "Restore WORKGROUP's working config, or base config is BASE is non-nil." + (wg-restore-wconfig (if base (wg-base-config workgroup) + (wg-working-config workgroup)))) + + +;;; workgroups list ops + +(defun wg-delete (workgroup) + "Remove WORKGROUP from `wg-list'. +Also delete all references to it in `wg-frame-table'." + (wg-dohash (frame state wg-frame-table) + (with-selected-frame frame + (wg-delete-frame-key (wg-uid workgroup)) + (when (eq workgroup (wg-current-workgroup t)) + (wg-set-current-workgroup nil)) + (when (eq workgroup (wg-previous-workgroup t)) + (wg-set-previous-workgroup nil)))) + (setq wg-dirty t wg-list (remove workgroup (wg-list)))) + +(defun wg-add (new &optional pos) + "Add WORKGROUP to `wg-list'. +If a workgroup with the same name exists, overwrite it." + (wg-awhen (wg-get-workgroup 'name (wg-name new) t) + (unless pos (setq pos (position it wg-list))) + (wg-delete it)) + (wg-set-uid new (wg-new-uid)) + (setq wg-dirty t wg-list (wg-insert-elt new wg-list pos))) + +(defun wg-check-and-add (workgroup) + "Add WORKGROUP to `wg-list'. +Query to overwrite if a workgroup with the same name exists." + (let ((name (wg-name workgroup))) + (when (wg-get-workgroup 'name name t) + (unless (or wg-no-confirm + (y-or-n-p (format "%S exists. Overwrite? " name))) + (error "Cancelled")))) + (wg-add workgroup)) + +(defun wg-cyclic-offset-workgroup (workgroup n) + "Offset WORKGROUP's position in `wg-list' by N." + (wg-aif (wg-cyclic-offset-elt workgroup (wg-list) n) + (setq wg-list it wg-dirty t) + (error "Workgroup isn't present in `wg-list'."))) + +(defun wg-list-swap (w1 w2) + "Swap the positions of W1 and W2 in `wg-list'." + (when (eq w1 w2) (error "Can't swap a workgroup with itself")) + (wg-aif (wg-util-swap w1 w2 (wg-list)) + (setq wg-list it wg-dirty t) + (error "Both workgroups aren't present in `wg-list'."))) + + +;;; buffer list ops + +(defun wg-wtree-buffer-list (wtree) + "Return a list of unique buffer names visible in WTREE." + (flet ((rec (w) (if (wg-window-p w) (list (wg-aget w 'bname)) + (mapcan #'rec (wg-wlist w))))) + (remove-duplicates (rec wtree) :test #'equal))) + +(defun wg-workgroup-buffer-list (workgroup) + "Call `wg-wconfig-buffer-list' on WORKGROUP's working config." + (wg-wtree-buffer-list (wg-wtree (wg-working-config workgroup)))) + +(defun wg-buffer-list () + "Call `wg-workgroup-buffer-list' on all workgroups in `wg-list'." + (remove-duplicates + (mapcan #'wg-workgroup-buffer-list (wg-list t)) + :test #'equal)) + +(defun wg-find-buffer (bname) + "Return the first workgroup in which a buffer named BNAME is visible." + (wg-get-some (wg (wg-list)) + (member bname (wg-workgroup-buffer-list wg)))) + + +;;; mode-line + +(defun wg-mode-line-string () + "Return the string to be displayed in the mode-line." + (let ((cur (wg-current-workgroup t))) + (cond (cur (wg-fontify " " + (:div wg-mode-line-left-brace) + (:mode (position cur (wg-list t))) + (:div wg-mode-line-divider) + (:mode (wg-name cur)) + (:div wg-mode-line-right-brace))) + (t (wg-fontify " " + (:div wg-mode-line-left-brace) + (:mode "No workgroups") + (:div wg-mode-line-right-brace)))))) + +(defun wg-mode-line-add-display () + "Add Workgroups' mode-line format to `mode-line-format'." + (unless (assq 'wg-mode-line-on mode-line-format) + (let ((format `(wg-mode-line-on (:eval (wg-mode-line-string)))) + (pos (1+ (position 'mode-line-position mode-line-format)))) + (set-default 'mode-line-format + (wg-insert-elt format mode-line-format pos))))) + +(defun wg-mode-line-remove-display () + "Remove Workgroups' mode-line format from `mode-line-format'." + (wg-awhen (assq 'wg-mode-line-on mode-line-format) + (set-default 'mode-line-format (remove it mode-line-format)) + (force-mode-line-update))) + + +;;; minibuffer reading + +(defun wg-completing-read (prompt choices &rest args) + "Call `completing-read' or `ido-completing-read'." + (apply (if (and (boundp 'ido-mode) ido-mode) + #'ido-completing-read + #'completing-read) prompt choices args)) + +(defun wg-read-workgroup (&optional noerror) + "Read a workgroup with `wg-completing-read'." + (wg-get-workgroup + 'name (wg-completing-read "Workgroup: " (wg-names)) + noerror)) + +(defun wg-read-buffer-name () + "Read and return a buffer-name from `wg-buffer-list'." + (wg-completing-read "Workgroup buffers: " (wg-buffer-list))) + +(defun wg-read-new-workgroup-name (&optional prompt) + "Read a non-empty name string from the minibuffer." + (wg-read-object + (or prompt "Name: ") + (lambda (obj) (and (stringp obj) (not (equal obj "")))) + "Please enter a unique, non-empty name")) + +(defun wg-read-workgroup-index () + "Prompt for the index of a workgroup." + (let ((max (1- (length (wg-list))))) + (wg-read-object + (format "%s\n\nEnter [0-%d]: " (wg-disp) max) + (lambda (obj) (and (integerp obj) (wg-within obj 0 max t))) + (format "Please enter an integer [%d-%d]" 0 max) + nil nil t))) + + +;;; messaging + +(defun wg-msg (format-string &rest args) + "Call `message' with FORMAT-STRING and ARGS. +Also save the msg to `wg-last-message'." + (setq wg-last-message (apply #'message format-string args))) + +(defmacro wg-fontified-msg (&rest format) + "`wg-fontify' FORMAT and call `wg-msg' on it." + (declare (indent defun)) + `(wg-msg (wg-fontify ,@format))) + + +;;; command utils + +(defun wg-arg (&optional reverse noerror) + "Return a workgroup one way or another. +For use in interactive forms. If `current-prefix-arg' is nil, +return the current workgroup. Otherwise read a workgroup from +the minibuffer. If REVERSE is non-nil, `current-prefix-arg's +begavior is reversed." + (wg-list noerror) + (if (if reverse (not current-prefix-arg) current-prefix-arg) + (wg-read-workgroup noerror) + (wg-current-workgroup noerror))) + +(defun wg-add-to-kill-ring (config) + "Add CONFIG to `wg-kill-ring'." + (push config wg-kill-ring) + (setq wg-kill-ring (wg-take wg-kill-ring wg-kill-ring-size))) + +(defun wg-disp () + "Return the Workgroups list display string. +The string contains the names of all workgroups in `wg-list', +decorated with faces, dividers and strings identifying the +current and previous workgroups." + (let ((wl (wg-list t)) + (cur (wg-current-workgroup t)) + (prev (wg-previous-workgroup t)) + (div (wg-add-face :div wg-display-divider)) + (cld wg-display-current-workgroup-left-decor) + (crd wg-display-current-workgroup-right-decor) + (pld wg-display-previous-workgroup-left-decor) + (prd wg-display-previous-workgroup-right-decor) + (i -1)) + (wg-fontify + (:brace wg-display-left-brace) + (if (not wl) (wg-fontify (:msg "No workgroups are defined")) + (wg-doconcat (w wl div) + (let ((str (format "%d: %s" (incf i) (wg-name w)))) + (cond ((eq w cur) + (wg-fontify (:cur (concat cld str crd)))) + ((eq w prev) + (wg-fontify (:prev (concat pld str prd)))) + (t (wg-fontify (:other str))))))) + (:brace wg-display-right-brace)))) + +(defun wg-cyclic-nth-from-workgroup (&optional workgroup n) + "Return the workgroup N places from WORKGROUP in `wg-list'." + (wg-when-let ((wg (or workgroup (wg-current-workgroup t)))) + (wg-cyclic-nth-from-elt wg (wg-list) (or n 1)))) + + +;;; commands + +(defun wg-switch-to-workgroup (workgroup &optional base) + "Switch to WORKGROUP. +BASE nil means restore WORKGROUP's working config. +BASE non-nil means restore WORKGROUP's base config." + (interactive (list (wg-read-workgroup) current-prefix-arg)) + (wg-awhen (wg-current-workgroup t) + (when (eq it workgroup) (error "Already on: %s" (wg-name it))) + (wg-update-working-config it)) + (wg-restore-workgroup workgroup base) + (wg-set-previous-workgroup (wg-current-workgroup t)) + (wg-set-current-workgroup workgroup) + (run-hooks 'wg-switch-hook) + (wg-fontified-msg (:cmd "Switched: ") (wg-disp))) + +(defun wg-create-workgroup (name) + "Create and add a workgroup named NAME. +If workgroups already exist, create a blank workgroup. If no +workgroups exist yet, create a workgroup from the current window +configuration." + (interactive (list (wg-read-new-workgroup-name))) + (let ((w (if (wg-current-workgroup t) (wg-make-blank-workgroup name) + (wg-make-default-workgroup name)))) + (wg-check-and-add w) + (wg-switch-to-workgroup w) + (wg-fontified-msg (:cmd "Created: ") (:cur name) " " (wg-disp)))) + +(defun wg-clone-workgroup (workgroup name) + "Create and add a clone of WORKGROUP named NAME." + (interactive (list (wg-arg) (wg-read-new-workgroup-name))) + (let ((new (wg-make-workgroup nil name (wg-base-config workgroup)))) + (wg-check-and-add new) + (wg-set-working-config new (wg-working-config workgroup)) + (wg-switch-to-workgroup new) + (wg-fontified-msg + (:cmd "Cloned: ") (:cur (wg-name workgroup)) + (:msg " to ") (:cur name) " " (wg-disp)))) + +(defun wg-kill-workgroup (workgroup) + "Kill WORKGROUP, saving its working config to the kill ring." + (interactive (list (wg-arg))) + (wg-add-to-kill-ring (wg-working-config workgroup)) + (let ((to (or (wg-previous-workgroup t) + (wg-cyclic-nth-from-workgroup workgroup)))) + (wg-delete workgroup) + (if (eq to workgroup) (wg-restore-blank-wconfig) + (wg-switch-to-workgroup to)) + (wg-fontified-msg + (:cmd "Killed: ") (:cur (wg-name workgroup)) " " (wg-disp)))) + +(defun wg-kill-ring-save-base-config (workgroup) + "Save WORKGROUP's base config to `wg-kill-ring'." + (interactive (list (wg-arg))) + (wg-add-to-kill-ring (wg-base-config workgroup)) + (wg-fontified-msg + (:cmd "Saved: ") (:cur (wg-name workgroup)) + (:cur "'s ") (:msg "base config to the kill ring"))) + +(defun wg-kill-ring-save-working-config (workgroup) + "Save WORKGROUP's working config to `wg-kill-ring'." + (interactive (list (wg-arg))) + (wg-add-to-kill-ring (wg-working-config workgroup)) + (wg-fontified-msg + (:cmd "Saved: ") (:cur (wg-name workgroup)) + (:cur "'s ") (:msg "working config to the kill ring"))) + +(defun wg-yank-config () + "Restore a wconfig from `wg-kill-ring'. +Successive yanks restore wconfigs sequentially from the kill +ring, starting at the front." + (interactive) + (unless wg-kill-ring (error "The kill-ring is empty")) + (let ((pos (if (not (eq real-last-command 'wg-yank-config)) 0 + (mod (1+ (or (get 'wg-yank-config :position) 0)) + (length wg-kill-ring))))) + (put 'wg-yank-config :position pos) + (wg-restore-wconfig (nth pos wg-kill-ring)) + (wg-fontified-msg (:cmd "Yanked: ") (:msg pos) " " (wg-disp)))) + +(defun wg-kill-workgroup-and-buffers (workgroup) + "Kill WORKGROUP and the buffers in its working config." + (interactive (list (wg-arg))) + (let ((bufs (save-window-excursion + (wg-restore-workgroup workgroup) + (mapcar #'window-buffer (window-list))))) + (wg-kill-workgroup workgroup) + (mapc #'kill-buffer bufs) + (wg-fontified-msg + (:cmd "Killed: ") (:cur (wg-name workgroup)) + (:msg " and its buffers ") "\n" (wg-disp)))) + +(defun wg-delete-other-workgroups (workgroup) + "Delete all workgroups but WORKGROUP." + (interactive (list (wg-arg))) + (unless (or wg-no-confirm (y-or-n-p "Really delete all other workgroups? ")) + (error "Cancelled")) + (let ((cur (wg-current-workgroup))) + (mapc #'wg-delete (remove workgroup (wg-list))) + (unless (eq workgroup cur) (wg-switch-to-workgroup workgroup)) + (wg-fontified-msg + (:cmd "Deleted: ") (:msg "All workgroups but ") + (:cur (wg-name workgroup))))) + +(defun wg-update-workgroup (workgroup) + "Set the base config of WORKGROUP to its working config in `selected-frame'." + (interactive (list (wg-arg))) + (wg-set-base-config workgroup (wg-working-config workgroup)) + (wg-fontified-msg + (:cmd "Updated: ") (:cur (wg-name workgroup)))) + +(defun wg-update-all-workgroups () + "Update all workgroups' base configs. +Worgroups are updated with their working configs in the +`selected-frame'." + (interactive) + (mapc #'wg-update-workgroup (wg-list)) + (wg-fontified-msg (:cmd "Updated: ") (:msg "All"))) + +(defun wg-revert-workgroup (workgroup) + "Set the working config of WORKGROUP to its base config in `selected-frame'." + (interactive (list (wg-arg))) + (wg-set-working-config + workgroup (wg-base-config workgroup)) + (when (eq workgroup (wg-current-workgroup)) + (wg-restore-workgroup workgroup t)) + (wg-fontified-msg (:cmd "Reverted: ") (:cur (wg-name workgroup)))) + +(defun wg-revert-all-workgroups () + "Revert all workgroups to their base configs." + (interactive) + (mapc #'wg-revert-workgroup (wg-list)) + (wg-fontified-msg (:cmd "Reverted: ") (:msg "All"))) + +(defun wg-switch-to-index (n) + "Switch to Nth workgroup in `wg-list'." + (interactive (list (or current-prefix-arg (wg-read-workgroup-index)))) + (let ((wl (wg-list))) + (wg-switch-to-workgroup + (or (nth n wl) (error "There are only %d workgroups" (length wl)))))) + +;; Define wg-switch-to-index-[0-9]: +(macrolet + ((defi (n) + `(defun ,(intern (format "wg-switch-to-index-%d" n)) () + ,(format "Switch to the workgroup at index %d in the list." n) + (interactive) (wg-switch-to-index ,n)))) + (defi 0) (defi 1) (defi 2) (defi 3) (defi 4) + (defi 5) (defi 6) (defi 7) (defi 8) (defi 9)) + +(defun wg-switch-left (&optional workgroup n) + "Switch to the workgroup left of WORKGROUP in `wg-list'." + (interactive (list (wg-arg nil t) current-prefix-arg)) + (wg-switch-to-workgroup + (or (wg-cyclic-nth-from-workgroup workgroup (or n -1)) + (car (wg-list))))) + +(defun wg-switch-right (&optional workgroup n) + "Switch to the workgroup right of WORKGROUP in `wg-list'." + (interactive (list (wg-arg nil t) current-prefix-arg)) + (wg-switch-to-workgroup + (or (wg-cyclic-nth-from-workgroup workgroup n) + (car (wg-list))))) + +(defun wg-switch-left-other-frame (&optional n) + "Like `wg-switch-left', but operates on the next frame." + (interactive "p") + (with-selected-frame (wg-cyclic-nth-from-frame (or n 1)) + (wg-switch-left))) + +(defun wg-switch-right-other-frame (&optional n) + "Like `wg-switch-right', but operates on the next frame." + (interactive "p") + (with-selected-frame (wg-cyclic-nth-from-frame (or n -1)) + (wg-switch-right))) + +(defun wg-switch-to-previous-workgroup () + "Switch to the previous workgroup." + (interactive) + (wg-switch-to-workgroup (wg-previous-workgroup))) + +(defun wg-swap-workgroups () + "Swap the previous and current workgroups." + (interactive) + (wg-list-swap (wg-current-workgroup) (wg-previous-workgroup)) + (wg-fontified-msg (:cmd "Swapped ") (wg-disp))) + +(defun wg-offset-left (workgroup &optional n) + "Offset WORKGROUP leftward in `wg-list' cyclically." + (interactive (list (wg-arg) current-prefix-arg)) + (wg-cyclic-offset-workgroup workgroup (or n -1)) + (wg-fontified-msg (:cmd "Offset left: ") (wg-disp))) + +(defun wg-offset-right (workgroup &optional n) + "Offset WORKGROUP rightward in `wg-list' cyclically." + (interactive (list (wg-arg) current-prefix-arg)) + (wg-cyclic-offset-workgroup workgroup (or n 1)) + (wg-fontified-msg (:cmd "Offset right: ") (wg-disp))) + +(defun wg-rename-workgroup (workgroup newname) + "Rename WORKGROUP to NEWNAME." + (interactive (list (wg-arg) (wg-read-new-workgroup-name "New name: "))) + (let ((oldname (wg-name workgroup))) + (wg-set-name workgroup newname) + (wg-fontified-msg + (:cmd "Renamed: ") (:cur oldname) (:msg " to ") + (:cur (wg-name workgroup))))) + +(defun wg-reset (&optional force) + "Reset workgroups. +Deletes saved state in `wg-frame-table' and nulls out `wg-list', +`wg-file' and `wg-kill-ring'." + (interactive "P") + (unless (or force wg-no-confirm (y-or-n-p "Are you sure? ")) + (error "Canceled")) + (clrhash wg-frame-table) + (setq wg-list nil wg-file nil wg-dirty nil) + (wg-fontified-msg (:cmd "Reset: ") (:msg "Workgroups"))) + + +;;; file commands + +(defun wg-save (file) + "Save workgroups to FILE. +Called interactively with a prefix arg, or if `wg-file' +is nil, read a filename. Otherwise use `wg-file'." + (interactive + (list (if (or current-prefix-arg (not (wg-file t))) + (read-file-name "File: ") (wg-file)))) + (wg-write-sexp-to-file + (cons wg-persisted-workgroups-tag (wg-list)) file) + (setq wg-dirty nil wg-file file) + (wg-fontified-msg (:cmd "Wrote: ") (:file file))) + +(defun wg-load (file) + "Load workgroups from FILE. +Called interactively with a prefix arg, and if `wg-file' +is non-nil, use `wg-file'. Otherwise read a filename." + (interactive + (list (if (and current-prefix-arg (wg-file t)) + (wg-file) (read-file-name "File: ")))) + (wg-dbind (tag . workgroups) (wg-read-sexp-from-file file) + (unless (or (eq tag wg-persisted-workgroups-tag) + ;; Added for compatibility with old save files. This tag had to + ;; be changed because it's formatted like a file-local variable, + ;; causing workgroups-mode to toggle on or off when a file of + ;; saved workgroups is visited (even though the symbol + ;; `workgroups' denotes nothing in Workgroups except its + ;; customization group -- yow! + (eq tag '-*-workgroups-*-)) + (error "%S is not a workgroups file." file)) + (wg-reset t) + (setq wg-list workgroups wg-file file)) + (when wg-switch-on-load + (wg-awhen (wg-list t) + (wg-switch-to-workgroup (car it)))) + (wg-fontified-msg (:cmd "Loaded: ") (:file file))) + +(defun wg-find-file (file) + "Create a new workgroup and find file FILE in it." + (interactive "FFile: ") + (wg-create-workgroup (file-name-nondirectory file)) + (find-file file)) + +(defun wg-find-file-read-only (file) + "Create a new workgroup and find FILE read-only in it." + (interactive "FFile: ") + (wg-create-workgroup (file-name-nondirectory file)) + (find-file-read-only file)) + +(defun wg-get-by-buffer (buf) + "Switch to the first workgroup in which BUF is visible." + (interactive (list (wg-read-buffer-name))) + (wg-aif (wg-find-buffer buf) (wg-switch-to-workgroup it) + (error "No workgroup contains %S" buf))) + +(defun wg-dired (dir &optional switches) + "Create a workgroup and open DIR in dired with SWITCHES." + (interactive (list (read-directory-name "Dired: ") current-prefix-arg)) + (wg-create-workgroup dir) + (dired dir switches)) + +(defun wg-update-all-workgroups-and-save () + "Call `wg-update-all-workgroups', the `wg-save'. +Keep in mind that workgroups will be updated with their +working-config in the current frame." + (interactive) + (wg-update-all-workgroups) + (call-interactively 'wg-save)) + + +;;; mode-line commands + +(defun wg-toggle-mode-line () + "Toggle Workgroups' mode-line display." + (interactive) + (setq wg-mode-line-on (not wg-mode-line-on)) + (force-mode-line-update) + (wg-fontified-msg + (:cmd "mode-line: ") (:msg (if wg-mode-line-on "on" "off")))) + + +;;; morph commands + +(defun wg-toggle-morph () + "Toggle `wg-morph', Workgroups' morphing animation." + (interactive) + (setq wg-morph-on (not wg-morph-on)) + (wg-fontified-msg + (:cmd "Morph: ") (:msg (if wg-morph-on "on" "off")))) + + +;;; Window movement commands + +(defun wg-move-window-backward (offset) + "Move `selected-window' backward by OFFSET in its wlist." + (interactive (list (or current-prefix-arg -1))) + (wg-restore-wconfig (wg-wconfig-move-window offset))) + +(defun wg-move-window-forward (offset) + "Move `selected-window' forward by OFFSET in its wlist." + (interactive (list (or current-prefix-arg 1))) + (wg-restore-wconfig (wg-wconfig-move-window offset))) + +(defun wg-reverse-frame-horizontally () + "Reverse the order of all horizontally split wtrees." + (interactive) + (wg-restore-wconfig (wg-reverse-wconfig))) + +(defun wg-reverse-frame-vertically () + "Reverse the order of all vertically split wtrees." + (interactive) + (wg-restore-wconfig (wg-reverse-wconfig t))) + +(defun wg-reverse-frame-horizontally-and-vertically () + "Reverse the order of all wtrees." + (interactive) + (wg-restore-wconfig (wg-reverse-wconfig 'both))) + + +;;; echo commands + +(defun wg-echo-current-workgroup () + "Display the name of the current workgroup in the echo area." + (interactive) + (wg-fontified-msg + (:cmd "Current: ") (:cur (wg-name (wg-current-workgroup))))) + +(defun wg-echo-all-workgroups () + "Display the names of all workgroups in the echo area." + (interactive) + (wg-fontified-msg (:cmd "Workgroups: ") (wg-disp))) + +(defun wg-echo-time () + "Echo the current time. Optionally includes `battery' info." + (interactive) + (wg-msg ;; Pass through format to escape the % in `battery' + "%s" (wg-fontify + (:cmd "Current time: ") + (:msg (format-time-string wg-time-format)) + (when (and wg-display-battery (fboundp 'battery)) + (wg-fontify "\n" (:cmd "Battery: ") (:msg (battery))))))) + +(defun wg-echo-version () + "Echo Workgroups' current version number." + (interactive) + (wg-fontified-msg + (:cmd "Workgroups version: ") (:msg wg-version))) + +(defun wg-echo-last-message () + "Echo the last message Workgroups sent to the echo area. +The string is passed through a format arg to escape %'s." + (interactive) + (message "%s" wg-last-message)) + + +;;; help + +(defvar wg-help + '("\\[wg-switch-to-workgroup]" + "Switch to a workgroup" + "\\[wg-create-workgroup]" + "Create a new workgroup and switch to it" + "\\[wg-clone-workgroup]" + "Create a clone of the current workgroug and switch to it" + "\\[wg-kill-workgroup]" + "Kill a workgroup" + "\\[wg-kill-ring-save-base-config]" + "Save the current workgroup's base config to the kill ring" + "\\[wg-kill-ring-save-working-config]" + "Save the current workgroup's working config to the kill ring" + "\\[wg-yank-config]" + "Yank a config from the kill ring into the current frame" + "\\[wg-kill-workgroup-and-buffers]" + "Kill a workgroup and all buffers visible in it" + "\\[wg-delete-other-workgroups]" + "Delete all but the specified workgroup" + "\\[wg-update-workgroup]" + "Update a workgroup's base config with its working config" + "\\[wg-update-all-workgroups]" + "Update all workgroups' base configs with their working configs" + "\\[wg-revert-workgroup]" + "Revert a workgroup's working config to its base config" + "\\[wg-revert-all-workgroups]" + "Revert all workgroups' working configs to their base configs" + "\\[wg-switch-to-index]" + "Jump to a workgroup by its index in the workgroups list" + "\\[wg-switch-to-index-0]" + "Switch to the workgroup at index 0" + "\\[wg-switch-to-index-1]" + "Switch to the workgroup at index 1" + "\\[wg-switch-to-index-2]" + "Switch to the workgroup at index 2" + "\\[wg-switch-to-index-3]" + "Switch to the workgroup at index 3" + "\\[wg-switch-to-index-4]" + "Switch to the workgroup at index 4" + "\\[wg-switch-to-index-5]" + "Switch to the workgroup at index 5" + "\\[wg-switch-to-index-6]" + "Switch to the workgroup at index 6" + "\\[wg-switch-to-index-7]" + "Switch to the workgroup at index 7" + "\\[wg-switch-to-index-8]" + "Switch to the workgroup at index 8" + "\\[wg-switch-to-index-9]" + "Switch to the workgroup at index 9" + "\\[wg-switch-left]" + "Switch to the workgroup leftward cyclically in the workgroups list" + "\\[wg-switch-right]" + "Switch to the workgroup rightward cyclically in the workgroups list" + "\\[wg-switch-left-other-frame]" + "Like `wg-switch-left', but operates in the next frame" + "\\[wg-switch-right-other-frame]" + "Like `wg-switch-right', but operates in the next frame" + "\\[wg-switch-to-previous-workgroup]" + "Switch to the previously selected workgroup" + "\\[wg-swap-workgroups]" + "Swap the positions of the current and previous workgroups" + "\\[wg-offset-left]" + "Offset a workgroup's position leftward cyclically in the workgroups list" + "\\[wg-offset-right]" + "Offset a workgroup's position rightward cyclically in the workgroups list" + "\\[wg-rename-workgroup]" + "Rename a workgroup" + "\\[wg-reset]" + "Reset Workgroups' entire state." + "\\[wg-save]" + "Save the workgroup list to a file" + "\\[wg-load]" + "Load a workgroups list from a file" + "\\[wg-find-file]" + "Create a new blank workgroup and find a file in it" + "\\[wg-find-file-read-only]" + "Create a new blank workgroup and find a file read-only in it" + "\\[wg-get-by-buffer]" + "Switch to the workgroup and config in which the specified buffer is visible" + "\\[wg-dired]" + "Create a new blank workgroup and open a dired buffer in it" + "\\[wg-move-window-backward]" + "Move `selected-window' backward in its wlist" + "\\[wg-move-window-forward]" + "Move `selected-window' forward in its wlist" + "\\[wg-reverse-frame-horizontally]" + "Reverse the order of all horizontall window lists." + "\\[wg-reverse-frame-vertically]" + "Reverse the order of all vertical window lists." + "\\[wg-reverse-frame-horizontally-and-vertically]" + "Reverse the order of all window lists." + "\\[wg-toggle-mode-line]" + "Toggle Workgroups' mode-line display" + "\\[wg-toggle-morph]" + "Toggle the morph animation on any wconfig change" + "\\[wg-echo-current-workgroup]" + "Display the name of the current workgroup in the echo area" + "\\[wg-echo-all-workgroups]" + "Display the names of all workgroups in the echo area" + "\\[wg-echo-time]" + "Display the current time in the echo area" + "\\[wg-echo-version]" + "Display the current version of Workgroups in the echo area" + "\\[wg-echo-last-message]" + "Display the last message Workgroups sent to the echo area in the echo area." + "\\[wg-help]" + "Show this help message") + "List of commands and their help messages. Used by `wg-help'.") + +(defun wg-help () + "Display Workgroups' help buffer." + (interactive) + (with-output-to-temp-buffer "*workroups help*" + (princ "Workgroups' keybindings:\n\n") + (dolist (elt (wg-partition wg-help 2)) + (wg-dbind (cmd help-string) elt + (princ (format "%15s %s\n" + (substitute-command-keys cmd) + help-string)))))) + + +;;; keymap + +(defvar wg-map + (wg-fill-keymap (make-sparse-keymap) + + ;; workgroup creation + + "C-c" 'wg-create-workgroup + "c" 'wg-create-workgroup + "C" 'wg-clone-workgroup + + + ;; killing and yanking + + "C-k" 'wg-kill-workgroup + "k" 'wg-kill-workgroup + "M-W" 'wg-kill-ring-save-base-config + "M-w" 'wg-kill-ring-save-working-config + "C-y" 'wg-yank-config + "y" 'wg-yank-config + "M-k" 'wg-kill-workgroup-and-buffers + "K" 'wg-delete-other-workgroups + + + ;; updating and reverting + + "C-u" 'wg-update-workgroup + "u" 'wg-update-workgroup + "C-S-u" 'wg-update-all-workgroups + "U" 'wg-update-all-workgroups + "C-r" 'wg-revert-workgroup + "r" 'wg-revert-workgroup + "C-S-r" 'wg-revert-all-workgroups + "R" 'wg-revert-all-workgroups + + + ;; workgroup switching + + "C-'" 'wg-switch-to-workgroup + "'" 'wg-switch-to-workgroup + "C-v" 'wg-switch-to-workgroup + "v" 'wg-switch-to-workgroup + "C-j" 'wg-switch-to-index + "j" 'wg-switch-to-index + "0" 'wg-switch-to-index-0 + "1" 'wg-switch-to-index-1 + "2" 'wg-switch-to-index-2 + "3" 'wg-switch-to-index-3 + "4" 'wg-switch-to-index-4 + "5" 'wg-switch-to-index-5 + "6" 'wg-switch-to-index-6 + "7" 'wg-switch-to-index-7 + "8" 'wg-switch-to-index-8 + "9" 'wg-switch-to-index-9 + "C-p" 'wg-switch-left + "p" 'wg-switch-left + "C-n" 'wg-switch-right + "n" 'wg-switch-right + "M-p" 'wg-switch-left-other-frame + "M-n" 'wg-switch-right-other-frame + "C-a" 'wg-switch-to-previous-workgroup + "a" 'wg-switch-to-previous-workgroup + + + ;; workgroup movement + + "C-x" 'wg-swap-workgroups + "C-," 'wg-offset-left + "C-." 'wg-offset-right + + + ;; file and buffer + + "C-s" 'wg-save + "C-l" 'wg-load + "S" 'wg-update-all-workgroups-and-save + "C-f" 'wg-find-file + "S-C-f" 'wg-find-file-read-only + "C-b" 'wg-get-by-buffer + "b" 'wg-get-by-buffer + "d" 'wg-dired + + + ;; window moving and frame reversal + + "<" 'wg-move-window-backward + ">" 'wg-move-window-forward + "|" 'wg-reverse-frame-horizontally + "-" 'wg-reverse-frame-vertically + "+" 'wg-reverse-frame-horizontally-and-vertically + + + ;; toggling + + "C-i" 'wg-toggle-mode-line + "C-w" 'wg-toggle-morph + + + ;; echoing + + "S-C-e" 'wg-echo-current-workgroup + "E" 'wg-echo-current-workgroup + "C-e" 'wg-echo-all-workgroups + "e" 'wg-echo-all-workgroups + "C-t" 'wg-echo-time + "t" 'wg-echo-time + "V" 'wg-echo-version + "C-m" 'wg-echo-last-message + "m" 'wg-echo-last-message + + + ;; misc + + "A" 'wg-rename-workgroup + "!" 'wg-reset + "?" 'wg-help + + ) + "Workgroups' keymap.") + + +;;; mode definition + +(defun wg-unset-prefix-key () + "Restore the original definition of `wg-prefix-key'." + (wg-awhen (get 'wg-prefix-key :original) + (wg-dbind (key . def) it + (when (eq wg-map (lookup-key global-map key)) + (global-set-key key def)) + (put 'wg-prefix-key :original nil)))) + +(defun wg-set-prefix-key () + "Define `wg-prefix-key' as `wg-map' in `global-map'." + (wg-unset-prefix-key) + (let ((key wg-prefix-key)) + (put 'wg-prefix-key :original (cons key (lookup-key global-map key))) + (global-set-key key wg-map))) + +(defun wg-query-for-save () + "Query for save when `wg-dirty' is non-nil." + (or (not wg-dirty) + (not (y-or-n-p "Save modified workgroups? ")) + (call-interactively 'wg-save) + t)) + +(defun wg-emacs-exit-query () + "Conditionally call `wg-query-for-save'. +Call `wg-query-for-save' when `wg-query-for-save-on-emacs-exit' +is non-nil." + (or (not wg-query-for-save-on-emacs-exit) + (wg-query-for-save))) + +(defun wg-workgroups-mode-exit-query () + "Conditionally call `wg-query-for-save'. +Call `wg-query-for-save' when +`wg-query-for-save-on-workgroups-mode-exit' is non-nil." + (or (not wg-query-for-save-on-workgroups-mode-exit) + (wg-query-for-save))) + +(define-minor-mode workgroups-mode + "This turns `workgroups-mode' on and off. +If ARG is null, toggle `workgroups-mode'. +If ARG is an integer greater than zero, turn on `workgroups-mode'. +If ARG is an integer less one, turn off `workgroups-mode'. +If ARG is anything else, turn on `workgroups-mode'." + :lighter " wg" + :init-value nil + :global t + :group 'workgroups + (cond (workgroups-mode + (add-hook 'kill-emacs-query-functions 'wg-emacs-exit-query) + (add-hook 'delete-frame-functions 'wg-delete-frame) + (wg-set-prefix-key) + (wg-mode-line-add-display)) + (t + (wg-workgroups-mode-exit-query) + (remove-hook 'kill-emacs-query-functions 'wg-emacs-exit-query) + (remove-hook 'delete-frame-functions 'wg-delete-frame) + (wg-unset-prefix-key) + (wg-mode-line-remove-display)))) + + +;;; provide + +(provide 'workgroups) + + +;;; workgroups.el ends here diff --git a/conf/emacs.d/workgroups.elc b/conf/emacs.d/workgroups.elc new file mode 100644 index 0000000000000000000000000000000000000000..886491f55262debb032024087b94eb4cecb24727 GIT binary patch literal 89188 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 z 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 Xr21m*Y;Wpn