;;; persp-mode.el --- windows/buffers sets shared among frames + save/load. -*- lexical-binding: t; -*- ;; Copyright (C) 2012 Constantin Kulikov ;; Author: Constantin Kulikov (Bad_ptr) ;; Version: 3.0.3 ;; Package-Version: 20220206.1742 ;; Package-Commit: 7a594a3d8f1c4ba9234dcd831a589e87f3f4ae86 ;; Package-Requires: ((emacs "24.3")) ;; Keywords: perspectives, session, workspace, persistence, windows, buffers, convenience ;; URL: https://github.com/Bad-ptr/persp-mode.el ;;; License: ;; This file is not part of GNU Emacs. ;; 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. ;;; Commentary: ;; Based on the perspective.el by Natalie Weizenbaum ;; (http://github.com/nex3/perspective-el) but the perspectives are shared ;; among the frames and could be saved/restored from/to a file. ;; ;; Homepage: https://github.com/Bad-ptr/persp-mode.el ;; Installation: ;; From the MELPA: M-x package-install RET persp-mode RET ;; From a file: M-x package-install-file RET 'path to this file' RET ;; Or put this file into your load-path. ;; Configuration: ;; When installed through the package-install: ;; (with-eval-after-load "persp-mode-autoloads" ;; (setq wg-morph-on nil) ;; ;; switch off the animation of restoring window configuration ;; (setq persp-autokill-buffer-on-remove 'kill-weak) ;; (add-hook 'after-init-hook #'(lambda () (persp-mode 1)))) ;; When installed without generating an autoloads file: ;; (with-eval-after-load "persp-mode" ;; ;; .. all settings you want here ;; (add-hook 'after-init-hook #'(lambda () (persp-mode 1)))) ;; (require 'persp-mode) ;; Dependencies: ;; The ability to save/restore window configurations from/to a file ;; depends on the workgroups.el(https://github.com/tlh/workgroups.el) ;; for the emacs versions < 24.4 ;; Customization: ;; M-x: customize-group RET persp-mode RET ;; You can read more in README.md ;;; Code: ;; Prerequirements: (require 'cl-lib) (require 'easymenu) (declare-function golden-ratio-mode "ext:golden-ratio") (declare-function tabbar-buffer-list "ext:tabbar-mode") (declare-function tramp-dissect-file-name "tramp") (declare-function tramp-file-name-hop "tramp") (declare-function tramp-file-name-host "tramp") (declare-function tramp-file-name-localname "tramp") (declare-function tramp-file-name-method "tramp") (declare-function tramp-file-name-user "tramp") (declare-function tramp-tramp-file-p "tramp") (defvar ido-cur-item) (defvar ido-exit) (defvar ido-temp-list) (defvar ido-text) (defvar ido-text-init) (defvar tabbar-buffer-list-function) (defvar persp-mode nil) (defconst persp-not-persp :nil "Something that is not a perspective.") (unless (fboundp 'condition-case-unless-debug) (defalias 'condition-case-unless-debug 'condition-case-no-debug)) (unless (fboundp 'read-multiple-choice) (defun read-multiple-choice (prompt choices) (let ((choice-chars (mapcar #'car choices))) (when choice-chars (assq (read-char-choice (format "%s(%s): " (substring prompt 0 (string-match ": $" prompt)) (mapconcat #'(lambda (ch) (format "[%c] - %s" (car ch) (cadr ch))) choices "; ")) choice-chars) choices))))) (unless (fboundp 'alist-get) (defun alist-get (key alist &optional default remove) (ignore remove) ;;Silence byte-compiler. (let ((x (assq key alist))) (if x (cdr x) default)))) ;; Customization variables: (unless (memq 'custom-group (symbol-plist 'session)) (defgroup session nil "Emacs' state(opened files, buffers, windows, etc.)" :group 'environment)) (defgroup persp-mode nil "Customization of the `persp-mode'." :prefix "persp-" :group 'session :link '(url-link :tag "Github page" "https://github.com/Bad-ptr/persp-mode.el")) (defcustom persp-nil-name "none" "Name for the nil perspective." :group 'persp-mode :type 'string :set #'(lambda (sym val) (when val (when persp-mode (cl-destructuring-bind (frames . windows) (persp-frames-and-windows-with-persp (persp-get-by-name persp-nil-name *persp-hash* nil)) (dolist (win windows) (when (equal persp-nil-name (get-window-persp* win)) (set-window-persp* win val)))) (run-hook-with-args 'persp-renamed-functions nil persp-nil-name val)) (custom-set-default sym val)))) (defface persp-face-lighter-buffer-not-in-persp '((default . (:background "#F00" :foreground "#00F" :weight bold))) "Face for the lighter when the current buffer is not in a perspective." :group 'persp-mode) (defface persp-face-lighter-nil-persp '((t :inherit bold-italic)) "Face for the lighter when the current perspective is nil." :group 'persp-mode) (defface persp-face-lighter-default '((t :inherit italic)) "Default face for the lighter.") (defcustom persp-lighter '(:eval (format (propertize " #%.5s" 'face (let ((persp (get-current-persp))) (if persp (if (persp-contain-buffer-p (current-buffer) persp) 'persp-face-lighter-default 'persp-face-lighter-buffer-not-in-persp) 'persp-face-lighter-nil-persp))) (safe-persp-name (get-current-persp)))) "Defines how the persp-mode show itself in the modeline." :group 'persp-mode :type 'sexp) (defcustom persp-save-dir (expand-file-name "persp-confs/" user-emacs-directory) "The directory to/from where perspectives saved/loaded by default. Autosave files are saved and loaded to/from this directory." :group 'persp-mode :type 'directory) (defcustom persp-auto-save-fname "persp-auto-save" "Name of the file for auto save/load perspectives on the persp-mode deactivation or the emacs shutdown." :group 'persp-mode :type 'string) (defcustom persp-auto-save-persps-to-their-file t "If t -- then a perspective will be autosaved to a file specified in the `persp-file' perspective parameter." :group 'persp-mode :type 'boolean) (defcustom persp-auto-save-persps-to-their-file-before-kill nil "Whether or not perspectives will be saved before killed." :group 'persp-mode :type '(choice (const :tag "Save perspectives which have `persp-file' parameter" :value persp-file) (const :tag "Save all perspectives" :value t) (const :tag "Don't save just kill" :value nil))) (defcustom persp-auto-save-opt 2 "This variable controls the autosave functionality of the persp-mode: 0 -- do not auto save; 1 -- save on the emacs shutdown and only if the persp-mode active; 2 -- save on the persp-mode deactivation or the emacs shutdown." :group 'persp-mode :type '(choice (const :tag "Do not save" :value 0) (const :tag "Save on exit" :value 1) (const :tag "Save on exit and persp-mode deactivation" :value 2))) (defcustom persp-auto-save-num-of-backups 3 "How many autosave file backups to keep." :group 'persp-mode :type 'integer) (defcustom persp-auto-resume-time 3.0 "Delay time in seconds before loading from the autosave file. If <= 0 -- do not autoresume." :group 'persp-mode :type 'float) (defcustom persp-set-last-persp-for-new-frames t "If nil new frames will be created with the 'nil' perspective, otherwise with a last activated perspective." :group 'persp-mode :type 'boolean) (defcustom persp-reset-windows-on-nil-window-conf t "t -- When a perspective without a window configuration is activated then delete all windows and show the *scratch* buffer; function -- run that function; nil -- do nothing." :group 'persp-mode :type '(choice (const :tag "Delete all windows" :value t) (const :tag "Do nothing" :value nil) (function :tag "Run function" :value (lambda () nil)))) (define-widget 'persp-buffer-list-restriction-choices 'lazy "Variants of how the buffer-list can be restricted." :offset 4 :tag "\nControl the persp-buffer-list-restricted behaviour" :type '(choice (const :tag "List all buffers" :value -1) (const :tag "List current perspective buffers" :value 0) (const :tag "List buffers that aren't in the perspective" :value 1) (const :tag "List buffers which unique to the perspective" :value 2) (const :tag "List unique buffers, but show all for the nil perspective" :value 2.5) (const :tag "List free buffers" :value 3) (const :tag "List free buffers, but show all for the nil perspective" :value 3.5))) (defcustom *persp-restrict-buffers-to* 0 "Controls the behaviour of the `persp-buffer-list-restricted' function." :group 'persp-mode :type '(choice persp-buffer-list-restriction-choices (function :tag "\nRun function with frame as an argument" :value (lambda (f) (buffer-list f))))) (defcustom persp-restrict-buffers-to-if-foreign-buffer nil "Override the *persp-restrict-buffers-to* if the current buffer is not in the current perspective. If nil -- do not override." :group 'persp-mode :type '(choice (const :tag "Do not override" :value nil) persp-buffer-list-restriction-choices (function :tag "\nRun function with frame as an argument" :value (lambda (f) (buffer-list f))))) (defcustom persp-set-frame-buffer-predicate 'restricted-buffer-list "t -- set the frame's buffer-predicate parameter to a function returning `t' for buffers in current persp; nil -- do not set the buffer-predicate; restricted-buffer-list -- return t for buffers contained in the list returned from the persp-buffer-list-restricted called without arguments; number -- the same meaning as for the `*persp-restrict-buffers-to*'; function -- use that function as buffer-predicate." :group 'persp-mode :type '(choice (const :tag "\nConstrain to current perspective's buffers." :value t) (const :tag "\nDo not set frames' buffer-predicate parameter." :value nil) (const :tag "\nConstrain with persp-buffer-list-restricted." :value restricted-buffer-list) persp-buffer-list-restriction-choices (function :tag "\nConstrain with a function which take buffer as an argument." :value (lambda (b) b))) :set #'(lambda (sym val) (custom-set-default sym val) (if val (if persp-mode (persp-update-frames-buffer-predicate) (if (and (not (daemonp)) (null (cdr (frame-list)))) (let (th) (setq th #'(lambda () (run-at-time 10 nil #'(lambda () (remove-hook 'window-setup-hook th) (persp-update-frames-buffer-predicate))))) (add-hook 'window-setup-hook th)) (add-hook 'persp-mode-hook #'persp-update-frames-buffer-predicate))) (persp-update-frames-buffer-predicate t)))) ;; TODO: remove this var (defcustom persp-hook-up-emacs-buffer-completion nil "If t -- try to restrict read-buffer function of the current completion system." :group 'persp-mode :type 'boolean) (make-obsolete-variable 'persp-hook-up-emacs-buffer-completion "`persp-set-read-buffer-function', `persp-set-ido-hooks', `persp-interactive-completion-function'" "persp-mode 2.6") (defsubst persp-set-read-buffer-function (&optional opt) (if opt (when (not (eq read-buffer-function #'persp-read-buffer)) (setq persp-saved-read-buffer-function read-buffer-function) (setq read-buffer-function #'persp-read-buffer)) (when (eq read-buffer-function #'persp-read-buffer) (setq read-buffer-function persp-saved-read-buffer-function)))) (defcustom persp-set-read-buffer-function nil "If t -- set the read-buffer-function to persp-read-buffer." :group 'persp-mode :type 'boolean :set #'(lambda (sym val) (custom-set-default sym val) (when persp-mode (persp-set-read-buffer-function val)))) (defsubst persp-set-ido-hooks (&optional opt) (if opt (progn (add-hook 'ido-make-buffer-list-hook #'persp-restrict-ido-buffers) (add-hook 'ido-setup-hook #'persp-ido-setup)) (remove-hook 'ido-make-buffer-list-hook #'persp-restrict-ido-buffers) (remove-hook 'ido-setup-hook #'persp-ido-setup))) (defcustom persp-set-ido-hooks nil "If t -- set the ido hooks for buffer list restriction." :group 'persp-mode :type 'boolean :set #'(lambda (sym val) (custom-set-default sym val) (when persp-mode (persp-set-ido-hooks val)))) ;; TODO: remove this var, just call the completing-read (defvar persp-interactive-completion-function #'completing-read "The function which is used by the persp-mode to interactivly read user input with completion.") (make-obsolete-variable 'persp-interactive-completion-function "`completing-read-function'" "persp-mode 2.7") (defun persp-update-completion-system (&optional system remove) (interactive "i") (when (and (not system) (not remove)) (setq system (intern (funcall persp-interactive-completion-function "Set the completion system for persp-mode: " '("ido" "completing-read") nil t)))) (if remove (progn (when (boundp 'persp-interactive-completion-system) (when persp-hook-up-emacs-buffer-completion (cl-case persp-interactive-completion-system (ido (persp-set-ido-hooks)) (t nil)))) (setq persp-interactive-completion-function #'completing-read) (custom-set-default 'persp-interactive-completion-system 'completing-read)) (persp-update-completion-system nil t) (when system (custom-set-default 'persp-interactive-completion-system system) (when persp-hook-up-emacs-buffer-completion (cl-case persp-interactive-completion-system (ido (persp-set-ido-hooks t) (setq persp-interactive-completion-function #'ido-completing-read)) (t nil)) (persp-set-toggle-read-buffer-filter-keys persp-toggle-read-buffer-filter-keys))))) ;; TODO: remove this var (defcustom persp-interactive-completion-system 'completing-read "What completion system to use." :group 'persp-mode :type '(choice (const :tag "ido" :value ido) (const :tag "completing-read" :value completing-read)) :set #'(lambda (sym val) (if persp-mode (persp-update-completion-system val) (custom-set-default sym val)))) (make-obsolete-variable 'persp-interactive-completion-system "`persp-set-read-buffer-function', `persp-set-ido-hooks', `persp-interactive-completion-function'" "persp-mode 2.6") (define-widget 'persp-init-frame-behaviour-choices 'lazy "Choices of the init-frame behavoiurs for the persp-mode." :offset 4 :tag "\nControl how frames initialized by persp-mode" :type '(choice (const :tag "Restore window-configuration" :value t) (const :tag "Do not restore window-configuration" :value nil) (const :tag "Set persp-ignore-wconf flag for frame" :value persp-ignore-wconf) (const :tag "Set persp-ignore-wconf-once flag for frame" :value persp-ignore-wconf-once) (const :tag "Create a new random auto-perspective for the new frame" :value auto-temp) (const :tag "Create a new perspective for the new frame and prompt for it's name" :value prompt) (string :tag "Use/create the perspective with a name" :value "pfnf") (function :tag "Run this function" :value (lambda (frame &optional new-frame-p) nil)))) (defcustom persp-init-frame-behaviour t "Control the behaviour of how frames initialized." :group 'persp-mode :type 'persp-init-frame-behaviour-choices) (defcustom persp-init-new-frame-behaviour-override -1 "Override the `persp-init-frame-behaviour` for new frames." :group 'persp-mode :type '(choice (const :tag "Do not override" : value -1) persp-init-frame-behaviour-choices)) (defcustom persp-interactive-init-frame-behaviour-override -1 "Override the `persp-init-frame-behaviour' when the `make-frame' was called interactively." :group 'persp-mode :type '(choice (const :tag "Do not override" :value -1) persp-init-frame-behaviour-choices)) (defcustom persp-emacsclient-init-frame-behaviour-override -1 "Override the `persp-init-frame-behaviour' variable for frames created using the emacsclient -[c|t]." :group 'persp-mode :type '(choice (const :tag "Do not override" :value -1) persp-init-frame-behaviour-choices)) (defcustom persp-server-switch-behaviour 'only-file-windows-for-client-frame "Controls the behaviour of the server-switch-hook." :group 'persp-mode :type '(choice (const :tag "Do nothing" :value nil) (const :tag "Leave only windows displaing files for edit (files that was supplied as parameters to emacsclient)" :value only-file-windows) (const :tag "For the new frame(created by emacsclient -c ...) leave only windows displaing files for edit" :value only-file-windows-for-client-frame) (function :tag "Run this function" :value (lambda (frame buflist) nil))) :set #'(lambda (sym val) (custom-set-default sym val) (if persp-mode (persp-update-frame-server-switch-hook) (add-hook 'persp-mode-hook #'persp-update-frame-server-switch-hook)))) ;; TODO: remove this var (defcustom persp-ignore-wconf-of-frames-created-to-edit-file t "If t -- set the persp-ignore-wconf frame parameter to t for frames that were created by emacsclient with file arguments. Also delete windows not showing that files (this is because server-switch-hook runs after after-make-frames); If function -- run that function." :group 'persp-mode :type '(choice (const :tag "Ignore window configuration" :value t) (const :tag "Do as usual" :value nil) (function :tag "Run function" :value (lambda () nil)))) (make-obsolete-variable 'persp-ignore-wconf-of-frames-created-to-edit-file "`persp-emacsclient-frame-to-edit-file-behavoiur'" "persp-mode 2.0") (defcustom persp-add-buffer-on-find-file t "If t -- add a buffer with opened file to current perspective." :group 'persp-mode :type '(choice (const :tag "Always add" :value t) (const :tag "Newer add" :value nil) (const :tag "\nAdd if not matching any predicate from `persp-auto-persp-alist'" :value if-not-autopersp) (const :tag "\nAlways add but do not switch if the buffer matches any \ predicate from `persp-auto-persp-alist'" :value add-but-not-switch-if-autopersp))) (defcustom persp-add-buffer-on-after-change-major-mode nil "t -- add the current buffer to the current perspective when the `after-change-major-mode-hook' fires; nil -- do not add; 'free -- add only _free_ buffers; function -- run that function." :group 'persp-mode :type '(choice (const :tag "Always add" :value t) (const :tag "Don't add" :value nil) (const :tag "\nAdd if the buffer is not already in any other persp" :value free) (function :tag "Run this function" :value (lambda () nil))) :set #'(lambda (sym val) (custom-set-default sym val) (when persp-mode (if val (add-hook 'after-change-major-mode-hook #'persp-after-change-major-mode-h t) (remove-hook 'after-change-major-mode-hook #'persp-after-change-major-mode-h))))) (defcustom persp-switch-to-added-buffer t "If t then after you add a buffer to the current perspective the currently selected window will be switched to that buffer." :group 'persp-mode :type 'boolean) (define-obsolete-variable-alias 'persp-when-kill-switch-to-buffer-in-perspective 'persp-when-remove-buffer-switch-to-other-buffer "persp-mode 2.9.7") (defcustom persp-when-remove-buffer-switch-to-other-buffer t "If t -- then after a buffer is removed all windows of the current perspective which showing that buffer will be switched to some previous buffer in the current perspective." :group 'persp-mode :type 'boolean) (defcustom persp-remove-buffers-from-nil-persp-behaviour 'ask-to-rem-from-all "What to do when removing a buffer from the nil perspective." :group 'persp-mode :type '(choice (const :tag "Ask to remove from all perspectives" ask-to-rem-from-all) (const :tag "Ask only if buffer belongs to a non-weak perspective" ask-if-in-non-weak-persp) (const :tag "Don't ask" nil) (function :tag "Run this function" (lambda (b-o-ns) b-o-ns)))) (define-widget 'persp-kill-foreign-buffer-behaviour-choices 'lazy "What to do when manually killing a buffer that is not in the current perspective." :offset 4 :tag "\nControl the persp-kill-buffer-query-function behaviour." :type '(choice (const :tag "Ask what to do" :value ask) (const :tag "\nDon't ask if a buffer belongs only to weak perspectives" :value dont-ask-weak) (const :tag "Just kill" :value kill) (const :tag "\nDo not suggest foreign buffer to the user(kill buffer)" :value nil) (function :tag "Run function" :value (lambda () t)))) (define-obsolete-variable-alias 'persp-kill-foreign-buffer-action 'persp-kill-foreign-buffer-behaviour "persp-mode 2.9.6") (defcustom persp-kill-foreign-buffer-behaviour 'dont-ask-weak "What to do when manually killing a buffer that is not in the current perspective." :group 'persp-mode :type 'persp-kill-foreign-buffer-behaviour-choices) (make-obsolete-variable 'persp-kill-foreign-indirect-buffer-behaviour-override "Don't use this" "persp-mode 2.9.7") (defcustom persp-autokill-buffer-on-remove nil "Kill the buffer if it removed from every(or non weak) perspective." :group 'persp-mode :type '(choice (const :tag "Just kill" :value kill) ;; or t (const :tag "Kill if buffer belongs only to weak perspectives" :value kill-weak) (const :tag "Do not kill" :value nil))) (defcustom persp-autokill-persp-when-removed-last-buffer 'hide-auto "Kill the perspective if no buffers left in it." :group 'persp-mode :type '(choice (const :tag "Just kill" :value kill) ;; or t (const :tag "Kill auto perspectives" :value kill-auto) (const :tag "Hide" :value hide) (const :tag "Hide auto perspectives" :value hide-auto) (const :tag "Do not kill" :value nil) (function :tag "\nRun this function with persp as an argument" :value (lambda (p) p)))) (defcustom persp-common-buffer-filter-functions (list #'(lambda (b) (or (string-prefix-p " " (buffer-name b)) (eq (buffer-local-value 'major-mode b) 'helm-major-mode)))) "The list of functions wich takes a buffer as an argument. If one of these functions returns a non nil value the buffer considered as 'filtered out'." :group 'persp-mode :type 'hook) (defcustom persp-buffer-list-restricted-filter-functions nil "Additional filters for use inside the `persp-buffer-list-restricted'." :group 'persp-mode :type 'hook) (defcustom persp-add-buffer-on-after-change-major-mode-filter-functions nil "Additional filters to know which buffers we dont want to add to the current perspective after the `after-change-major-mode-hook' is fired." :group 'persp-mode :type 'hook) (defcustom persp-filter-save-buffers-functions (list #'(lambda (b) (string-prefix-p "*" (buffer-name b)))) "Additional filters to not save unneeded buffers." :group 'persp-mode :type 'hook) (defcustom persp-save-buffer-functions (list #'(lambda (b) (when (persp-buffer-filtered-out-p b persp-filter-save-buffers-functions) 'skip)) #'persp-tramp-save-buffer #'(lambda (b) (when (eq 'dired-mode (buffer-local-value 'major-mode b)) `(def-buffer ,(buffer-name b) ,(buffer-local-value 'default-directory b) ,(buffer-local-value 'major-mode b)))) #'(lambda (b) `(def-buffer ,(buffer-name b) ,(buffer-file-name b) ,(buffer-local-value 'major-mode b)))) "Convert a buffer to a structure that could be saved to a file. If a function return nil -- follow to the next function in the list. If a function return 'skip -- don't save a buffer." :group 'persp-mode :type 'hook) (defcustom persp-load-buffer-functions (list #'persp-buffer-from-savelist) "Restore a buffer from a saved structure. If a function return nil -- follow to the next function in the list. If a function return 'skip -- don't restore a buffer." :group 'persp-mode :type 'hook) (defcustom persp-mode-hook nil "The hook that's run after the `persp-mode' has been activated." :group 'persp-mode :type 'hook) (defcustom persp-mode-deactivated-hook nil "Runs when the persp-mode is deactivated." :group 'persp-mode :type 'hook) (defcustom persp-created-functions nil "Functions to run after a perspective was created. These functions must accept two arguments -- the created perspective and the hash in which this perspective will be placed, you can check if that hash is the same as `*persp-hash*' or another(when you load a subset of perspectives(with `persp-load-from-file-by-names') they will be added to a temporary hash)." :group 'persp-mode :type 'hook) (defcustom persp-renamed-functions nil "Functions to run if a perspective was renamed. Each must take three arguments: 1) perspective; 2) old name; 3) new name. These functions only run when renaming a perspective from `*persp-hash*'." :group 'persp-mode :type 'hook) (defcustom persp-before-kill-functions nil "Functions that runs just before a perspective will be destroyed. It's single argument is the perspective that will be killed." :group 'persp-mode :type 'hook) (defcustom persp-before-switch-functions nil "Functions that runs before actually switching to a perspective. These functions must take two arguments -- a name of a perspective to switch (it could be a name of an nonexistent perspective or it could be the same as current) and a frame or a window for which the switching will take place." :group 'persp-mode :type 'hook) (defcustom persp-activated-functions nil "Functions that runs after a perspective has been activated. These functions must take one argument -- a symbol, if it is eq 'frame -- then the perspective is activated for `selected-frame', if it is eq 'window -- then the perspective is activated for `selected-window'. The activated perspective is available with `get-current-persp'." :group 'persp-mode :type 'hook) (defcustom persp-before-deactivate-functions nil "Functions that runs before the current perspective has been deactivated for selected frame or window. These functions must take one argument -- a symbol, if it's 'frame -- perspective will be deactivated for the `selected-frame', if it's 'window -- perspective will be deactivated for the `selected-window'. The perspective is available with `get-current-persp'." :group 'persp-mode :type 'hook) (defcustom persp-before-save-state-to-file-functions nil "Functions to run before saving perspectives to a file. Each function in this list will be called with 3 arguments: 1) a file name to which perspectives will be saved; 2) a hash with perspectives; 3) a bool argument indicating if the persp-file parameter of perspectives must be set." :group 'persp-mode :type 'hook) (defcustom persp-after-load-state-functions (list #'(lambda (file phash persp-names) (when (eq phash *persp-hash*) (persp-update-frames-window-confs persp-names)))) "Functions that runs after perspectives state was loaded. These functions must take 3 arguments: 1) a file from which the state was loaded; 2) a hash in which loaded perspectives were placed; 3) list of names of perspectives that was loaded." :group 'persp-mode :type 'hook) (defcustom persp-use-workgroups (and (version< emacs-version "24.4") (locate-library "workgroups")) "If t -- use the workgroups.el package for saving/restoring windows configurations." :group 'persp-mode :type 'boolean :set #'(lambda (sym val) (custom-set-default sym val) ;; require workgroups if we are going to use it (when persp-use-workgroups ;;(require 'workgroups) (unless (fboundp 'wg-make-wconfig) (autoload 'wg-make-wconfig "workgroups" "Return a new Workgroups window config from `selected-frame'." )) (unless (fboundp 'wg-restore-wconfig) (autoload 'wg-restore-wconfig "workgroups" "Restore WCONFIG in `selected-frame'." ))))) (defcustom persp-restore-window-conf-method t "Defines how to restore window configurations for the new frames: t -- the standard action. function -- run that function." :group 'persp-mode :type '(choice (const :tag "Standard action" :value t) (const :tag "Do nothing" :value nil) (function :tag "Run function" :value (lambda (frame persp new-frame-p) nil)))) (defcustom persp-restore-window-conf-filter-functions (list #'(lambda (f p new-f-p) (or (null f) (frame-parameter f 'persp-ignore-wconf) (let ((old-piw (frame-parameter f 'persp-ignore-wconf-once))) (when old-piw (set-frame-parameter f 'persp-ignore-wconf-once nil) old-piw))))) "The list of functions which takes a frame, persp and new-frame-p as arguments. If one of these functions return a non nil value then the window configuration of the persp will not be restored for the frame" :group 'persp-mode :type 'hook) (defcustom persp-window-state-get-function (if persp-use-workgroups #'(lambda (&optional frame rwin) (when (or frame (setq frame (selected-frame))) (with-selected-frame frame (wg-make-wconfig)))) (if (version< emacs-version "24.4") #'(lambda (&optional frame rwin) (when (or rwin (setq rwin (frame-root-window (or frame (selected-frame))))) (when (fboundp 'window-state-get) (window-state-get rwin)))) #'(lambda (&optional frame rwin) (when (or rwin (setq rwin (frame-root-window (or frame (selected-frame))))) (window-state-get rwin t))))) "Function for getting a window configuration of a frame, accept two optional arguments: first -- a frame(default is the selected one) second -- a root window(default is the root window of the selected frame)." :group 'persp-mode :type 'function) (defcustom persp-window-state-put-function (if persp-use-workgroups #'(lambda (pwc &optional frame rwin) (when (or frame (setq frame (selected-frame))) (with-selected-frame frame (cl-letf (((symbol-function 'wg-switch-to-window-buffer) #'(lambda (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 ((wg-awhen (get-buffer bname) (persp-switch-to-buffer it))) (t (persp-switch-to-buffer wg-default-buffer) nil)))))) (wg-restore-wconfig pwc))))) #'(lambda (pwc &optional frame rwin) (when (or rwin (setq rwin (frame-root-window (or frame (selected-frame))))) (when (fboundp 'window-state-put) (window-state-put pwc rwin t))))) "Function for restoring a window configuration. Accept a window configuration obtained by the `persp-window-state-get-function' and two optional arguments: one -- a frame(default is the selected frame) and another -- root window(default is the root window of the selected frame)." :group 'persp-mode :type 'function) (defcustom persp-buffer-list-function (symbol-function 'buffer-list) "The function that is used mostly internally by persp-mode functions to get a list of all buffers." :group 'persp-mode :type 'function) (defcustom persp-dont-count-weaks-in-restricted-buffer-list nil "if t -- dont count weak perspectives in `persp-buffer-list-restricted'. For now it makes any effect only if the value of the `*persp-restrict-buffers-to*' and friends is 2, 2.5, 3 or 3.5." :group 'persp-mode :type 'boolean) (defcustom persp-auto-persp-alist nil "Alist of auto-persp definitions." :group 'persp-mode :tag "Auto perspectives" :type '(alist :key-type (string :tag "Name") :value-type (alist :tag "Parameters" :key-type (symbol :tag "Keyword")))) ;; Global variables: ;; check if the initial-buffer-choice may be a function (emacs >= 24.4) (defvar persp-is-ibc-as-f-supported (or (not (version< emacs-version "24.4")) (not (null (assq 'function (cdr (cl-getf (symbol-plist 'initial-buffer-choice) 'custom-type)))))) "t if the `initial-buffer-choice' as a function is supported in your emacs, otherwise nil.") (defvar persp-minor-mode-menu nil "Menu for the persp-mode.") (defvar *persp-hash* nil "The hash table that contain perspectives.") (defvar persp-names-cache (when *persp-hash* (persp-names)) "List of perspective names. Used by the `persp-read-persp' and other UI functions, so it can be used to alter the order of perspective names present to user. To achieve that you must add functions to `persp-created-functions', `persp-renamed-functions', `persp-before-kill-functions', `persp-before-switch-functions' and `persp-after-load-state-functions' or just set the `persp-names-sort-before-read-function'.") (defcustom persp-names-sort-before-read-function nil "Function(or nil) to sort `persp-names-cache' before prompting a user for a perspective name(s). The function must take a list of perspective names and return a sorted list." :group 'persp-mode :type '(choice (const :tag "No sort." :value nil) (function :tag "Function" :value #'identity))) (defvar persp-temporarily-display-buffer nil "This variable dynamically bound to t inside the `persp-temporarily-display-buffer'.") (defvar persp-saved-read-buffer-function read-buffer-function "Save the `read-buffer-function' to restore it on deactivation.") (defvar persp-last-persp-name persp-nil-name "The last activated perspective. New frames will be created with that perspective if `persp-set-last-persp-for-new-frames' is t.") (defvar persp-special-last-buffer nil "Special variable to handle the case when new frames are switching the selected window to a wrong buffer.") (defvar persp-frame-buffer-predicate nil "Current buffer-predicate.") (defvar persp-frame-buffer-predicate-buffer-list-cache nil "Variable to cache the perspective buffer list for buffer-predicate.") (defvar persp-frame-server-switch-hook nil "Current persp-server-switch-hook.") (defvar persp-disable-buffer-restriction-once nil "The flag used for toggling buffer filtering during read-buffer.") (defvar persp-inhibit-switch-for nil "List of frames/windows for which the switching of perspectives is inhibited.") (defvar persp-read-multiple-exit-minibuffer-function #'exit-minibuffer "Function to call to exit minibuffer when reading multiple candidates.") (defvar persp-buffer-props-hash (when persp-mode (make-hash-table :test #'eq :size 10)) "Cache to store buffer properties.") (defvar persp-backtrace-frame-function (if (version< emacs-version "24.4") #'(lambda (nframes &optional base) (let ((i (if base (let ((k 8) found bt) (while (and (not found) (setq bt (cadr (funcall #'backtrace-frame (cl-incf k))))) ;; (message "%s:%s" k (backtrace-frame k)) (when (eq bt base) (setq found t))) (when found (+ nframes (- k 3)))) (+ nframes 6)))) (when i (funcall #'backtrace-frame i)))) #'backtrace-frame) "Backtrace function with base argument.") (defcustom persp-switch-wrap t "Whether `persp-next' and `persp-prev' should wrap." :group 'persp-mode :type 'boolean) ;; Key bindings: (define-prefix-command 'persp-key-map) (defvar persp-mode-map (make-sparse-keymap) "The keymap with a prefix for the persp-mode.") (define-key persp-key-map (kbd "n") #'persp-next) (define-key persp-key-map (kbd "p") #'persp-prev) (define-key persp-key-map (kbd "s") #'persp-frame-switch) (define-key persp-key-map (kbd "S") #'persp-window-switch) (define-key persp-key-map (kbd "r") #'persp-rename) (define-key persp-key-map (kbd "c") #'persp-copy) (define-key persp-key-map (kbd "C") #'persp-kill) (define-key persp-key-map (kbd "z") #'persp-save-and-kill) (define-key persp-key-map (kbd "a") #'persp-add-buffer) (define-key persp-key-map (kbd "b") #'persp-switch-to-buffer) (define-key persp-key-map (kbd "t") #'persp-temporarily-display-buffer) (define-key persp-key-map (kbd "i") #'persp-import-buffers) (define-key persp-key-map (kbd "I") #'persp-import-win-conf) (define-key persp-key-map (kbd "k") #'persp-remove-buffer) (define-key persp-key-map (kbd "K") #'persp-kill-buffer) (define-key persp-key-map (kbd "w") #'persp-save-state-to-file) (define-key persp-key-map (kbd "W") #'persp-save-to-file-by-names) (define-key persp-key-map (kbd "l") #'persp-load-state-from-file) (define-key persp-key-map (kbd "L") #'persp-load-from-file-by-names) (define-key persp-key-map (kbd "o") #'(lambda () (interactive) (persp-mode -1))) (defun persp-set-keymap-prefix (prefix) (interactive (list (read-key-sequence "Now press a key sequence to be used as the persp-key-map prefix: "))) (when prefix (when (boundp 'persp-keymap-prefix) (substitute-key-definition 'persp-key-map nil persp-mode-map)) (define-key persp-mode-map prefix 'persp-key-map) (custom-set-default 'persp-keymap-prefix prefix))) (defcustom persp-keymap-prefix (kbd "C-c p") "The prefix for activating the persp-mode keymap." :group 'persp-mode :type 'key-sequence :set #'(lambda (sym val) (persp-set-keymap-prefix val))) ;; TODO: remove this function (defun persp-set-toggle-read-buffer-filter-keys (keys) (interactive (list (read-key-sequence "Now press a key sequence to be used for toggling persp filters during the read-buffer: "))) (setcdr (assq 'toggle-persp-buffer-filter persp-read-multiple-keys) keys) (custom-set-default 'persp-toggle-read-buffer-filter-keys keys)) (define-obsolete-function-alias 'persp-set-toggle-read-persp-filter-keys 'persp-set-toggle-read-buffer-filter-keys "persp-mode 2.9") (defcustom persp-read-multiple-keys `((toggle-persp-buffer-filter . ,(kbd "C-x C-p")) (push-item . ,(kbd "C-")) (pop-item . ,(kbd "M-"))) "Keybindings to use while prompting for multiple items." :group 'persp-mode :tag "Keys for reading multiple items" :type '(alist :key-type symbol :value-type key-sequence)) (define-obsolete-variable-alias 'persp-toggle-read-persp-filter-keys 'persp-toggle-read-buffer-filter-keys "persp-mode 2.9") (defcustom persp-toggle-read-buffer-filter-keys (kbd "C-x C-p") "Keysequence to toggle the buffer filtering during read-buffer." :group 'persp-mode :type 'key-sequence :set #'(lambda (sym val) (persp-set-toggle-read-buffer-filter-keys val))) ;; Perspective struct: (cl-defstruct (perspective (:conc-name persp-) (:constructor make-persp)) (name "") (buffers nil) (window-conf nil) ;; reserved parameters: dont-save-to-file, persp-file. (parameters nil) (weak nil) (auto nil) (hidden nil)) (defun persp-p (obj) (or (null obj) (perspective-p obj))) (defvar persp-nil-wconf nil "Window configuration for the `nil' perspective.") (defvar persp-nil-parameters nil "Parameters of the `nil' perspective.") (defvar persp-nil-hidden nil "Hidden filed for the `nil' perspective.") (defun persp-buffer-list (&optional frame window) (safe-persp-buffers (get-current-persp frame window))) (cl-defun persp-buffer-list-restricted (&optional (frame (selected-frame)) (option *persp-restrict-buffers-to*) (option-foreign-override persp-restrict-buffers-to-if-foreign-buffer) sure-not-killing) (unless frame (setq frame (selected-frame))) (unless option (setq option 0)) (let* ((cpersp (get-current-persp frame)) (curbuf (current-buffer)) (cb-foreign (not (persp-contain-buffer-p curbuf cpersp)))) (when (and option-foreign-override cb-foreign) (setq option option-foreign-override)) (cl-typecase option (function (funcall option frame)) (t (when (= option 2.5) (setq option (if (null cpersp) -1 2))) (when (= option 3.5) (setq option (if (null cpersp) -1 3))) (let ((bl (cl-case option (-1 (funcall persp-buffer-list-function frame)) (0 (if cpersp (cl-copy-list (persp-buffers cpersp)) (funcall persp-buffer-list-function frame))) (1 (let ((ret (if cpersp (let ((pbs (cl-copy-list (persp-buffers cpersp)))) (cl-delete-if #'(lambda (b) (let ((cns (memq b pbs))) (when cns (setcar cns (cadr cns)) (setcdr cns (cddr cns)) t))) (funcall persp-buffer-list-function frame))) nil))) (unless (persp-contain-buffer-p curbuf cpersp) (setq ret (cons curbuf (cl-delete curbuf ret :count 1)))) ret)) (2 (let ((ret (cl-delete-if #'(lambda (b) (persp-buffer-in-other-p* b cpersp persp-dont-count-weaks-in-restricted-buffer-list)) (if cpersp (cl-copy-list (persp-buffers cpersp)) (funcall persp-buffer-list-function frame))))) ret)) (3 (let ((ret (cl-delete-if #'(lambda (b) (or (and cpersp (persp-contain-buffer-p b cpersp)) (persp-buffer-in-other-p* b cpersp persp-dont-count-weaks-in-restricted-buffer-list))) (funcall persp-buffer-list-function frame)))) ret))))) (when persp-buffer-list-restricted-filter-functions (setq bl (cl-delete-if #'(lambda (b) (persp-buffer-filtered-out-p b persp-buffer-list-restricted-filter-functions)) bl))) (when (and (not sure-not-killing) cpersp (symbolp this-command) persp-kill-foreign-buffer-behaviour (string-match-p "^.*?kill-buffer.*?$" (symbol-name this-command)) (not (memq curbuf bl)) ;; TODO: remove this ;; (not (persp-buffer-filtered-out-p curbuf)) ) (push curbuf bl)) bl))))) (cl-defmacro with-persp-buffer-list ((&key (buffer-list-function persp-buffer-list-function) (restriction *persp-restrict-buffers-to*) (restriction-foreign-override persp-restrict-buffers-to-if-foreign-buffer) sortp cache) &rest body) (let ((pblf-body `(persp-buffer-list-restricted frame))) (when sortp (setq pblf-body `(sort ,pblf-body (with-no-warnings ',sortp)))) `(let ((*persp-restrict-buffers-to* ,restriction) (persp-restrict-buffers-to-if-foreign-buffer ,restriction-foreign-override) ,@(if cache `(persp-buffer-list-cache) nil)) (cl-letf (((symbol-function 'buffer-list) #'(lambda (&optional frame) ,(if cache `(if persp-buffer-list-cache persp-buffer-list-cache (setq persp-buffer-list-cache ,pblf-body)) pblf-body)))) ,@body)))) (cl-defmacro with-persp-read-buffer ((&key multiple (default-mode t)) &rest body) `(let ((read-buffer-function #'persp-read-buffer)) ,@body)) (defmacro with-persp-ido-hooks (&rest body) `(let ((ido-make-buffer-list-hook ido-make-buffer-list-hook) (ido-setup-hook ido-setup-hook)) (persp-set-ido-hooks t) ,@body)) ;; TODO: rename (defun safe-persp-name (p) (if p (persp-name p) persp-nil-name)) ;; TODO: rename (defun safe-persp-buffers (p) (if p (persp-buffers p) (funcall persp-buffer-list-function))) ;; TODO: rename (defun safe-persp-window-conf (p) (if p (persp-window-conf p) persp-nil-wconf)) ;; TODO: rename (defun safe-persp-parameters (p) (if p (persp-parameters p) persp-nil-parameters)) ;; TODO: rename (defun safe-persp-weak (p) (if p (persp-weak p) t)) ;; TODO: rename (defun safe-persp-auto (p) (if p (persp-auto p) nil)) ;; TODO: rename (defun safe-persp-hidden (p) (if p (persp-hidden p) persp-nil-hidden)) ;; TODO: rename (cl-defun modify-persp-parameters (alist &optional (persp (get-current-persp))) (cl-loop for (name . value) in alist do (set-persp-parameter name value persp))) ;; TODO: rename (cl-defun set-persp-parameter (param-name &optional value (persp (get-current-persp))) (let* ((params (safe-persp-parameters persp)) (old-cons (assq param-name params))) (if old-cons (setcdr old-cons value) (if persp (setf (persp-parameters persp) (push (cons param-name value) params)) (setq persp-nil-parameters (push (cons param-name value) params)))))) (cl-defun persp-parameter (param-name &optional (persp (get-current-persp))) (alist-get param-name (safe-persp-parameters persp))) ;; TODO: rename (cl-defun delete-persp-parameter (param-name &optional (persp (get-current-persp))) (when (and (not (null param-name)) (symbolp param-name)) (if persp (setf (persp-parameters persp) (delq (assq param-name (persp-parameters persp)) (persp-parameters persp))) (setq persp-nil-parameters (delq (assq param-name persp-nil-parameters) persp-nil-parameters))))) (defun persp--buffer-in-persps (buf) (cdr (assq 'persp-buffer-in-persps (gethash buf persp-buffer-props-hash)))) (defun persp--buffer-in-persps-set (buf persps) (let* ((buf-props (gethash buf persp-buffer-props-hash)) (cons (assq 'persp-buffer-in-persps buf-props))) (if cons (setf (cdr cons) persps) (setq cons (cons 'persp-buffer-in-persps persps)) (push cons buf-props) (puthash buf buf-props persp-buffer-props-hash)))) (defun persp--buffer-in-persps-add (buf persp) (persp--buffer-in-persps-set buf (cons persp (persp--buffer-in-persps buf)))) (defun persp--buffer-in-persps-remove (buf persp) (persp--buffer-in-persps-set buf (delq persp (persp--buffer-in-persps buf)))) ;; Used in mode defenition: (defun persp-mode-restore-and-remove-from-make-frame-hook (&optional f) (remove-hook 'after-make-frame-functions #'persp-mode-restore-and-remove-from-make-frame-hook) (if (> persp-auto-resume-time 0) (run-at-time persp-auto-resume-time nil #'(lambda () (remove-hook 'find-file-hook #'persp-special-last-buffer-make-current) (when (> persp-auto-resume-time 0) (condition-case-unless-debug err (persp-load-state-from-file) (error (message "[persp-mode] Error: Can not autoresume perspectives -- %s" err))) (when (persp-get-buffer-or-null persp-special-last-buffer) (persp-switch-to-buffer persp-special-last-buffer))))) (remove-hook 'find-file-hook #'persp-special-last-buffer-make-current))) (defun persp-asave-on-exit (&optional interactive-query opt) (when persp-mode (when (null opt) (setq opt 0)) (if (> persp-auto-save-opt opt) (condition-case-unless-debug err (persp-save-state-to-file) (error (message "[persp-mode] Error: Can not autosave perspectives -- %s" err) (when (or noninteractive (progn (when (null (persp-frame-list-without-daemon)) (make-frame)) (null (persp-frame-list-without-daemon)))) (setq interactive-query nil)) (if interactive-query (yes-or-no-p "persp-mode can not save perspectives, do you want to exit anyway?") t))) t))) (defun persp-kill-emacs-h () (persp-asave-on-exit nil)) (defun persp-kill-emacs-query-function () (if persp-mode (when (persp-asave-on-exit t) (remove-hook 'kill-emacs-hook #'persp-kill-emacs-h) t) t)) (defun persp-special-last-buffer-make-current () (setq persp-special-last-buffer (current-buffer))) ;; Auto persp functions: (defun persp-auto-persp-parameters (name) (cdr (assoc name persp-auto-persp-alist))) (defun persp--auto-persp-pickup-buffer (a-p-def buffer) (let ((action (alist-get :main-action a-p-def))) (when (functionp action) (funcall action buffer)))) (defun persp-auto-persp-pickup-bufferlist-for (name bufferlist) (let ((a-p-def (persp-auto-persp-parameters name))) (when a-p-def (mapc (apply-partially #'persp--auto-persp-pickup-buffer a-p-def) bufferlist)))) (defun persp-auto-persps-pickup-bufferlist (bufferlist) (mapc #'(lambda (name) (persp-auto-persp-pickup-bufferlist-for name bufferlist)) (mapcar #'car persp-auto-persp-alist))) (defun persp-auto-persp-pickup-buffers-for (name) (persp-auto-persp-pickup-bufferlist-for name (funcall persp-buffer-list-function))) (defun persp-auto-persps-pickup-buffers () (interactive) (persp-auto-persps-pickup-bufferlist (funcall persp-buffer-list-function))) (defun persp-buffer-match-auto-persp-p (buffer-or-name) (let ((buffer (persp-get-buffer-or-null buffer-or-name)) pred) (car-safe (cl-find-if #'(lambda (a-p-def) (and (setq pred (alist-get :generated-predicate a-p-def)) (funcall pred buffer))) persp-auto-persp-alist :key #'cdr)))) (defun persp-auto-persps-for-buffer (buffer-or-name) (let ((buffer (persp-get-buffer-or-null buffer-or-name))) (cl-remove-if #'(lambda (pred) (funcall pred buffer)) persp-auto-persp-alist :key #'(lambda (a-p-cons) (alist-get :generated-predicate (cdr a-p-cons)))))) (defun persp-auto-persp-activate-hooks (name) (let ((hooks (alist-get :hooks (persp-auto-persp-parameters name)))) (mapc #'(lambda (hook-cons) (add-hook (car hook-cons) (cdr hook-cons))) hooks))) (defun persp-auto-persp-deactivate-hooks (name) (let ((hooks (alist-get :hooks (persp-auto-persp-parameters name)))) (mapc #'(lambda (hook-cons) (remove-hook (car hook-cons) (cdr hook-cons))) hooks))) (defun persp-auto-persps-activate-hooks () (mapc #'persp-auto-persp-activate-hooks (mapcar #'car persp-auto-persp-alist))) (defun persp-auto-persps-deactivate-hooks () (mapc #'persp-auto-persp-deactivate-hooks (mapcar #'car persp-auto-persp-alist))) (defsubst persp--generate-predicate-loop-any-all (items-list condition &rest body) (if items-list (let (all noquote) (setq items-list (cl-typecase items-list (function (list items-list)) (list (if (persp-regexp-p items-list) (list items-list) items-list)) (t (list items-list)))) (setq noquote (eq :noquote (car items-list))) (when noquote (setq items-list (cadr items-list))) (when (listp items-list) (setq all (eq :all (car items-list))) (when all (pop items-list)) (unless noquote (setq items-list `',items-list))) (let* ((cnd `(cl-member-if #'(lambda (item) (setq cond-result ,(if all `(not ,condition) condition))) ,items-list))) `(let (cond-result) (when ,(if all `(not ,cnd) cnd) ,@body)))) `(let (cond-result) ,@body))) (cl-defun persp--generate-buffer-predicate (&key buffer-name file-name mode mode-name minor-mode minor-mode-name predicate (true-value (if predicate 'cond-result t)) &allow-other-keys) (let ((predicate-body true-value)) (when predicate (setq predicate-body (persp--generate-predicate-loop-any-all predicate '(apply item buffer rest-args) predicate-body))) (when file-name (setq predicate-body (persp--generate-predicate-loop-any-all file-name '(persp-string-match-p item (buffer-file-name buffer)) predicate-body))) (when buffer-name (setq predicate-body (persp--generate-predicate-loop-any-all buffer-name '(persp-string-match-p item (buffer-name buffer)) predicate-body))) (when minor-mode-name (setq predicate-body (persp--generate-predicate-loop-any-all minor-mode-name `(let ((regexp item)) ,(persp--generate-predicate-loop-any-all '(:noquote minor-mode-alist) '(persp-string-match-p regexp (format-mode-line item)) t)) predicate-body))) (when minor-mode (setq predicate-body (persp--generate-predicate-loop-any-all minor-mode `(cond ((symbolp item) (bound-and-true-p item)) ((persp-regexp-p item) (let ((regexp item)) ,(persp--generate-predicate-loop-any-all '(:noquote minor-mode-list) '(and (bound-and-true-p item) (persp-string-match-p regexp item)) t))) (t nil)) predicate-body))) (when mode-name (setq predicate-body (persp--generate-predicate-loop-any-all mode-name '(persp-string-match-p item (format-mode-line mode-name)) predicate-body))) (when mode (setq predicate-body (persp--generate-predicate-loop-any-all mode '(cond ((symbolp item) (eq item major-mode)) ((persp-regexp-p item) (persp-string-match-p item (symbol-name major-mode))) (t nil)) predicate-body))) (eval `(lambda (buffer &rest rest-args) (when (buffer-live-p buffer) (with-current-buffer buffer ,predicate-body)))))) (defun persp--auto-persp-default-on-match (state) (persp-add-buffer (alist-get 'buffer state) (alist-get 'persp state) nil nil) state) (defun persp--auto-persp-default-after-match (state) (let ((persp (alist-get 'persp state)) (noauto (alist-get :noauto state)) (weak (alist-get :weak state)) (parameters (alist-get :parameters state))) (when persp (when (not noauto) (setf (persp-auto persp) t)) (when weak (setf (persp-weak persp) t)) (modify-persp-parameters parameters persp))) (let ((persp-name (alist-get 'persp-name state)) (switch (alist-get :switch state))) (persp-unhide persp-name) (cl-case switch ('nil nil) (window (persp-window-switch persp-name)) (frame (persp-frame-switch persp-name)) (t (persp-switch persp-name))) (when switch (persp-switch-to-buffer (alist-get 'buffer state)))) state) ;;;###autoload (cl-defun persp-def-auto-persp (name &rest keyargs &key buffer-name file-name mode mode-name minor-mode minor-mode-name predicate hooks dyn-env get-name get-buffer get-persp switch parameters noauto weak user-data on-match after-match dont-pick-up-buffers delete) (if delete (let ((ap-cons (assoc name persp-auto-persp-alist))) (persp-auto-persp-deactivate-hooks name) (setq persp-auto-persp-alist (delq ap-cons persp-auto-persp-alist))) (let (auto-persp-parameters generated-predicate generated-hook hook-body main-action) (cl-loop for (key val) on keyargs by #'cddr when (and val (not (or (eq key :dont-pick-up-buffers)))) do (push (cons key (if (and (functionp val) (not (or (eq key :mode) (eq key :minor-mode))) (null (byte-code-function-p val))) val ;;(byte-compile val) val)) auto-persp-parameters)) (unless get-name (push (cons :get-name (byte-compile `(lambda (state) (push (cons 'persp-name ,name) state) state))) auto-persp-parameters)) (unless get-persp (push (cons :get-persp #'(lambda (state) (let ((name (alist-get 'persp-name state))) (when name (push (cons 'persp (persp-add-new name)) state))) state)) auto-persp-parameters)) (unless get-buffer (push (cons :get-buffer #'(lambda (state) (push (cons 'buffer (current-buffer)) state) state)) auto-persp-parameters)) (unless on-match (push (cons :on-match #'persp--auto-persp-default-on-match) auto-persp-parameters)) (unless after-match (push (cons :after-match #'persp--auto-persp-default-after-match) auto-persp-parameters)) (when (or (null hooks) (not (consp hooks))) (unless hooks (setq hooks (when minor-mode (intern (concat (symbol-name minor-mode) "-hook"))))) (unless hooks (setq hooks (cond (mode (intern (concat (symbol-name mode) "-hook"))) (minor-mode (intern (concat (symbol-name minor-mode) "-hook"))) ((or mode-name predicate buffer-name) 'after-change-major-mode-hook) (file-name 'find-file-hook) (t 'after-change-major-mode-hook)))) (when (and hooks (not (consp hooks))) (setq hooks (list hooks))) (push (cons :hooks hooks) auto-persp-parameters)) (setq generated-predicate (apply #'persp--generate-buffer-predicate (if predicate keyargs (cons :true-value (cons '(car rest-args) keyargs))))) (push (cons :generated-predicate generated-predicate) auto-persp-parameters) (setq main-action (eval `(lambda (&optional buffer hook hook-args) (let (,@dyn-env) (let* ((state (copy-alist (persp-auto-persp-parameters ,name)))) (push (cons 'hook hook) state) (push (cons 'hook-args hook-args) state) (if buffer (push (cons 'buffer buffer) state) (let ((get-buffer (alist-get :get-buffer state))) (setq state (funcall get-buffer state)))) (when (setq state (funcall (alist-get :generated-predicate state) (alist-get 'buffer state) state)) (with-current-buffer (alist-get 'buffer state) (let ((get-name (alist-get :get-name state))) (setq state (funcall get-name state))) (let ((get-persp (alist-get :get-persp state))) (setq state (funcall get-persp state))) (let ((on-match (alist-get :on-match state))) (when on-match (setq state (funcall on-match state)) (let ((after-match (alist-get :after-match state))) (when after-match (setq state (funcall after-match state))))))))))))) (push (cons :main-action main-action) auto-persp-parameters) (when hooks (let ((aparams-hooks (assq :hooks auto-persp-parameters))) (dolist (hook hooks) (setq generated-hook (with-no-warnings (let ((warning-minimum-level :emergency) byte-compile-warnings) (byte-compile `(lambda (&rest hook-args) (when persp-mode (funcall (with-no-warnings ',main-action) nil ',hook hook-args))))))) (setcdr aparams-hooks (delete hook (cdr aparams-hooks))) (push (cons hook generated-hook) (cdr aparams-hooks))))) (let ((auto-persp-definition (assoc name persp-auto-persp-alist))) (if auto-persp-definition (progn (persp-auto-persp-deactivate-hooks name) (setcdr auto-persp-definition auto-persp-parameters)) (setq auto-persp-definition (cons name auto-persp-parameters)) (push auto-persp-definition persp-auto-persp-alist))) (persp-auto-persp-activate-hooks name) (unless dont-pick-up-buffers (persp-auto-persp-pickup-buffers-for name))))) ;;;###autoload (define-obsolete-function-alias 'def-auto-persp 'persp-def-auto-persp "persp-mode 2.9.6") ;; Custom save/load functions: ;;;###autoload (cl-defun persp-def-buffer-save/load (&rest keyargs &key buffer-name file-name mode mode-name minor-mode minor-mode-name predicate tag-symbol save-vars save-function load-function after-load-function mode-restore-function append) (let ((generated-save-predicate (apply #'persp--generate-buffer-predicate keyargs)) save-body load-fun) (when save-vars (unless (listp save-vars) (setq save-vars (list save-vars))) (when (and (or mode mode-name) (not (memq 'major-mode save-vars))) (push 'major-mode save-vars))) (unless tag-symbol (setq tag-symbol 'def-buffer-with-vars)) (setq save-body `(let ((vars-list (with-current-buffer buffer (cl-delete-if-not #'(lambda (lvar) (and ,(persp--generate-predicate-loop-any-all save-vars '(if (persp-regexp-p item) (persp-string-match-p item (symbol-name lvar)) (eq item lvar)) t) (persp-elisp-object-readable-p (symbol-value lvar)))) (buffer-local-variables) :key #'car-safe)))) ,(if save-function `(funcall (with-no-warnings ',save-function) buffer ',tag-symbol vars-list) `(list ',tag-symbol (buffer-name buffer) vars-list))) save-body `(when (funcall (with-no-warnings ',generated-save-predicate) buffer) ,save-body)) (setq load-fun `(lambda (savelist) (cl-destructuring-bind (buffer-name vars-list &rest _rest) (cdr savelist) (let ((buf-file (alist-get 'buffer-file-name vars-list)) (buf-mmode (alist-get 'major-mode vars-list))) ,(when mode-restore-function `(push (cons 'persp-load-buffer-mode-restore-function (with-no-warnings ',mode-restore-function)) vars-list)) (let ((persp-loaded-buffer (persp-buffer-from-savelist (list 'def-buffer buffer-name buf-file buf-mmode (list (cons 'local-vars vars-list))))) (persp-after-load-function (with-no-warnings ',after-load-function)) persp-after-load-lambda) (when (and persp-loaded-buffer persp-after-load-function) (setq persp-after-load-lambda #'(lambda (&rest pall-args) (apply persp-after-load-function persp-loaded-buffer pall-args) (remove-hook 'persp-after-load-state-functions persp-after-load-lambda))) (add-hook 'persp-after-load-state-functions persp-after-load-lambda t)) persp-loaded-buffer))))) (add-hook 'persp-save-buffer-functions (eval `(lambda (buffer) ,save-body)) append) (add-hook 'persp-load-buffer-functions (eval `(lambda (savelist) (when (eq (car savelist) ',tag-symbol) (let ((default-load-fun (with-no-warnings ',load-fun))) ,(if load-function `(funcall (with-no-warnings ',load-function) savelist default-load-fun (with-no-warnings ',after-load-function)) `(funcall default-load-fun savelist)))))) append))) ;;;###autoload (define-obsolete-function-alias 'def-persp-buffer-save/load 'persp-def-buffer-save/load "persp-mode 2.9.6") ;; Mode itself: ;;;###autoload (define-minor-mode persp-mode "Toggle the persp-mode. When active, keeps track of multiple 'perspectives', named collections of buffers and window configurations. Here is a keymap of this minor mode: \\{persp-mode-map}" :require 'persp-mode :group 'persp-mode :keymap persp-mode-map :init-value nil :global t :lighter (:eval persp-lighter) (if persp-mode (when (or (eq 'persp-force-restart persp-mode) (null *persp-hash*)) (setq persp-special-last-buffer nil) (add-hook 'find-file-hook #'persp-special-last-buffer-make-current) (setq *persp-hash* (make-hash-table :test #'equal :size 10)) (setq persp-buffer-props-hash (make-hash-table :test #'eq :size 10)) (setq persp-names-cache nil) (push '(persp . writable) window-persistent-parameters) (persp-add-minor-mode-menu) (persp-add-new persp-nil-name) (add-hook 'find-file-hook #'persp-add-or-not-on-find-file) (add-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function) (add-hook 'kill-buffer-hook #'persp-kill-buffer-h) (add-hook 'before-make-frame-hook #'persp-before-make-frame) (add-hook 'after-make-frame-functions #'persp-init-new-frame) (add-hook 'delete-frame-functions #'persp-delete-frame) (add-hook 'kill-emacs-query-functions #'persp-kill-emacs-query-function) (add-hook 'kill-emacs-hook #'persp-kill-emacs-h) (add-hook 'server-switch-hook #'persp-server-switch) (add-hook 'after-change-major-mode-hook #'persp-after-change-major-mode-h) (persp-set-ido-hooks persp-set-ido-hooks) (persp-set-read-buffer-function persp-set-read-buffer-function) (persp-update-completion-system persp-interactive-completion-system) (condition-case-unless-debug err (mapc #'persp-init-frame (persp-frame-list-without-daemon)) (error (message "[persp-mode] Error: Can not initialize frame -- %s" err))) (when (fboundp 'tabbar-mode) (setq tabbar-buffer-list-function #'persp-buffer-list)) (persp-auto-persps-activate-hooks) (if (or noninteractive (and (daemonp) (null (cdr (frame-list))) (eq (selected-frame) terminal-frame))) (add-hook 'after-make-frame-functions #'persp-mode-restore-and-remove-from-make-frame-hook) (persp-mode-restore-and-remove-from-make-frame-hook))) (run-hooks 'persp-mode-deactivated-hook) (unless (memq #'persp-mode-restore-and-remove-from-make-frame-hook after-make-frame-functions) (persp-asave-on-exit t 1)) (remove-hook 'find-file-hook #'persp-add-or-not-on-find-file) (remove-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function) (remove-hook 'kill-buffer-hook #'persp-kill-buffer-h) (remove-hook 'before-make-frame-hook #'persp-before-make-frame) (remove-hook 'after-make-frame-functions #'persp-init-new-frame) (remove-hook 'delete-frame-functions #'persp-delete-frame) (remove-hook 'kill-emacs-query-functions #'persp-kill-emacs-query-function) (remove-hook 'kill-emacs-hook #'persp-kill-emacs-h) (remove-hook 'server-switch-hook #'persp-server-switch) (remove-hook 'after-change-major-mode-hook #'persp-after-change-major-mode-h) (persp-set-ido-hooks) (persp-set-read-buffer-function) (persp-update-frames-buffer-predicate t) (persp-update-completion-system nil t) (persp-auto-persps-deactivate-hooks) (when (fboundp 'tabbar-mode) (setq tabbar-buffer-list-function #'tabbar-buffer-list)) (setq window-persistent-parameters (delq (assq 'persp window-persistent-parameters) window-persistent-parameters)) ;; TODO: do it properly -- remove buffers, kill perspectives (setq *persp-hash* nil) (setq persp-buffer-props-hash nil) (setq persp-names-cache nil))) ;; Hooks: (defun persp--kill-buffer-query-function-foreign-check (persp buf) (let ((opt persp-kill-foreign-buffer-behaviour)) (cond ((functionp opt) (funcall opt)) (t (if (cl-case opt ((kill nil) t) (dont-ask-weak (persp-buffer-free-p buf t)) (t (persp-buffer-filtered-out-p buf))) 'kill (let ((curwin (selected-window)) (prompt (format "You are going to kill a buffer(%s) \ which is not in the current(%s) perspective. It will be removed from \ %s perspectives and then killed.\nWhat do you really want to do? " (buffer-name buf) (safe-persp-name persp) (mapcar #'persp-name (persp--buffer-in-persps buf))))) (cl-macrolet ((clwin (w) `(run-at-time 1 nil #'(lambda (ww) (when (window-live-p ww) (delete-window ww))) ,w)) (swb (b w) `(run-at-time 1 nil #'(lambda (bb ww) (with-selected-window ww (persp-set-another-buffer-for-window bb ww))) ,b ,w))) (cl-destructuring-bind (char &rest _) (let ((variants (list '(?q "do nothing") '(?k "kill") '(?K "kill and close window") '(?c "close window") '(?s "switch to another buffer"))) (cwin (selected-window))) (when (minibuffer-window-active-p cwin) (setq cwin (minibuffer-selected-window))) (unless (eq buf (window-buffer cwin)) (setq variants (delq (assq ?K variants) (delq (assq ?c variants) (delq (assq ?s variants) variants))))) (read-multiple-choice prompt variants)) (cl-case char ((?q ?\C-g ?\C-\[) nil) (?k 'kill) (?K (clwin curwin) 'kill) (?c (clwin curwin) nil) (?s (swb buf curwin) nil) (t t)))))))))) (defun persp-kill-buffer-query-function () "This must be the last hook in the `kill-buffer-query-functions'. Otherwise if next function in the list returns nil -- the buffer will not be killed, but just removed from a perspective(s)." (if persp-mode (let ((buffer (current-buffer))) (if (persp--buffer-in-persps buffer) (let* ((persp (get-current-persp)) (foreign-check (if (and persp (persp-contain-buffer-p buffer persp)) 'not-foreign (persp--kill-buffer-query-function-foreign-check persp buffer)))) (cl-case foreign-check (kill (let (persp-autokill-buffer-on-remove) (persp--remove-buffer-2 nil buffer)) t) (not-foreign (if (persp-buffer-in-other-p* buffer persp) (progn (persp--remove-buffer-2 persp buffer) nil) (if (or (not (buffer-live-p buffer)) (persp--buffer-in-persps buffer)) nil t) t)) (t nil))) t)) t)) (defun persp-kill-buffer-h () (let ((buffer (current-buffer))) (when (and persp-mode (persp--buffer-in-persps buffer)) (let (persp-autokill-buffer-on-remove (persp-when-remove-buffer-switch-to-other-buffer (unless persp-set-frame-buffer-predicate persp-when-remove-buffer-switch-to-other-buffer))) (persp--remove-buffer-2 nil buffer))))) (defun persp--restore-buffer-on-find-file () (when (buffer-live-p persp-special-last-buffer) (set-window-buffer (or (get-buffer-window) (selected-window)) persp-special-last-buffer)) (setq persp-special-last-buffer nil) (remove-hook 'window-configuration-change-hook #'persp--restore-buffer-on-find-file)) (defun persp-add-or-not-on-find-file () (let ((no-select (not (funcall persp-backtrace-frame-function 0 'find-file)))) (and (cl-case persp-add-buffer-on-find-file ('nil nil) (if-not-autopersp (let ((ret (not (persp-buffer-match-auto-persp-p (current-buffer))))) (unless (or ret no-select) (setq persp-special-last-buffer (window-buffer)) (add-hook 'window-configuration-change-hook #'persp--restore-buffer-on-find-file)) ret)) (add-but-not-switch-if-autopersp (when (and (not no-select) (persp-buffer-match-auto-persp-p (current-buffer))) (setq no-select t) (setq persp-special-last-buffer (window-buffer)) (add-hook 'window-configuration-change-hook #'persp--restore-buffer-on-find-file)) t) (t t)) (persp-add-buffer (current-buffer) (get-current-persp) (not no-select) nil)))) (defun persp-after-change-major-mode-h () (let ((buf (current-buffer))) (persp-find-and-set-persps-for-buffer buf) (when (and (cl-case persp-add-buffer-on-after-change-major-mode ('nil nil) (free (persp-buffer-free-p buf)) (t t)) (not (persp-buffer-filtered-out-p buf persp-add-buffer-on-after-change-major-mode-filter-functions))) (persp-add-buffer buf (get-current-persp) nil nil)))) (defun persp-server-switch () (condition-case-unless-debug err (let* ((frame (selected-frame)) (persp-server-switch-hook (frame-parameter frame 'persp-server-switch-hook))) (when persp-server-switch-hook (unless (string-match-p "^.*magit.*$" (symbol-name last-command)) (funcall persp-server-switch-hook frame)) (set-frame-parameter frame 'persp-server-switch-hook nil))) (error (message "[persp-mode] Error: error in server-switch-hook -- %s" err)))) ;; Misc funcs: (cl-defun persp-get-by-name (name &optional (phash *persp-hash*) (default persp-not-persp)) (gethash name phash default)) (cl-defun persp-with-name-exists-p (name &optional (phash *persp-hash*)) (persp-p (persp-get-by-name name phash))) (cl-defun persp-by-name-and-exists (name &optional (phash *persp-hash*)) (let ((persp (persp-get-by-name name phash))) (cons (persp-p persp) persp))) (cl-defun persp-gen-random-name (&optional name (phash *persp-hash*)) (unless name (setq name (number-to-string (random)))) (cl-macrolet ((namegen () `(format "%s:%s" name (random 9)))) (cl-do ((nname name (namegen))) ((not (persp-with-name-exists-p nname phash)) nname)))) (defsubst persp-is-frame-daemons-frame (f) (and (daemonp) (eq f terminal-frame))) (defun persp-frame-list-without-daemon () "Return a list of frames without the daemon's frame." (if (daemonp) (filtered-frame-list #'(lambda (f) (not (persp-is-frame-daemons-frame f)))) (frame-list))) ;; TODO: rename (defun set-frame-persp (persp &optional frame) (set-frame-parameter frame 'persp persp)) ;; TODO: rename (defun get-frame-persp (&optional frame) (frame-parameter frame 'persp)) (cl-defun persp-names (&optional (phash *persp-hash*) (reverse t)) (let (ret) (maphash #'(lambda (k p) (push k ret)) phash) (if reverse (nreverse ret) ret))) ;; TODO: rename (defun set-window-persp* (persp-name &optional window) (when persp-name (set-window-parameter window 'persp persp-name))) ;; TODO: rename (defun get-window-persp* (&optional window) (window-parameter window 'persp)) ;; TODO: rename (defun set-window-persp (persp &optional window) (let ((frame (window-frame window))) (if (eq persp (get-frame-persp frame)) (clear-window-persp window) (set-window-persp* (safe-persp-name persp) window)))) ;; TODO: rename (defun window-persp-set-p (&optional window) (get-window-persp* window)) ;; TODO: rename (defun get-window-persp (&optional window) (let ((pn (get-window-persp* window))) (when pn (cl-destructuring-bind (e . p) (persp-by-name-and-exists pn) (and e p))))) ;; TODO: rename (defun clear-window-persp (&optional window) (set-window-parameter window 'persp nil)) ;; TODO: rename (defun get-current-persp (&optional frame window) (with-selected-frame (or frame (selected-frame)) (if (window-persp-set-p window) (get-window-persp window) (get-frame-persp frame)))) ;; TODO: rename (defun set-current-persp (persp) (if (window-persp-set-p) (set-window-persp persp) (set-frame-persp persp))) (defun persp-names-current-frame-fast-ordered () (cl-copy-list persp-names-cache)) ;; TODO: remove this (cl-defsubst persp-names-sorted (&optional (phash *persp-hash*)) (sort (persp-names phash nil) #'string<)) (make-obsolete 'persp-names-sorted "it will be removed." "persp-mode 2.9.6") (defun persp-group-by (keyf lst &optional reverse) (let (result) (mapc #'(lambda (pd) (let* ((key (funcall keyf pd)) (kv (assoc key result))) (if kv (setcdr kv (cons pd (cdr kv))) (push (cons key (list pd)) result)))) lst) (if reverse (nreverse (mapcar #'(lambda (gr) (cl-destructuring-bind (key . pd) gr (cons key (nreverse pd)))) result)) result))) (defun persp-regexp-p (obj) (or (stringp obj) (and (consp obj) (stringp (cdr obj))))) (defun persp-string-match-p (regexp string &optional start) (when (and regexp (not (consp regexp))) (setq regexp (cons t regexp))) (let ((ret (string-match-p (cdr regexp) string start))) (if (eq :not (car regexp)) (not ret) ret))) (cl-defun persp-persps (&optional (phash *persp-hash*) names-regexp reverse) (when (and names-regexp (not (consp names-regexp))) (setq names-regexp (cons t names-regexp))) (let (ret) (maphash #'(lambda (k p) (if names-regexp (when (persp-string-match-p names-regexp k) (push p ret)) (push p ret))) phash) (if reverse (nreverse ret) ret))) (cl-defun persp-other-not-hidden-persps (&optional persp (phash *persp-hash*)) (cl-delete-if #'safe-persp-hidden (delq persp (persp-persps phash)))) (cl-defun persp-other-persps-with-buffer-except-nil (&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) (phash *persp-hash*) del-weak) (let ((buf (persp-get-buffer-or-null buff-or-name)) ret) (when buf (setq ret (cl-delete-if-not (apply-partially #'memq buf) (delq persp (delq nil (persp-persps phash))) :key #'persp-buffers)) (when del-weak (setq ret (cl-delete-if #'persp-weak ret)))) ret)) (cl-defun persp-other-persps-with-buffer-except-nil* (&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) del-weak) (let ((persps (persp--buffer-in-persps (persp-get-buffer-or-null buff-or-name)))) (when persp (setq persps (remq persp persps))) (when del-weak (setq persps (cl-remove-if #'persp-weak persps))) persps)) (cl-defun persp-buffer-in-other-p (&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) (phash *persp-hash*) del-weak) (persp-other-persps-with-buffer-except-nil buff-or-name persp phash del-weak)) (cl-defun persp-buffer-in-other-p* (&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) del-weak) (persp-other-persps-with-buffer-except-nil* buff-or-name persp del-weak)) (cl-defun persp-frames-with-persp (&optional (persp (get-frame-persp))) (cl-delete-if-not (apply-partially #'eq persp) (persp-frame-list-without-daemon) :key #'get-frame-persp)) (cl-defun persp-frames-and-windows-with-persp (&optional (persp (get-current-persp))) (let (frames windows) (dolist (frame (persp-frame-list-without-daemon)) (when (eq persp (get-frame-persp frame)) (push frame frames)) (dolist (window (window-list frame 'no-minibuf)) (when (and (window-persp-set-p window) (eq persp (get-window-persp window))) (push window windows)))) (cons frames windows))) (cl-defun persp-do-buffer-list-by-regexp (&key func regexp blist noask (rest-args nil rest-args-p)) (interactive) (unless func (let ((fs (completing-read "What function to apply: " obarray 'functionp t))) (when (and fs (not (string= fs ""))) (setq func (read fs))))) (when func (unless regexp (setq regexp (read-regexp "Regexp: "))) (when regexp (unless blist (setq blist (eval (read--expression "Buffer list expression: " "nil")))) (when blist (unless rest-args-p (setq rest-args (read--expression "Rest arguments: " "nil"))) (setq blist (cl-remove-if-not (apply-partially #'persp-string-match-p regexp) (mapcar #'get-buffer blist) :key #'buffer-name)) (when (and blist (or noask (y-or-n-p (format "Do %s on these buffers:\n%s?\n" func (mapconcat #'buffer-name blist ", "))))) (mapcar #'(lambda (b) (apply func b rest-args)) blist)))))) ;; Perspective funcs: (defun persp-next () "Switch to next perspective (to the right)." (interactive) (let* ((persp-list (persp-names-current-frame-fast-ordered)) (persp-list-length (length persp-list)) (only-perspective? (equal persp-list-length 1)) (pos (cl-position (safe-persp-name (get-current-persp)) persp-list))) (cond ((null pos) nil) (only-perspective? nil) ((= pos (1- persp-list-length)) (if persp-switch-wrap (persp-switch (nth 0 persp-list)))) (t (persp-switch (nth (1+ pos) persp-list)))))) (defun persp-prev () "Switch to previous perspective (to the left)." (interactive) (let* ((persp-list (persp-names-current-frame-fast-ordered)) (persp-list-length (length persp-list)) (only-perspective? (equal persp-list-length 1)) (pos (cl-position (safe-persp-name (get-current-persp)) persp-list))) (cond ((null pos) nil) (only-perspective? nil) ((= pos 0) (if persp-switch-wrap (persp-switch (nth (1- persp-list-length) persp-list)))) (t (persp-switch (nth (1- pos) persp-list)))))) (cl-defun persp-add (persp &optional (phash *persp-hash*)) "Insert `PERSP' to `PHASH'. If we adding to the `*persp-hash*' add entries to the mode menu. Return `PERSP'." (let ((name (safe-persp-name persp))) (puthash name persp phash) (when (eq phash *persp-hash*) (persp-add-to-menu persp))) persp) (cl-defun persp-remove-by-name (name &optional (phash *persp-hash*)) "Remove a perspective with name `NAME' from `PHASH'. Save it's state before removing. If we removing from the `*persp-hash*' remove also the menu entries. Switch all frames with that perspective to another one. Return the removed perspective." (interactive "i") (unless name (setq name (persp-read-persp "to remove" nil (and (eq phash *persp-hash*) (safe-persp-name (get-current-persp))) t t))) (let ((persp (persp-get-by-name name phash)) (persp-to-switch persp-nil-name)) (when (persp-p persp) (persp-save-state persp) (if (and (eq phash *persp-hash*) (null persp)) (message "[persp-mode] Error: Can't remove the 'nil' perspective") (when (eq phash *persp-hash*) (persp-remove-from-menu persp) (cl-destructuring-bind (frames . windows) (persp-frames-and-windows-with-persp persp) (dolist (w windows) (clear-window-persp w)) ;; (setq persp-to-switch (or (car (persp-names phash nil)) ;; persp-nil-name)) (dolist (f frames) (persp-frame-switch persp-to-switch f)))) (remhash name phash))) persp)) (cl-defun persp-add-new (name &optional (phash *persp-hash*)) "Create a new perspective with the given `NAME'. Add it to `PHASH'. Return the created perspective." (interactive "sA name for the new perspective: ") (if (and name (not (equal "" name))) (cl-destructuring-bind (e . p) (persp-by-name-and-exists name phash) (if e p (setq p (if (equal persp-nil-name name) nil (make-persp :name name))) (persp-add p phash) (run-hook-with-args 'persp-created-functions p phash) p)) (message "[persp-mode] Error: Can't create a perspective with empty name.") nil)) (defun persp-find-and-set-persps-for-buffer (&optional buffer-or-name) (setq buffer-or-name (if buffer-or-name (persp-get-buffer-or-null buffer-or-name) (current-buffer))) (mapc #'(lambda (p) (when p (persp-add-buffer buffer-or-name p nil nil))) (persp--buffer-in-persps buffer-or-name)) (persp--buffer-in-persps-set buffer-or-name (cl-delete-if-not (apply-partially #'memq buffer-or-name) (delq nil (persp-persps)) :key #'persp-buffers))) (cl-defun persp-contain-buffer-p (&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) delweak) (if (and delweak (safe-persp-weak persp)) nil (if persp (memq (persp-get-buffer-or-null buff-or-name) (persp-buffers persp)) t))) (cl-defun persp-contain-buffer-p* (&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) delweak) (if (and delweak (safe-persp-weak persp)) nil (if persp (memq persp (persp--buffer-in-persps (persp-get-buffer-or-null buff-or-name))) t))) (cl-defun persp-add-buffer (&optional buffs-or-names (persp (get-current-persp)) (switchorno persp-switch-to-added-buffer) (called-interactively-p (called-interactively-p 'any))) (interactive "i") (when (and called-interactively-p current-prefix-arg) (setq switchorno (not switchorno))) (unless buffs-or-names (setq buffs-or-names (when called-interactively-p (let ((*persp-restrict-buffers-to* 1) persp-restrict-buffers-to-if-foreign-buffer) (persp-read-buffer (concat "Add buffers to the perspective" (and switchorno " and switch to first added buffer") ": ") (current-buffer) t nil t))))) (unless (listp buffs-or-names) (setq buffs-or-names (list buffs-or-names))) (mapc #'(lambda (bon) (let ((buffer (persp-get-buffer-or-null bon))) (when (and persp buffer) (unless (persp-contain-buffer-p buffer persp) (push buffer (persp-buffers persp))) (unless (persp-contain-buffer-p* buffer persp) (persp--buffer-in-persps-add buffer persp))) (when (and buffer switchorno (eq persp (get-current-persp))) (persp-switch-to-buffer buffer)) buffer)) buffs-or-names) buffs-or-names) (cl-defun persp-add-buffers-by-regexp (&optional regexp (persp (get-current-persp))) (interactive) (when persp (persp-do-buffer-list-by-regexp :regexp regexp :func 'persp-add-buffer :rest-args (list persp nil) :blist (persp-buffer-list-restricted (selected-frame) 1)))) (cl-defun persp-temporarily-display-buffer (&optional buff-or-name (called-interactively-p (called-interactively-p 'any))) (interactive "i") (let ((persp-temporarily-display-buffer t)) (unless buff-or-name (setq buff-or-name (if called-interactively-p (let ((*persp-restrict-buffers-to* (if (and called-interactively-p current-prefix-arg) 0 1)) (persp-restrict-buffers-to-if-foreign-buffer (if (= 0 *persp-restrict-buffers-to*) -1 nil))) (persp-read-buffer (if (= 0 *persp-restrict-buffers-to*) "Remove a buffer from the perspective, but still display it: " "Temporarily display a buffer, not adding it to the current perspective: ") nil t)) (current-buffer)))) (let ((buffer (persp-get-buffer-or-null buff-or-name))) (when buffer (let ((persp (get-current-persp))) (when (and persp (persp-contain-buffer-p* buffer persp)) (let (persp-autokill-buffer-on-remove persp-autokill-persp-when-removed-last-buffer) (persp-remove-buffer buffer persp nil nil nil nil)))) (persp-switch-to-buffer buffer t))))) (defun persp--buffer-do-auto-action-if-needed (buffer) (when (and persp-autokill-buffer-on-remove (persp-buffer-free-p buffer (eq 'kill-weak persp-autokill-buffer-on-remove))) (let (persp-autokill-buffer-on-remove) (persp-kill-buffer buffer)))) (defun persp--remove-buffer-1 (buffer &optional persp) (if persp (progn (when persp-when-remove-buffer-switch-to-other-buffer (persp-switch-to-prev-buffer buffer persp)) (persp--buffer-in-persps-remove buffer persp) (setf (persp-buffers persp) (delq buffer (persp-buffers persp))) persp) (mapcar (apply-partially #'persp--remove-buffer-1 buffer) (persp-other-persps-with-buffer-except-nil buffer persp)))) (defun persp--remove-buffer-2 (&optional persp buffer-or-name) (let ((buffer (if buffer-or-name (persp-get-buffer-or-null buffer-or-name) (current-buffer)))) (when buffer (persp--remove-buffer-1 buffer persp) (persp--buffer-do-auto-action-if-needed buffer) (persp--do-auto-action-if-needed persp)) buffer)) (defun persp--remove-buffers-from-nil-p (buffs-or-names) (cl-typecase persp-remove-buffers-from-nil-persp-behaviour (function (funcall persp-remove-buffers-from-nil-persp-behaviour buffs-or-names)) (symbol (cl-macrolet ((ask () `(yes-or-no-p (format "Remove %s buffers from all perspectives?" buffs-or-names)))) (cl-case persp-remove-buffers-from-nil-persp-behaviour (ask-to-rem-from-all (if (cl-find-if-not #'persp-buffer-free-p buffs-or-names) (ask) t)) (ask-if-in-non-weak-persp (if (cl-find-if-not #'(lambda (bon) (persp-buffer-free-p bon t)) buffs-or-names) (ask) t)) (t t)))) (t t))) (cl-defun persp-remove-buffer (&optional buffs-or-names (persp (get-current-persp)) (rem-from-nil-opt persp-remove-buffers-from-nil-persp-behaviour) (switch persp-when-remove-buffer-switch-to-other-buffer) called-from-kill-buffer-hook (called-interactively-p (called-interactively-p 'any))) "Remove BUFFS-OR-NAMES(which may be a single buffer or a list of buffers) from the PERSP. On success return removed buffers otherwise nil." (interactive "i") ;; TODO: remove these parameters (ignore called-from-kill-buffer-hook rem-from-nil-opt switch) (unless (listp buffs-or-names) (setq buffs-or-names (list buffs-or-names))) (unless buffs-or-names (setq buffs-or-names (if called-interactively-p (let ((*persp-restrict-buffers-to* 0) persp-restrict-buffers-to-if-foreign-buffer) (persp-read-buffer "Remove buffers from the perspective: " (current-buffer) t nil t)) (current-buffer)))) (when (or persp (persp--remove-buffers-from-nil-p buffs-or-names)) (let ((persp-autokill-buffer-on-remove (if (and called-interactively-p current-prefix-arg) (not persp-autokill-buffer-on-remove) persp-autokill-buffer-on-remove))) (mapcar (apply-partially #'persp--remove-buffer-2 persp) buffs-or-names)))) (defun persp-kill-buffer (&optional buffers-or-names) "Kill buffers, read buffer with restriction to current perspective." (interactive (list (let ((*persp-restrict-buffers-to* 0) persp-restrict-buffers-to-if-foreign-buffer) (if persp-mode (persp-read-buffer "Kill buffers: " (current-buffer) t nil t) (read-buffer "Kill buffer: " (current-buffer) t))))) (unless (listp buffers-or-names) (setq buffers-or-names (list buffers-or-names))) (mapc #'kill-buffer (cl-remove-if-not #'persp-get-buffer-or-null buffers-or-names)) buffers-or-names) (defun persp-switch-to-buffer (buffer-or-name &optional norecord force-same-window) "Switch to buffer, read buffer with restriction to current perspective." (interactive (list (let ((*persp-restrict-buffers-to* 0) persp-restrict-buffers-to-if-foreign-buffer) (if persp-mode (let ((dflt (other-buffer (current-buffer)))) (unless (memq dflt (safe-persp-buffers (get-current-persp))) (cl-psetq dflt (current-buffer))) (persp-read-buffer "Switch to buffer: " dflt t)) (read-buffer-to-switch "Switch to buffer: "))))) (when (and buffer-or-name (persp-get-buffer-or-null (get-buffer buffer-or-name))) (switch-to-buffer buffer-or-name norecord force-same-window))) (cl-defun persp-remove-buffers-by-regexp (&optional regexp (persp (get-current-persp))) (interactive) (when persp (persp-do-buffer-list-by-regexp :regexp regexp :func 'persp-remove-buffer :blist (persp-buffers persp) :rest-args (list persp)))) (cl-defun persp-import-buffers-from (persp-from &optional (persp-to (get-current-persp))) (if persp-to (mapc #'(lambda (b) (persp-add-buffer b persp-to nil nil)) (safe-persp-buffers persp-from)) (message "[persp-mode] Error: Can't import buffers to the 'nil' perspective, \ cause it already contain all buffers."))) (cl-defun persp-import-buffers (names &optional (persp-to (get-current-persp)) (phash *persp-hash*)) "Import buffers from perspectives with the given names to another one." (interactive "i") (unless (listp names) (setq names (list names))) (unless names (setq names (persp-read-persp "to import buffers from" t nil t nil t))) (mapc #'(lambda (persp-from) (persp-import-buffers-from persp-from persp-to)) (mapcar #'(lambda (pn) (persp-get-by-name pn phash)) names))) (cl-defun persp-import-win-conf (name &optional (persp-to (get-current-persp)) (phash *persp-hash*) no-update-frames) (interactive "i") (unless name (setq name (persp-read-persp "to import window configuration from" nil nil t nil t))) (let ((persp-from (persp-get-by-name name phash))) (unless (or (eq persp-to persp-from) (not (persp-p persp-from))) (if persp-to (setf (persp-window-conf persp-to) (safe-persp-window-conf persp-from)) (setq persp-nil-wconf (persp-window-conf persp-from))) (unless no-update-frames (persp-update-frames-window-confs (list (safe-persp-name persp-to))))))) (cl-defun persp-copy (new-name &optional switch (called-interactively-p (called-interactively-p 'any))) (interactive "i") (unless new-name (setq new-name (read-string "Copy current persp with name: "))) (if (member new-name (persp-names)) (progn (message "[persp-mode] Error: There is already a perspective with that name %s" new-name) nil) (let* ((new-persp (persp-add-new new-name)) (current-persp (get-current-persp)) (new-buffers (when new-persp (if current-persp (cl-copy-list (persp-buffers current-persp)) (safe-persp-buffers current-persp))))) (when new-persp (when (and called-interactively-p current-prefix-arg) (setq new-buffers (let (choosen-buffers) (cl-delete-if-not (cl-destructuring-bind (char &rest _) (read-multiple-choice "What buffers to copy? " '((?a "all") (?d "displayed") (?f "free and displayed") (?F "free") (?c "choose") (?n "none"))) (cl-case char (?d #'(lambda (b) (get-buffer-window-list b 'no-minibuf))) (?f #'(lambda (b) (or (persp-buffer-free-p b t) (get-buffer-window-list b 'no-minibuf)))) (?F #'(lambda (b) (persp-buffer-free-p b t))) (?c (setq choosen-buffers (mapcar #'get-buffer (persp-read-buffer "" (current-buffer) t nil t 'push))) #'(lambda (b) (memq b choosen-buffers))) (?n #'not) (?a nil) (t nil))) new-buffers)))) (persp-save-state current-persp) (setf (persp-window-conf new-persp) (safe-persp-window-conf current-persp) (persp-parameters new-persp) (cl-copy-list (safe-persp-parameters current-persp)) (persp-weak new-persp) (if current-persp (persp-weak current-persp) nil)) (persp-add-buffer new-buffers new-persp nil nil) (cl-case switch (window (persp-window-switch new-name)) (frame (persp-frame-switch new-name)) (no-switch nil) (t (persp-switch new-name))) new-persp)))) (cl-defun persp-get-buffer (&optional (buff-or-name (current-buffer)) (persp (get-current-persp))) "Like `get-buffer', but constrained to the perspective's list of buffers. Return the buffer if it's in the perspective or the first buffer from the perspective buffers or nil." (let ((buffer (persp-get-buffer-or-null buff-or-name))) (or (cl-find buffer (safe-persp-buffers persp)) (cl-first (safe-persp-buffers persp))))) (defun persp-get-buffer-or-null (buff-or-name) "Safely return a buffer or the nil without errors." (cl-typecase buff-or-name ((or string buffer) (let ((buf (get-buffer buff-or-name))) (and (buffer-live-p buf) buf))) (otherwise nil))) (defun persp-buffer-filtered-out-p (buff-or-name &rest filters) (setq filters (if filters (cons persp-common-buffer-filter-functions filters) persp-common-buffer-filter-functions) buff-or-name (get-buffer buff-or-name)) (cl-find-if #'(lambda (filter) (if (functionp filter) (funcall filter buff-or-name) (cl-find-if #'(lambda (f) (funcall f buff-or-name)) filter))) filters)) (defun persp-buffer-free-p (&optional buff-or-name del-weak) (unless buff-or-name (setq buff-or-name (current-buffer))) (let ((persps (persp--buffer-in-persps (persp-get-buffer-or-null buff-or-name)))) (if persps (if del-weak (not (cl-find-if-not #'persp-weak persps)) nil) t))) (cl-defun persp-set-another-buffer-for-window (&optional (old-buff-or-name (current-buffer)) (window (selected-window)) (persp (get-current-persp nil window))) (unless (window-minibuffer-p window) (let* ((old-buf (persp-get-buffer-or-null old-buff-or-name)) (new-buf (if persp-set-frame-buffer-predicate (other-buffer old-buf) (cl-find-if #'(lambda (bc) (and (bufferp bc) (not (eq bc old-buf)) (persp-contain-buffer-p bc persp))) (append (mapcar #'car (window-prev-buffers window)) (window-next-buffers window)))))) (set-window-buffer window (or (and (buffer-live-p new-buf) new-buf) (car (persp-buffer-list-restricted (window-frame window) 2.5)) (car (buffer-list))))))) (cl-defun persp-switch-to-prev-buffer (&optional (old-buff-or-name (current-buffer)) (persp (get-current-persp))) "Switch all windows in all frames with a perspective displaying that buffer to some previous buffer in the perspective. Return that old buffer." (let ((old-buf (persp-get-buffer-or-null old-buff-or-name))) (cl-destructuring-bind (frames . windows) (persp-frames-and-windows-with-persp persp) (dolist (w windows) (persp-set-another-buffer-for-window old-buf w)) (dolist (f frames) (dolist (w (get-buffer-window-list old-buf 'no-minibuf f)) (persp-set-another-buffer-for-window old-buf w)))) old-buf)) (cl-defsubst persp-filter-out-bad-buffers (&optional (persp (get-current-persp))) ;; filter out killed buffers (when persp (setf (persp-buffers persp) (cl-delete-if-not #'persp-get-buffer-or-null (persp-buffers persp))))) (defun persp-hide (names) (interactive "i") (unless (listp names) (setq names (list names))) (unless names (setq names (persp-read-persp "to hide" t (safe-persp-name (get-current-persp)) t))) (let ((persp-to-switch (get-current-persp)) (hidden-persps (mapcar #'(lambda (pn) (let ((persp (persp-get-by-name pn))) (when (persp-p persp) (if persp (setf (persp-hidden persp) t) (setq persp-nil-hidden t))) persp)) names))) (when (safe-persp-hidden persp-to-switch) (setq persp-to-switch (car (persp-other-not-hidden-persps persp-to-switch)))) (mapc #'(lambda (p) (when (persp-p p) (cl-destructuring-bind (frames . windows) (persp-frames-and-windows-with-persp p) (dolist (w windows) (clear-window-persp w)) (dolist (f frames) (persp-frame-switch (safe-persp-name persp-to-switch) f))))) hidden-persps))) (defun persp-unhide (names) (interactive "i") (unless (listp names) (setq names (list names))) (unless names (let ((hidden-persps (mapcar #'safe-persp-name (cl-delete-if-not #'safe-persp-hidden (persp-persps))))) (setq names (persp-read-persp "to unhide" t (car hidden-persps) t nil nil hidden-persps t)))) (when names (mapc #'(lambda (pn) (let ((persp (persp-get-by-name pn))) (when (persp-p persp) (if persp (setf (persp-hidden persp) nil) (setq persp-nil-hidden nil))))) names))) (cl-defun persp-kill (names &optional dont-kill-buffers (called-interactively-p (called-interactively-p 'any))) (interactive "i") (when (and called-interactively-p current-prefix-arg) (setq dont-kill-buffers (not dont-kill-buffers))) (unless (listp names) (setq names (list names))) (unless names (setq names (persp-read-persp (concat "to kill" (and dont-kill-buffers " not killing buffers")) t (safe-persp-name (get-current-persp)) t))) (mapc #'(lambda (pn) (let ((persp (persp-get-by-name pn))) (when (persp-p persp) (when (or (not called-interactively-p) (not (null persp)) (yes-or-no-p "Really kill the 'nil' perspective (It'l kill all buffers)?")) (let ((pfile (persp-parameter 'persp-file persp))) (cl-case persp-auto-save-persps-to-their-file-before-kill (persp-file nil) ('nil (setq pfile nil)) (t (unless pfile (setq pfile persp-auto-save-fname)))) (when pfile (persp-save-to-file-by-names pfile *persp-hash* (list pn) t nil))) (run-hook-with-args 'persp-before-kill-functions persp) (let (persp-autokill-persp-when-removed-last-buffer) (if dont-kill-buffers (let (persp-autokill-buffer-on-remove) (mapc #'(lambda (b) (persp-remove-buffer b persp t t nil nil)) (safe-persp-buffers persp))) (mapc #'(lambda (b) (persp-remove-buffer b persp t t nil nil)) (safe-persp-buffers persp)))) (when persp (persp-remove-by-name pn)))))) names)) (defun persp-kill-without-buffers (names) (interactive "i") (persp-kill names t nil)) (cl-defun persp-save-and-kill (names &optional dont-kill-buffers (called-interactively-p (called-interactively-p 'any))) (interactive "i") (when (and called-interactively-p current-prefix-arg) (setq dont-kill-buffers (not dont-kill-buffers))) (unless (listp names) (setq names (list names))) (unless names (setq names (persp-read-persp (concat "to save and kill" (and dont-kill-buffers " not killing buffers")) t (safe-persp-name (get-current-persp)) t))) (let ((temphash (make-hash-table :test 'equal :size 10))) (mapc #'(lambda (p) (persp-add p temphash)) (mapcar #'(lambda (pn) (persp-get-by-name pn)) names)) (persp-save-state-to-file persp-auto-save-fname temphash persp-auto-save-persps-to-their-file 'yes))) (cl-defun persp-rename (new-name &optional (persp (get-current-persp)) (phash *persp-hash*)) "Change the name field of the `PERSP'. Return old name on success, otherwise nil." (interactive "i") (if persp (let ((opersp (persp-get-by-name new-name phash)) (old-name (safe-persp-name persp))) (unless new-name (setq new-name (read-string (concat "New name for the " old-name " perspective: ") old-name))) (if (and (not (persp-p opersp)) new-name (not (equal old-name new-name))) (progn (when (eq phash *persp-hash*) (persp-remove-from-menu persp)) (remhash old-name phash) (setf (persp-name persp) new-name) (puthash new-name persp phash) (when (eq phash *persp-hash*) (persp-add-to-menu persp) (run-hook-with-args 'persp-renamed-functions persp old-name new-name)) old-name) (message "[persp-mode] Error: There is already a perspective with that name: %s." new-name) nil)) (message "[persp-mode] Error: You can't rename the `nil' perspective, use \ M-x: customize-variable RET persp-nil-name RET") nil)) (cl-defun persp-switch (name &optional frame (window (selected-window)) (called-interactively-p (called-interactively-p 'any))) "Switch to the perspective with name `NAME'. If there is no perspective with that name it will be created. Return `NAME'." (interactive "i") (let ((switch-type 'frame)) (if (or (window-persp-set-p window) (and called-interactively-p current-prefix-arg)) (setq switch-type 'window) (unless frame (setq frame (window-frame window)))) (if (eq 'window switch-type) (persp-window-switch name window) (persp-frame-switch name frame)))) (cl-defun persp-frame-switch (name &optional (frame (selected-frame))) (interactive "i") (unless name (setq name (persp-read-persp "to switch(in frame)" nil nil nil nil t))) (unless (memq frame persp-inhibit-switch-for) (run-hook-with-args 'persp-before-switch-functions name frame) (let ((persp-inhibit-switch-for (cons frame persp-inhibit-switch-for))) (persp-activate (persp-add-new name) frame))) name) (cl-defun persp-window-switch (name &optional (window (selected-window))) (interactive "i") (unless name (setq name (persp-read-persp "to switch(in window)" nil nil nil nil t))) (unless (memq window persp-inhibit-switch-for) (run-hook-with-args 'persp-before-switch-functions name window) (let ((persp-inhibit-switch-for (cons window persp-inhibit-switch-for))) (persp-activate (persp-add-new name) window))) name) (defun persp-before-make-frame () (let ((persp (persp-get-by-name (or (and persp-set-last-persp-for-new-frames persp-last-persp-name) persp-nil-name)))) (unless (persp-p persp) (when persp-set-last-persp-for-new-frames (setq persp-last-persp-name persp-nil-name)) (setq persp (persp-add-new persp-nil-name))) (persp-save-state persp nil t))) (defun persp--do-auto-action-if-needed (persp) (when (and (safe-persp-auto persp) persp-autokill-persp-when-removed-last-buffer (null (safe-persp-buffers persp))) (cond ((functionp persp-autokill-persp-when-removed-last-buffer) (funcall persp-autokill-persp-when-removed-last-buffer persp)) ((or (eq 'hide persp-autokill-persp-when-removed-last-buffer) (and (eq 'hide-auto persp-autokill-persp-when-removed-last-buffer) (safe-persp-auto persp))) (persp-hide (safe-persp-name persp))) ((or (eq t persp-autokill-persp-when-removed-last-buffer) (eq 'kill persp-autokill-persp-when-removed-last-buffer) (and (eq 'kill-auto persp-autokill-persp-when-removed-last-buffer) (safe-persp-auto persp))) (persp-kill (safe-persp-name persp) nil nil))))) (defsubst persp--deactivate (frame-or-window &optional new-persp) (let (persp) (cl-typecase frame-or-window (frame (setq persp (get-frame-persp frame-or-window)) (unless (eq persp new-persp) (with-selected-frame frame-or-window (run-hook-with-args 'persp-before-deactivate-functions 'frame)) (persp-frame-save-state frame-or-window (if persp-set-last-persp-for-new-frames (equal (safe-persp-name persp) persp-last-persp-name) (null persp))))) (window (setq persp (get-window-persp frame-or-window)) (unless (eq persp new-persp) (with-selected-window frame-or-window (run-hook-with-args 'persp-before-deactivate-functions 'window))))) (let ((persp-inhibit-switch-for (cons frame-or-window persp-inhibit-switch-for))) (persp--do-auto-action-if-needed persp)))) (cl-defun persp-activate (persp &optional (frame-or-window (selected-frame)) new-frame-p) (when frame-or-window (let (old-persp type) (cl-typecase frame-or-window (frame (setq old-persp (get-frame-persp frame-or-window) type 'frame)) (window (setq old-persp (get-window-persp frame-or-window) type 'window))) (when (or new-frame-p (not (eq old-persp persp))) (unless new-frame-p (persp--deactivate frame-or-window persp)) (cl-case type (frame (setq persp-last-persp-name (safe-persp-name persp)) (set-frame-persp persp frame-or-window) (when persp-init-frame-behaviour (persp-restore-window-conf frame-or-window persp new-frame-p)) (with-selected-frame frame-or-window (run-hook-with-args 'persp-activated-functions 'frame))) (window (set-window-persp persp frame-or-window) (let ((cbuf (window-buffer frame-or-window))) (unless (persp-contain-buffer-p cbuf persp) (persp-set-another-buffer-for-window cbuf frame-or-window persp))) (with-selected-window frame-or-window (run-hook-with-args 'persp-activated-functions 'window)))))))) (defun persp-init-new-frame (frame) (condition-case-unless-debug err (persp-init-frame frame t (frame-parameter frame 'client)) (error (message "[persp-mode] Error: Can not initialize frame -- %s" err)))) (cl-defun persp-init-frame (frame &optional new-frame-p client) (let ((persp-init-frame-behaviour (cond ((and client (not (eql -1 persp-emacsclient-init-frame-behaviour-override))) persp-emacsclient-init-frame-behaviour-override) ((and (eq this-command 'make-frame) (not (eql -1 persp-interactive-init-frame-behaviour-override))) persp-interactive-init-frame-behaviour-override) ((and new-frame-p (not (eql -1 persp-init-new-frame-behaviour-override))) persp-init-new-frame-behaviour-override) (t persp-init-frame-behaviour)))) (let (persp-name persp) (cl-macrolet ((set-default-persp () `(progn (setq persp-name (or (and persp-set-last-persp-for-new-frames persp-last-persp-name) persp-nil-name) persp (persp-get-by-name persp-name)) (unless (persp-p persp) (setq persp-name persp-nil-name persp (persp-add-new persp-name)))))) (cl-typecase persp-init-frame-behaviour (function (funcall persp-init-frame-behaviour frame new-frame-p)) (string (setq persp-name persp-init-frame-behaviour persp (persp-add-new persp-name))) (symbol (cl-case persp-init-frame-behaviour (auto-temp (setq persp-name (persp-gen-random-name) persp (persp-add-new persp-name)) (when persp (setf (persp-auto persp) t))) (prompt (select-frame frame) (setq persp-name (persp-read-persp "to switch" nil nil nil nil t) persp (persp-add-new persp-name))) (t (set-default-persp)))) (t (set-default-persp)))) (when persp-name (modify-frame-parameters frame `((persp . nil))) (when persp-set-frame-buffer-predicate (persp-set-frame-buffer-predicate frame)) (persp-set-frame-server-switch-hook frame) (when (or (eq persp-init-frame-behaviour 'persp-ignore-wconf) (eq persp-init-frame-behaviour 'persp-ignore-wconf-once)) (set-frame-parameter frame persp-init-frame-behaviour t)) (persp-activate persp frame new-frame-p))))) (defun persp-delete-frame (frame) (condition-case-unless-debug err (persp--deactivate frame persp-not-persp) (error (message "[persp-mode] Error: Can not deactivate frame -- %s" err)))) ;; TODO: rename (cl-defun find-other-frame-with-persp (&optional (persp (get-frame-persp)) (exframe (selected-frame)) for-save) (let ((flist (delq exframe (persp-frames-with-persp persp)))) (cl-find-if #'(lambda (f) (and f (if for-save (and (not (frame-parameter f 'persp-ignore-wconf)) (not (frame-parameter f 'persp-ignore-wconf-once))) t) (eq persp (get-frame-persp f)))) flist))) ;; Helper funcs: (defun persp-add-minor-mode-menu () (easy-menu-define persp-minor-mode-menu persp-mode-map "The menu for the `persp-mode'." '("Perspectives" "-"))) (defun persp-remove-from-menu (persp) (let ((name (safe-persp-name persp))) (cl-psetq persp-names-cache (cl-delete name persp-names-cache :count 1)) (easy-menu-remove-item persp-minor-mode-menu nil name) (when persp (easy-menu-remove-item persp-minor-mode-menu '("kill") name)))) (defun persp-add-to-menu (persp) (let ((name (safe-persp-name persp))) (cl-psetq persp-names-cache (append persp-names-cache (list name))) (let ((str_name name)) (easy-menu-add-item persp-minor-mode-menu nil (vector str_name #'(lambda () (interactive) (persp-switch str_name)))) (when persp (easy-menu-add-item persp-minor-mode-menu '("kill") (vector str_name #'(lambda () (interactive) (persp-kill str_name)))))))) (cl-defun persp-read-persp (&optional action multiple default require-match delnil delcur persp-list show-hidden (default-mode t)) "Read perspective name(s)." (when persp-names-sort-before-read-function (cl-psetq persp-names-cache (funcall persp-names-sort-before-read-function persp-names-cache))) (cl-psetq persp-list (if persp-list (cl-delete-if-not #'(lambda (pn) (member pn persp-list)) (persp-names-current-frame-fast-ordered)) (persp-names-current-frame-fast-ordered))) (when delnil (setq persp-list (cl-delete persp-nil-name persp-list :count 1))) (when delcur (setq persp-list (cl-delete (safe-persp-name (get-current-persp)) persp-list :count 1))) (unless show-hidden (setq persp-list (cl-delete-if #'safe-persp-hidden persp-list :key #'persp-get-by-name))) (when (and default (not (member default persp-list))) (setq default nil)) (let (retlst) (cl-macrolet ((call-pif () `(funcall persp-interactive-completion-function (concat "Perspective name" (and multiple "s") (and action " ") action (if default (concat " (default " default ")") "") (when retlst (concat "< " (mapconcat #'identity retlst " ") " > ")) ": ") persp-list nil require-match nil nil default))) (if multiple (let ((done_str "[>done<]") (not-finished default-mode) exit-minibuffer-function mb-local-key-map (push-keys (alist-get 'push-item persp-read-multiple-keys)) (pop-keys (alist-get 'pop-item persp-read-multiple-keys)) push-keys-backup pop-keys-backup) (while (member done_str persp-list) (setq done_str (concat ">" done_str))) (let ((persp-minibuffer-setup #'(lambda () (setq mb-local-key-map (current-local-map)) (when (keymapp mb-local-key-map) (unless exit-minibuffer-function (setq exit-minibuffer-function (or (lookup-key mb-local-key-map (kbd "RET")) persp-read-multiple-exit-minibuffer-function))) (unless push-keys-backup (setq push-keys-backup (lookup-key mb-local-key-map push-keys))) (define-key mb-local-key-map push-keys #'(lambda () (interactive) (setq not-finished 'push) (funcall exit-minibuffer-function))) (unless pop-keys-backup (setq pop-keys-backup (lookup-key mb-local-key-map pop-keys))) (define-key mb-local-key-map pop-keys #'(lambda () (interactive) (setq not-finished 'pop) (funcall exit-minibuffer-function)))))) cp) (unwind-protect (progn (add-hook 'minibuffer-setup-hook persp-minibuffer-setup t) (while not-finished (setq cp (call-pif)) (cl-case not-finished (push (when (and cp (member cp persp-list)) (if retlst (when (string= cp done_str) (setq not-finished nil)) (push done_str persp-list)) (when not-finished (if (eq 'reverse multiple) (setq retlst (append retlst (list cp))) (push cp retlst)) (setq persp-list (cl-delete cp persp-list :count 1) default done_str))) (when not-finished (setq not-finished default-mode))) (pop (let ((last-item (pop retlst))) (unless retlst (setq persp-list (cl-delete done_str persp-list :count 1) default nil)) (when last-item (push last-item persp-list))) (setq not-finished default-mode)) (t (when (and cp (not (string= cp done_str)) (member cp persp-list)) (push cp retlst)) (setq not-finished nil))))) (remove-hook 'minibuffer-setup-hook persp-minibuffer-setup) (when (keymapp mb-local-key-map) (when (lookup-key mb-local-key-map push-keys) (define-key mb-local-key-map push-keys push-keys-backup)) (when (lookup-key mb-local-key-map pop-keys) (define-key mb-local-key-map pop-keys pop-keys-backup))))) retlst) (call-pif))))) (define-obsolete-function-alias 'persp-prompt 'persp-read-persp "persp-mode 2.9") (defsubst persp--set-frame-buffer-predicate-buffer-list-cache (buflist) (prog1 (setq persp-frame-buffer-predicate-buffer-list-cache buflist) (unless persp-frame-buffer-predicate-buffer-list-cache (setq persp-frame-buffer-predicate-buffer-list-cache :nil)) (run-at-time 2 nil #'(lambda () (setq persp-frame-buffer-predicate-buffer-list-cache nil))))) (defmacro persp--get-frame-buffer-predicate-buffer-list-cache (buflist) `(if persp-frame-buffer-predicate-buffer-list-cache (if (eq :nil persp-frame-buffer-predicate-buffer-list-cache) nil persp-frame-buffer-predicate-buffer-list-cache) (persp--set-frame-buffer-predicate-buffer-list-cache ,buflist))) (defun persp-generate-frame-buffer-predicate (opt) (if opt (eval `(lambda (b) (if (string-prefix-p " *temp*" (buffer-name (current-buffer))) t ,(cl-typecase opt (function `(funcall (with-no-warnings ',opt) b)) (number `(let ((*persp-restrict-buffers-to* ,opt)) (memq b (persp--get-frame-buffer-predicate-buffer-list-cache (let ((ret (persp-buffer-list-restricted (selected-frame) ,opt persp-restrict-buffers-to-if-foreign-buffer t))) (if (get-current-persp) ret (cl-delete-if #'persp-buffer-filtered-out-p ret))))))) (symbol (cl-case opt ('nil t) (restricted-buffer-list '(progn (memq b (persp--get-frame-buffer-predicate-buffer-list-cache (let ((ret (persp-buffer-list-restricted (selected-frame) *persp-restrict-buffers-to* persp-restrict-buffers-to-if-foreign-buffer t))) (if (get-current-persp) ret (cl-delete-if #'persp-buffer-filtered-out-p ret))))))) (t '(memq b (persp--get-frame-buffer-predicate-buffer-list-cache (let ((ret (safe-persp-buffers (get-current-persp)))) (if (get-current-persp) ret (cl-delete-if #'persp-buffer-filtered-out-p ret)))))))) (t t))))) nil)) (defun persp-set-frame-buffer-predicate (frame &optional off) (let ((old-pred (frame-parameter frame 'persp-buffer-predicate-old)) (cur-pred (frame-parameter frame 'buffer-predicate)) (last-persp-pred (frame-parameter frame 'persp-buffer-predicate-generated))) (let (new-pred) (if off (progn (set-frame-parameter frame 'persp-buffer-predicate-old nil) (set-frame-parameter frame 'persp-buffer-predicate-generated nil) (setq new-pred (if (eq cur-pred last-persp-pred) old-pred cur-pred)) (set-frame-parameter frame 'buffer-predicate new-pred)) (unless persp-frame-buffer-predicate (setq persp-frame-buffer-predicate (persp-generate-frame-buffer-predicate persp-set-frame-buffer-predicate))) (if persp-frame-buffer-predicate (progn (set-frame-parameter frame 'persp-buffer-predicate-old (if (eq cur-pred last-persp-pred) old-pred (setq old-pred cur-pred))) (setq new-pred (cl-case old-pred ('nil persp-frame-buffer-predicate) (t `(lambda (b) (and (funcall (with-no-warnings ',persp-frame-buffer-predicate) b) (funcall (with-no-warnings ',old-pred) b)))))) (unless (symbolp new-pred) (setq new-pred (with-no-warnings (let ((warning-minimum-level :emergency) byte-compile-warnings) (byte-compile new-pred))))) (set-frame-parameter frame 'persp-buffer-predicate-generated new-pred) (set-frame-parameter frame 'buffer-predicate new-pred)) (persp-set-frame-buffer-predicate frame t)))))) (defun persp-update-frames-buffer-predicate (&optional off) (unless off (setq persp-frame-buffer-predicate nil) (persp-update-frames-buffer-predicate t)) (mapc #'(lambda (f) (persp-set-frame-buffer-predicate f off)) (persp-frame-list-without-daemon))) (defun persp-generate-frame-server-switch-hook (opt) (if opt (eval `(lambda (frame) ,(if (functionp opt) `(funcall (with-no-warnings ',opt) frame) `(let* ((frame-client (frame-parameter frame 'client)) (frame-client-bl (when (processp frame-client) (process-get frame-client 'buffers)))) ,(cl-case opt (only-file-windows `(if frame-client (when frame-client-bl (mapc #'(lambda (w) (unless (memq (window-buffer w) frame-client-bl) (delete-window w))) (window-list frame 'no-minibuf))) (let (frame-server-bl) (mapc #'(lambda (proc) (setq frame-server-bl (append frame-server-bl (process-get proc 'buffers)))) (server-clients-with 'frame nil)) (when frame-server-bl (mapc #'(lambda (w) (unless (memq (window-buffer w) frame-server-bl) (delete-window w))) (window-list frame 'no-minibuf)))))) (only-file-windows-for-client-frame `(when frame-client-bl (mapc #'(lambda (w) (unless (memq (window-buffer w) frame-client-bl) (delete-window w))) (window-list frame 'no-minibuf)))) (t nil)))))) nil)) (defun persp-set-frame-server-switch-hook (frame) (when (frame-parameter frame 'client) (set-frame-parameter frame 'persp-server-switch-hook persp-frame-server-switch-hook))) (defun persp-update-frame-server-switch-hook () (setq persp-frame-server-switch-hook (persp-generate-frame-server-switch-hook persp-server-switch-behaviour)) (mapc #'persp-set-frame-server-switch-hook (persp-frame-list-without-daemon))) (defun persp-ido-setup () (when (eq ido-cur-item 'buffer) (setq persp-disable-buffer-restriction-once nil))) (defun persp-restrict-ido-buffers () "Support for the `ido-mode'." (let ((buffer-names-sorted (if persp-disable-buffer-restriction-once (mapcar #'buffer-name (persp-buffer-list-restricted nil -1 nil)) (mapcar #'buffer-name (persp-buffer-list-restricted)))) (indices (make-hash-table))) (let ((i 0)) (dolist (elt ido-temp-list) (puthash elt i indices) (setq i (1+ i)))) (setq ido-temp-list (sort buffer-names-sorted #'(lambda (a b) (< (gethash a indices 10000) (gethash b indices 10000))))))) ;; TODO: rename (defun ido-toggle-persp-filter () (interactive) (setq persp-disable-buffer-restriction-once (not persp-disable-buffer-restriction-once) ido-text-init ido-text ido-exit 'refresh) (exit-minibuffer)) (cl-defun persp-read-buffer (prompt &optional default require-match predicate multiple (default-mode t)) "Read buffers with restriction." (setq persp-disable-buffer-restriction-once nil) (when default (unless (stringp default) (if (and (bufferp default) (buffer-live-p default)) (setq default (buffer-name default)) (setq default nil)))) (if prompt (setq prompt (car (split-string prompt ": *$" t))) (setq prompt "Please provide a buffer name: ")) (let* ((buffer-names (mapcar #'buffer-name (persp-buffer-list-restricted))) cp retlst (done_str "[>done<]") (not-finished default-mode) (push-keys (alist-get 'push-item persp-read-multiple-keys)) (pop-keys (alist-get 'pop-item persp-read-multiple-keys)) push-keys-backup pop-keys-backup (toggle-filter-keys (alist-get 'toggle-persp-buffer-filter persp-read-multiple-keys)) toggle-filter-keys-backup exit-minibuffer-function mb-local-key-map (persp-minibuffer-setup #'(lambda () (setq mb-local-key-map (current-local-map)) (when (keymapp mb-local-key-map) (unless exit-minibuffer-function (setq exit-minibuffer-function (or (lookup-key mb-local-key-map (kbd "RET")) persp-read-multiple-exit-minibuffer-function))) (unless toggle-filter-keys-backup (setq toggle-filter-keys-backup (lookup-key mb-local-key-map toggle-filter-keys))) (define-key mb-local-key-map toggle-filter-keys #'(lambda () (interactive) (setq not-finished 'toggle-filter) (funcall exit-minibuffer-function)))))) (persp-multiple-minibuffer-setup #'(lambda () (when (keymapp mb-local-key-map) (unless push-keys-backup (setq push-keys-backup (lookup-key mb-local-key-map push-keys))) (define-key mb-local-key-map push-keys #'(lambda () (interactive) (setq not-finished 'push) (funcall exit-minibuffer-function))) (unless pop-keys-backup (setq pop-keys-backup (lookup-key mb-local-key-map pop-keys))) (define-key mb-local-key-map pop-keys #'(lambda () (interactive) (setq not-finished 'pop) (funcall exit-minibuffer-function))))))) (while (member done_str buffer-names) (setq done_str (concat ">" done_str))) (unwind-protect (progn (when (and default (not (member default buffer-names))) (push default buffer-names) ;; TODO: remove this ;; (setq default nil) ) (when multiple (add-hook 'minibuffer-setup-hook persp-multiple-minibuffer-setup)) (add-hook 'minibuffer-setup-hook persp-minibuffer-setup) (while not-finished (setq cp (funcall persp-interactive-completion-function (concat prompt (and default (concat "(default " default ")")) (and retlst (concat "< " (mapconcat #'identity retlst " ") " >")) ": ") buffer-names predicate require-match nil nil default)) (cl-case not-finished (push (when (and cp (member cp buffer-names)) (if retlst (when (string= cp done_str) (setq not-finished nil)) (push done_str buffer-names)) (when not-finished (if (eq 'reverse multiple) (setq retlst (append retlst (list cp))) (push cp retlst)) (setq buffer-names (cl-delete cp buffer-names :count 1) default done_str))) (when not-finished (setq not-finished default-mode))) (pop (let ((last-item (pop retlst))) (unless retlst (setq buffer-names (cl-delete done_str buffer-names :count 1) default nil)) (when last-item (push last-item buffer-names))) (setq not-finished default-mode)) (toggle-filter (setq persp-disable-buffer-restriction-once (not persp-disable-buffer-restriction-once)) (setq buffer-names (cl-delete-if #'(lambda (bn) (member bn retlst)) (mapcar #'buffer-name (if persp-disable-buffer-restriction-once (funcall persp-buffer-list-function) (cl-delete-if #'persp-buffer-filtered-out-p (persp-buffer-list-restricted)))))) (setq not-finished default-mode)) (t (when (and cp (not (string= cp done_str)) (member cp buffer-names)) (push cp retlst)) (setq not-finished nil)))) (if multiple retlst (car retlst))) (remove-hook 'minibuffer-setup-hook persp-multiple-minibuffer-setup) (remove-hook 'minibuffer-setup-hook persp-minibuffer-setup) (when (keymapp mb-local-key-map) (when multiple (when (lookup-key mb-local-key-map push-keys) (define-key mb-local-key-map push-keys push-keys-backup)) (when (lookup-key mb-local-key-map pop-keys) (define-key mb-local-key-map pop-keys pop-keys-backup))) (when (lookup-key mb-local-key-map toggle-filter-keys) (define-key mb-local-key-map toggle-filter-keys toggle-filter-keys-backup))) (setq persp-disable-buffer-restriction-once nil)))) ;; Save/Load funcs: (defun persp-delete-other-windows () (let ((win (selected-window))) (when (window-parameter win 'window-side) (setq win (cl-loop for win in (window-list nil 1) unless (window-parameter win 'window-side) return win))) (when win (let ((ignore-window-parameters t)) (delete-other-windows win))))) (cl-defun persp-restore-window-conf (&optional (frame (selected-frame)) (persp (get-frame-persp frame)) new-frame-p) (when new-frame-p (sit-for 0.01)) (unless (run-hook-with-args-until-success 'persp-restore-window-conf-filter-functions frame persp new-frame-p) (with-selected-frame frame (let ((pwc (safe-persp-window-conf persp)) (split-width-threshold 2) (split-height-threshold 2) (window-safe-min-height 1) (window-safe-min-width 1) (window-min-height 1) (window-min-width 1) (window-resize-pixelwise t) (gr-mode (and (boundp 'golden-ratio-mode) golden-ratio-mode))) (when gr-mode (golden-ratio-mode -1)) (unwind-protect (cond ((functionp persp-restore-window-conf-method) (funcall persp-restore-window-conf-method frame persp new-frame-p)) ((null persp-restore-window-conf-method) nil) (t (if pwc (progn (persp-delete-other-windows) (set-window-dedicated-p nil nil) (condition-case-unless-debug err (funcall persp-window-state-put-function pwc frame) (error (message "[persp-mode] Warning: Can not restore the window \ configuration, because of the error -- %s" err) (let* ((cw (selected-window)) (cwb (window-buffer cw))) (unless (persp-contain-buffer-p cwb persp) (persp-set-another-buffer-for-window cwb cw persp))))) (when (and new-frame-p persp-is-ibc-as-f-supported) (setq initial-buffer-choice #'(lambda () persp-special-last-buffer)))) (when persp-reset-windows-on-nil-window-conf (if (functionp persp-reset-windows-on-nil-window-conf) (funcall persp-reset-windows-on-nil-window-conf) (persp-delete-other-windows) (set-window-dedicated-p nil nil) (let* ((pbs (safe-persp-buffers persp)) (w (selected-window)) (wb (window-buffer w))) (when (and pbs (not (memq wb pbs))) (persp-set-another-buffer-for-window wb w persp)))))))) (when gr-mode (golden-ratio-mode 1))))))) ;; Save funcs (cl-defun persp-frame-save-state (&optional (frame (selected-frame)) set-persp-special-last-buffer) (when (and (frame-live-p frame) (not (persp-is-frame-daemons-frame frame)) (not (frame-parameter frame 'persp-ignore-wconf)) (not (frame-parameter frame 'persp-ignore-wconf-once))) (let ((persp (get-frame-persp frame))) (with-selected-frame frame (when set-persp-special-last-buffer (persp-special-last-buffer-make-current)) (if persp (setf (persp-window-conf persp) (funcall persp-window-state-get-function frame)) (setq persp-nil-wconf (funcall persp-window-state-get-function frame))))))) (cl-defun persp-save-state (&optional (persp (get-frame-persp)) exfr set-persp-special-last-buffer) (let ((frame (selected-frame))) (when (eq frame exfr) (setq frame nil)) (unless (and frame (eq persp (get-frame-persp frame))) (setq frame (find-other-frame-with-persp persp exfr t))) (when frame (persp-frame-save-state frame set-persp-special-last-buffer)))) (defun persp-buffers-to-savelist (persp) (cl-delete-if #'symbolp (let (find-ret) (mapcar #'(lambda (b) (setq find-ret nil) (cl-find-if #'(lambda (sl) (when sl (setq find-ret sl))) persp-save-buffer-functions :key #'(lambda (s-f) (with-current-buffer b (funcall s-f b)))) find-ret) (if persp (persp-buffers persp) (cl-delete-if-not #'persp-buffer-free-p (funcall persp-buffer-list-function))))))) (defun persp-window-conf-to-savelist (persp) `(def-wconf ,(if (or persp-use-workgroups (not (version< emacs-version "24.4"))) (safe-persp-window-conf persp) nil))) (defun persp-elisp-object-readable-p (obj) (let (print-length print-level) (or (stringp obj) (not (string-match-p "#<.*?>" (prin1-to-string obj)))))) (defun persp-parameters-to-savelist (persp) `(def-params ,(cl-remove-if #'(lambda (param) (and (not (persp-elisp-object-readable-p param)) (message "[persp-mode] Info: The parameter %S \ of the perspective %s can't be saved." param (safe-persp-name persp)) t)) (safe-persp-parameters persp)))) (defun persp-to-savelist (persp) `(def-persp ,(and persp (persp-name persp)) ,(persp-buffers-to-savelist persp) ,(persp-window-conf-to-savelist persp) ,(persp-parameters-to-savelist persp) ,(safe-persp-weak persp) ,(safe-persp-auto persp) ,(safe-persp-hidden persp))) (defun persps-to-savelist (&optional phash names-regexp) (mapcar #'persp-to-savelist (cl-delete-if (apply-partially #'persp-parameter 'dont-save-to-file) (if (eq phash *persp-hash*) (mapcar #'(lambda (pn) (when (or (not names-regexp) (persp-string-match-p names-regexp pn)) (persp-get-by-name pn *persp-hash* nil))) (persp-names-current-frame-fast-ordered)) (persp-persps (or phash *persp-hash*) names-regexp t))))) (defsubst persp-save-with-backups (fname) (when (and (string= fname (concat (expand-file-name persp-save-dir) persp-auto-save-fname)) (> persp-auto-save-num-of-backups 0)) (cl-do ((cur persp-auto-save-num-of-backups (1- cur)) (prev (1- persp-auto-save-num-of-backups) (1- prev))) ((> 1 cur) nil) (let ((cf (concat fname (number-to-string cur))) (pf (concat fname (if (> prev 0) (number-to-string prev) "")))) (when (file-exists-p pf) (when (file-exists-p cf) (delete-file cf)) (rename-file pf cf t)))) (when (file-exists-p fname) (rename-file fname (concat fname (number-to-string 1)) t))) (write-file fname nil) t) (cl-defun persp-save-state-to-file (&optional (fname persp-auto-save-fname) (phash *persp-hash*) (respect-persp-file-parameter persp-auto-save-persps-to-their-file) (keep-others-in-non-parametric-file 'no)) (interactive (list (read-file-name "Save perspectives to a file: " persp-save-dir ""))) (when (and (stringp fname) phash) (when (< (string-width (file-name-nondirectory fname)) 1) (message "[persp-mode] Error: You must provide nonempty filename to save perspectives.") (cl-return-from persp-save-state-to-file nil)) (let* ((p-save-dir (or (file-name-directory fname) (expand-file-name persp-save-dir))) (p-save-file (concat p-save-dir (file-name-nondirectory fname)))) (unless (and (file-exists-p p-save-dir) (file-directory-p p-save-dir)) (message "[persp-mode] Info: Trying to create the `persp-conf-dir'.") (make-directory p-save-dir t)) (if (not (and (file-exists-p p-save-dir) (file-directory-p p-save-dir))) (progn (message "[persp-mode] Error: Can't save perspectives -- \ `persp-save-dir' does not exists or not a directory %S." p-save-dir) nil) (mapc #'persp-save-state (persp-persps phash)) (run-hook-with-args 'persp-before-save-state-to-file-functions fname phash respect-persp-file-parameter) (if (and respect-persp-file-parameter (cl-member-if (apply-partially #'persp-parameter 'persp-file) (persp-persps phash nil))) (let (persp-auto-save-persps-to-their-file persp-before-save-state-to-file-functions) (mapc #'(lambda (gr) (cl-destructuring-bind (pfname . pl) gr (let ((names (mapcar #'safe-persp-name pl))) (if pfname (persp-save-to-file-by-names pfname phash names 'yes nil) (persp-save-to-file-by-names p-save-file phash names keep-others-in-non-parametric-file nil))))) (persp-group-by (apply-partially #'persp-parameter 'persp-file) (persp-persps phash nil t) t))) (with-temp-buffer (buffer-disable-undo) (erase-buffer) (goto-char (point-min)) (insert ";; -*- mode: emacs-lisp; eval: (progn (pp-buffer) (indent-buffer)) -*-") (newline) (insert (let (print-length print-level) (pp-to-string (persps-to-savelist phash)))) (persp-save-with-backups p-save-file))))))) (cl-defun persp-save-to-file-by-names (&optional (fname persp-auto-save-fname) (phash *persp-hash*) names keep-others (called-interactively-p (called-interactively-p 'any))) (interactive) (unless names (setq names (persp-read-persp "to save" 'reverse (safe-persp-name (get-current-persp)) t nil nil nil nil 'push))) (when (or (not fname) called-interactively-p) (setq fname (read-file-name (format "Save a subset of perspectives%s to a file: " names) persp-save-dir))) (when names (unless keep-others (setq keep-others (if (and (file-exists-p fname) (yes-or-no-p "Keep other perspectives in the file?")) 'yes 'no))) (let ((temphash (make-hash-table :test 'equal :size 10)) (persp-nil-wconf persp-nil-wconf) (persp-nil-parameters (copy-tree persp-nil-parameters)) (persp-nil-hidden persp-nil-hidden) bufferlist-diff) (when (or (eq keep-others 'yes) (eq keep-others t)) (let ((bufferlist-pre (mapcar #'(lambda (b) (cons b (persp--buffer-in-persps b))) (funcall persp-buffer-list-function)))) (persp-load-state-from-file fname temphash (cons :not (regexp-opt names))) (setq bufferlist-diff (cl-delete-if #'(lambda (bcons) (when bcons (cl-destructuring-bind (buf . buf-persps) bcons (when buf (persp--buffer-in-persps-set buf buf-persps) t)))) (funcall persp-buffer-list-function) :key #'(lambda (b) (assq b bufferlist-pre)))))) (mapc #'(lambda (p) (persp-add p temphash) (when (and p persp-auto-save-persps-to-their-file) (set-persp-parameter 'persp-file fname p))) (mapcar #'(lambda (pn) (persp-get-by-name pn phash)) names)) (persp-save-state-to-file fname temphash nil) (mapc #'kill-buffer bufferlist-diff)))) (defun persp-tramp-save-buffer (b) (let* ((buf-f-name (buffer-file-name b)) (persp-tramp-file-name (when (and (or (featurep 'tramp) (require 'tramp nil t)) (tramp-tramp-file-p buf-f-name)) (let ((dissected-f-name (tramp-dissect-file-name buf-f-name)) tmh) (if (tramp-file-name-hop dissected-f-name) (when (and (or (featurep 'tramp-sh) (require 'tramp-sh nil t)) (fboundp 'tramp-compute-multi-hops) (setq tmh (condition-case-unless-debug err (tramp-compute-multi-hops dissected-f-name) (error nil)))) (let ((persp-tramp-file-name tramp-prefix-format)) (while tmh (let* ((hop (car tmh)) (method (tramp-file-name-method hop)) (user (tramp-file-name-user hop)) (host (tramp-file-name-host hop)) (filename (tramp-file-name-localname hop))) (setq persp-tramp-file-name (concat persp-tramp-file-name method tramp-postfix-method-format user (when user tramp-postfix-user-format) host (if (= (string-width filename) 0) tramp-postfix-hop-format (concat tramp-postfix-host-format filename))) tmh (cdr tmh)))) persp-tramp-file-name)) buf-f-name))))) (when persp-tramp-file-name `(def-buffer ,(buffer-name b) ,persp-tramp-file-name ,(buffer-local-value 'major-mode b))))) ;; Load funcs (defun persp-update-frames-window-confs (&optional persp-names) (mapc #'persp-restore-window-conf (if persp-names (cl-delete-if-not #'(lambda (pn) (member pn persp-names)) (persp-frame-list-without-daemon) :key #'(lambda (f) (safe-persp-name (get-frame-persp f)))) (persp-frame-list-without-daemon)))) (defmacro persp-car-as-fun-cdr-as-args (lst) (let ((kar (gensym "lst-car"))) `(let* ((,kar (car-safe ,lst)) (args (cdr-safe ,lst)) (fun (or (condition-case-unless-debug err (symbol-function ,kar) (error nil)) (symbol-value ,kar)))) (if (functionp fun) (apply fun args) (message "[persp-mode] Error: %s is not a function." fun))))) (defvar def-buffer nil) (defun persp-buffer-from-savelist (savelist) (when (eq (car savelist) 'def-buffer) (let (persp-add-buffer-on-find-file (def-buffer #'(lambda (name fname mode &optional parameters) (let ((buf (persp-get-buffer-or-null name))) (if buf (if (or (null fname) (string= fname (buffer-file-name buf))) buf (if (file-exists-p fname) (setq buf (find-file-noselect fname)) (message "[persp-mode] Warning: The file %s no longer exists." fname) (setq buf nil))) (if (and fname (file-exists-p fname)) (setq buf (find-file-noselect fname)) (when fname (message "[persp-mode] Warning: The file %s no longer exists." fname)) (setq buf (get-buffer-create name)))) (when (buffer-live-p buf) (cl-macrolet ((restorevars () `(mapc #'(lambda (varcons) (cl-destructuring-bind (vname . vvalue) varcons (unless (or (eq vname 'buffer-file-name) (eq vname 'major-mode)) (set (make-local-variable vname) vvalue)))) (alist-get 'local-vars parameters)))) (with-current-buffer buf (restorevars) (cond ((and (boundp 'persp-load-buffer-mode-restore-function) (variable-binding-locus 'persp-load-buffer-mode-restore-function) (functionp persp-load-buffer-mode-restore-function)) (funcall persp-load-buffer-mode-restore-function mode) (restorevars)) ((functionp mode) (when (and (not (eq major-mode mode)) (not (eq major-mode 'not-loaded-yet))) (funcall mode) (restorevars))))))) buf)))) (persp-car-as-fun-cdr-as-args savelist)))) (defun persp-buffers-from-savelist-0 (savelist) (cl-delete-if-not #'persp-get-buffer-or-null (let (find-ret) (mapcar #'(lambda (saved-buf) (setq find-ret nil) (cl-find-if #'(lambda (lb) (when lb (setq find-ret lb))) persp-load-buffer-functions :key #'(lambda (l-f) (funcall l-f saved-buf))) find-ret) savelist)))) (defvar def-wconf nil) (defun persp-window-conf-from-savelist-0 (savelist) (let ((def-wconf #'identity)) (persp-car-as-fun-cdr-as-args savelist))) (defvar def-params nil) (defun persp-parameters-from-savelist-0 (savelist) (let ((def-params #'identity)) (persp-car-as-fun-cdr-as-args savelist))) (defvar def-persp nil) (defun persp-from-savelist-0 (savelist phash persp-file) (let ((def-persp #'(lambda (name dbufs dwc &optional dparams weak auto hidden) (let* ((pname (or name persp-nil-name)) (persp (persp-add-new pname phash))) (mapc #'(lambda (b) (persp-add-buffer b persp nil nil)) (persp-buffers-from-savelist-0 dbufs)) (if persp (setf (persp-window-conf persp) (persp-window-conf-from-savelist-0 dwc)) (setq persp-nil-wconf (persp-window-conf-from-savelist-0 dwc))) (modify-persp-parameters (persp-parameters-from-savelist-0 dparams) persp) (when persp (setf (persp-weak persp) weak (persp-auto persp) auto)) (if persp (setf (persp-hidden persp) hidden) (setq persp-nil-hidden hidden)) (when persp-file (set-persp-parameter 'persp-file persp-file persp)) pname)))) (persp-car-as-fun-cdr-as-args savelist))) (defun persps-from-savelist-0 (savelist phash persp-file set-persp-file names-regexp) (when (and names-regexp (not (consp names-regexp))) (setq names-regexp (cons t names-regexp))) (mapcar #'(lambda (pd) (persp-from-savelist-0 pd phash (and set-persp-file persp-file))) (if names-regexp (cl-delete-if-not (apply-partially #'persp-string-match-p names-regexp) savelist :key #'(lambda (pd) (or (cadr pd) persp-nil-name))) savelist))) (defun persp-names-from-savelist-0 (savelist) (mapcar #'(lambda (pd) (or (cadr pd) persp-nil-name)) savelist)) (defun persps-savelist-version-string (savelist) (let* ((version-list (car savelist)) (version (or (and (eq (car version-list) 'def-persp-save-format-version) (cadr version-list)) 0))) (list (format "%S" version) (if (eql version 0) savelist (cdr savelist))))) (defun persp-dispatch-loadf-version (funsym savelist) (cl-destructuring-bind (version s-list) (persps-savelist-version-string savelist) (let ((funame (intern (concat (symbol-name funsym) "-" version)))) (if (fboundp funame) (list funame s-list) (message "[persp-mode] Warning: Can not find load function for this version: %S." version) (list nil s-list))))) (defun persps-from-savelist (savelist phash persp-file set-persp-file names-regexp) (cl-destructuring-bind (fun s-list) (persp-dispatch-loadf-version 'persps-from-savelist savelist) (if fun (let ((persp-names (funcall fun s-list phash persp-file set-persp-file names-regexp))) (run-hook-with-args 'persp-after-load-state-functions persp-file phash persp-names) persp-names) (message "[persp-mode] Error: Can not load perspectives from savelist: %s \tloaded from %s" savelist persp-file) nil))) (defun persp-list-persp-names-in-file (fname) (when (and fname (file-exists-p fname)) (let* ((pslist (with-temp-buffer (buffer-disable-undo) (insert-file-contents fname nil nil nil t) (goto-char (point-min)) (read (current-buffer))))) (cl-destructuring-bind (fun s-list) (persp-dispatch-loadf-version 'persp-names-from-savelist pslist) (if fun (funcall fun s-list) (message "[persp-mode] Error: Can not list perspective names in file %S." fname)))))) (cl-defun persp-load-state-from-file (&optional (fname persp-auto-save-fname) (phash *persp-hash*) names-regexp set-persp-file) (interactive (list (read-file-name "Load perspectives from a file: " persp-save-dir))) (when fname (let ((p-save-file (concat (or (file-name-directory fname) (expand-file-name persp-save-dir)) (file-name-nondirectory fname)))) (if (not (file-exists-p p-save-file)) (progn (message "[persp-mode] Error: No such file -- %S." p-save-file) nil) (let ((readed-list (with-temp-buffer (buffer-disable-undo) (insert-file-contents p-save-file nil nil nil t) (goto-char (point-min)) (read (current-buffer))))) (persps-from-savelist readed-list phash p-save-file set-persp-file names-regexp)))))) (cl-defun persp-load-from-file-by-names (&optional (fname persp-auto-save-fname) (phash *persp-hash*) names) (interactive (list (read-file-name "Load a subset of perspectives from a file: " persp-save-dir))) (unless names (let* ((p-save-file (concat (or (file-name-directory fname) (expand-file-name persp-save-dir)) (file-name-nondirectory fname))) (available-names (persp-list-persp-names-in-file p-save-file))) (setq names (persp-read-persp "to load" 'reverse nil t nil nil available-names nil 'push)))) (when names (let ((names-regexp (regexp-opt names))) (persp-load-state-from-file fname phash names-regexp t)))) (provide 'persp-mode) ;;; persp-mode.el ends here