emacs/org/elpa/persp-mode-20230110.1045/persp-mode.el

4176 lines
169 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; persp-mode.el --- windows/buffers sets shared among frames + save/load. -*- lexical-binding: t; -*-
;; Copyright (C) 2012 Constantin Kulikov
;; Author: Constantin Kulikov (Bad_ptr) <zxnotdead@gmail.com>
;; Version: 3.0.7
;; Package-Version: 20230110.1045
;; Package-Commit: df95ea710e2a72f7a88293b72137acb0ca024d90
;; 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-<return>"))
(pop-item . ,(kbd "M-<return>")))
"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 (eval default-load-fun t) 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 " ") " >"))
(and persp-toggle-read-buffer-filter-keys
(concat
" [`"
(help-key-description
persp-toggle-read-buffer-filter-keys
nil)
"' toggles filter]"))
": ")
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 (or (window-parameter win 'window-side)
(window-minibuffer-p win))
(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))
(condition-case-unless-debug err
(delete-other-windows win)
(error
(message "[persp-mode] Warning: Can not delete-other-windows -- %S" err)))))))
(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-method 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))
(port (tramp-file-name-port 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
(when port tramp-prefix-port-format)
port
(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
buf
(def-buffer
#'(lambda (bname fname mode &optional parameters)
(setq buf (persp-get-buffer-or-null bname))
(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))
(with-current-buffer (setq buf (find-file-noselect fname))
(unless (string= bname (buffer-name buf))
(rename-buffer bname t)))
(when fname
(message
"[persp-mode] Warning: The file %S no longer exists."
fname))
(setq buf (get-buffer-create bname))))
(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)))
(condition-case-unless-debug err
(persp-car-as-fun-cdr-as-args savelist)
(error
(message "[persp-mode] Error details: %S" savelist)
(message "[persp-mode] Error: persp-buffer-from-savelist failed to restore a buffer -- %S" err)
buf)))))
(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)
(condition-case-unless-debug err
(funcall l-f saved-buf)
(error
(message "[persp-mode] Error details: %S" saved-buf)
(message "[persp-mode] Error: Failed to resume buffer using %S load buffer function -- %S" l-f err)
nil))))
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))
(condition-case-unless-debug err
(persp-buffers-from-savelist-0 dbufs)
(error
(message "[persp-mode] Error details: %S" dbufs)
(message "[persp-mode] Error: failed to load buffers for %S perspective from %S file -- %S" pname persp-file err)
nil)))
(let ((loaded-wconf
(condition-case-unless-debug err
(persp-window-conf-from-savelist-0 dwc)
(error
(message "[persp-mode] Error details: %S" dwc)
(message "[persp-mode] Error: failed to load window configuration for %S perspective from %S file -- %S" pname persp-file err)
nil))))
(if (and persp loaded-wconf)
(setf (persp-window-conf persp) loaded-wconf)
(setq persp-nil-wconf loaded-wconf)))
(modify-persp-parameters
(condition-case-unless-debug err
(persp-parameters-from-savelist-0 dparams)
(error
(message "[persp-mode] Error details: %S" dparams)
(message "[persp-mode] Error: Failed to load %S perspective parameters from %S file -- %S" pname persp-file err)
nil))
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)))
(delq nil
(mapcar #'(lambda (pd)
(condition-case-unless-debug err
(persp-from-savelist-0 pd phash (and set-persp-file persp-file))
(error
(message "[persp-mode] Error details: %S" pd)
(message "[persp-mode] Error: Can not load a perspective from %S file -- %S" persp-file err)
nil)))
(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)
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; persp-mode.el ends here