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

4118 lines
166 KiB
EmacsLisp
Raw Normal View History

;;; 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.3
2022-08-04 18:39:15 +00:00
;; Package-Version: 20220507.32
;; Package-Commit: d0f8eb099e852b6073d4ec06d32587c60a7dce3f
;; 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)))
2022-08-04 18:39:15 +00:00
(when sortp (setq pblf-body `(sort ,pblf-body (with-no-warnings ,sortp))))
`(let ((*persp-restrict-buffers-to* ,restriction)
(persp-restrict-buffers-to-if-foreign-buffer
,restriction-foreign-override)
,@(if cache `(persp-buffer-list-cache) nil))
(cl-letf (((symbol-function 'buffer-list)
#'(lambda (&optional frame)
,(if cache
`(if persp-buffer-list-cache
persp-buffer-list-cache
(setq persp-buffer-list-cache ,pblf-body))
pblf-body))))
,@body))))
(cl-defmacro with-persp-read-buffer ((&key multiple (default-mode t)) &rest body)
`(let ((read-buffer-function #'persp-read-buffer))
,@body))
(defmacro with-persp-ido-hooks (&rest body)
`(let ((ido-make-buffer-list-hook ido-make-buffer-list-hook)
(ido-setup-hook ido-setup-hook))
(persp-set-ido-hooks t)
,@body))
;; TODO: rename
(defun safe-persp-name (p)
(if p (persp-name p)
persp-nil-name))
;; TODO: rename
(defun safe-persp-buffers (p)
(if p (persp-buffers p)
(funcall persp-buffer-list-function)))
;; TODO: rename
(defun safe-persp-window-conf (p)
(if p (persp-window-conf p)
persp-nil-wconf))
;; TODO: rename
(defun safe-persp-parameters (p)
(if p (persp-parameters p)
persp-nil-parameters))
;; TODO: rename
(defun safe-persp-weak (p)
(if p (persp-weak p)
t))
;; TODO: rename
(defun safe-persp-auto (p)
(if p (persp-auto p)
nil))
;; TODO: rename
(defun safe-persp-hidden (p)
(if p (persp-hidden p)
persp-nil-hidden))
;; TODO: rename
(cl-defun modify-persp-parameters (alist &optional (persp (get-current-persp)))
(cl-loop for (name . value) in alist
do (set-persp-parameter name value persp)))
;; TODO: rename
(cl-defun set-persp-parameter
(param-name &optional value (persp (get-current-persp)))
(let* ((params (safe-persp-parameters persp))
(old-cons (assq param-name params)))
(if old-cons
(setcdr old-cons value)
(if persp
(setf (persp-parameters persp)
(push (cons param-name value) params))
(setq persp-nil-parameters
(push (cons param-name value) params))))))
(cl-defun persp-parameter (param-name &optional (persp (get-current-persp)))
(alist-get param-name (safe-persp-parameters persp)))
;; TODO: rename
(cl-defun delete-persp-parameter (param-name &optional (persp (get-current-persp)))
(when (and (not (null param-name)) (symbolp param-name))
(if persp
(setf (persp-parameters persp)
(delq (assq param-name (persp-parameters persp))
(persp-parameters persp)))
(setq persp-nil-parameters
(delq (assq param-name persp-nil-parameters)
persp-nil-parameters)))))
(defun persp--buffer-in-persps (buf)
(cdr (assq 'persp-buffer-in-persps
(gethash buf persp-buffer-props-hash))))
(defun persp--buffer-in-persps-set (buf persps)
(let* ((buf-props (gethash buf persp-buffer-props-hash))
(cons (assq 'persp-buffer-in-persps buf-props)))
(if cons
(setf (cdr cons) persps)
(setq cons (cons 'persp-buffer-in-persps persps))
(push cons buf-props)
(puthash buf buf-props persp-buffer-props-hash))))
(defun persp--buffer-in-persps-add (buf persp)
(persp--buffer-in-persps-set
buf (cons persp (persp--buffer-in-persps buf))))
(defun persp--buffer-in-persps-remove (buf persp)
(persp--buffer-in-persps-set
buf (delq persp (persp--buffer-in-persps buf))))
;; Used in mode defenition:
(defun persp-mode-restore-and-remove-from-make-frame-hook (&optional f)
(remove-hook 'after-make-frame-functions
#'persp-mode-restore-and-remove-from-make-frame-hook)
(if (> persp-auto-resume-time 0)
(run-at-time
persp-auto-resume-time nil
#'(lambda ()
(remove-hook 'find-file-hook
#'persp-special-last-buffer-make-current)
(when (> persp-auto-resume-time 0)
(condition-case-unless-debug err
(persp-load-state-from-file)
(error
(message
"[persp-mode] Error: Can not autoresume perspectives -- %s"
err)))
(when (persp-get-buffer-or-null persp-special-last-buffer)
(persp-switch-to-buffer persp-special-last-buffer)))))
(remove-hook 'find-file-hook
#'persp-special-last-buffer-make-current)))
(defun persp-asave-on-exit (&optional interactive-query opt)
(when persp-mode
(when (null opt)
(setq opt 0))
(if (> persp-auto-save-opt opt)
(condition-case-unless-debug err
(persp-save-state-to-file)
(error
(message "[persp-mode] Error: Can not autosave perspectives -- %s"
err)
(when (or noninteractive
(progn
(when (null (persp-frame-list-without-daemon))
(make-frame))
(null (persp-frame-list-without-daemon))))
(setq interactive-query nil))
(if interactive-query
(yes-or-no-p
"persp-mode can not save perspectives, do you want to exit anyway?")
t)))
t)))
(defun persp-kill-emacs-h ()
(persp-asave-on-exit nil))
(defun persp-kill-emacs-query-function ()
(if persp-mode
(when (persp-asave-on-exit t)
(remove-hook 'kill-emacs-hook #'persp-kill-emacs-h)
t)
t))
(defun persp-special-last-buffer-make-current ()
(setq persp-special-last-buffer (current-buffer)))
;; Auto persp functions:
(defun persp-auto-persp-parameters (name)
(cdr (assoc name persp-auto-persp-alist)))
(defun persp--auto-persp-pickup-buffer (a-p-def buffer)
(let ((action (alist-get :main-action a-p-def)))
(when (functionp action)
(funcall action buffer))))
(defun persp-auto-persp-pickup-bufferlist-for (name bufferlist)
(let ((a-p-def (persp-auto-persp-parameters name)))
(when a-p-def
(mapc (apply-partially #'persp--auto-persp-pickup-buffer a-p-def)
bufferlist))))
(defun persp-auto-persps-pickup-bufferlist (bufferlist)
(mapc
#'(lambda (name) (persp-auto-persp-pickup-bufferlist-for name bufferlist))
(mapcar #'car persp-auto-persp-alist)))
(defun persp-auto-persp-pickup-buffers-for (name)
(persp-auto-persp-pickup-bufferlist-for name
(funcall persp-buffer-list-function)))
(defun persp-auto-persps-pickup-buffers ()
(interactive)
(persp-auto-persps-pickup-bufferlist (funcall persp-buffer-list-function)))
(defun persp-buffer-match-auto-persp-p (buffer-or-name)
(let ((buffer (persp-get-buffer-or-null buffer-or-name))
pred)
(car-safe
(cl-find-if #'(lambda (a-p-def)
(and (setq pred (alist-get :generated-predicate a-p-def))
(funcall pred buffer)))
persp-auto-persp-alist
:key #'cdr))))
(defun persp-auto-persps-for-buffer (buffer-or-name)
(let ((buffer (persp-get-buffer-or-null buffer-or-name)))
(cl-remove-if #'(lambda (pred) (funcall pred buffer))
persp-auto-persp-alist
:key #'(lambda (a-p-cons)
(alist-get :generated-predicate (cdr a-p-cons))))))
(defun persp-auto-persp-activate-hooks (name)
(let ((hooks
(alist-get :hooks
(persp-auto-persp-parameters name))))
(mapc #'(lambda (hook-cons)
(add-hook (car hook-cons) (cdr hook-cons)))
hooks)))
(defun persp-auto-persp-deactivate-hooks (name)
(let ((hooks
(alist-get :hooks
(persp-auto-persp-parameters name))))
(mapc #'(lambda (hook-cons)
(remove-hook (car hook-cons) (cdr hook-cons)))
hooks)))
(defun persp-auto-persps-activate-hooks ()
(mapc #'persp-auto-persp-activate-hooks
(mapcar #'car persp-auto-persp-alist)))
(defun persp-auto-persps-deactivate-hooks ()
(mapc #'persp-auto-persp-deactivate-hooks
(mapcar #'car persp-auto-persp-alist)))
(defsubst persp--generate-predicate-loop-any-all
(items-list condition &rest body)
(if items-list
(let (all noquote)
(setq items-list
(cl-typecase items-list
(function (list items-list))
(list (if (persp-regexp-p items-list) (list items-list) items-list))
(t (list items-list))))
(setq noquote (eq :noquote (car items-list)))
(when noquote (setq items-list (cadr items-list)))
(when (listp items-list)
(setq all (eq :all (car items-list)))
(when all (pop items-list))
(unless noquote (setq items-list `',items-list)))
(let* ((cnd `(cl-member-if
#'(lambda (item)
(setq cond-result
,(if all
`(not ,condition)
condition)))
,items-list)))
`(let (cond-result)
(when ,(if all `(not ,cnd) cnd)
,@body))))
`(let (cond-result)
,@body)))
(cl-defun persp--generate-buffer-predicate
(&key
buffer-name file-name mode mode-name minor-mode minor-mode-name predicate
(true-value (if predicate 'cond-result t))
&allow-other-keys)
(let ((predicate-body true-value))
(when predicate
(setq predicate-body
(persp--generate-predicate-loop-any-all
predicate '(apply item buffer rest-args) predicate-body)))
(when file-name
(setq predicate-body
(persp--generate-predicate-loop-any-all
file-name '(persp-string-match-p item (buffer-file-name buffer))
predicate-body)))
(when buffer-name
(setq predicate-body
(persp--generate-predicate-loop-any-all
buffer-name '(persp-string-match-p item (buffer-name buffer))
predicate-body)))
(when minor-mode-name
(setq predicate-body
(persp--generate-predicate-loop-any-all
minor-mode-name
`(let ((regexp item))
,(persp--generate-predicate-loop-any-all
'(:noquote minor-mode-alist)
'(persp-string-match-p regexp (format-mode-line item))
t))
predicate-body)))
(when minor-mode
(setq predicate-body
(persp--generate-predicate-loop-any-all
minor-mode
`(cond
((symbolp item) (bound-and-true-p item))
((persp-regexp-p item) (let ((regexp item))
,(persp--generate-predicate-loop-any-all
'(:noquote minor-mode-list)
'(and
(bound-and-true-p item)
(persp-string-match-p regexp item))
t)))
(t nil))
predicate-body)))
(when mode-name
(setq predicate-body
(persp--generate-predicate-loop-any-all
mode-name '(persp-string-match-p item (format-mode-line mode-name))
predicate-body)))
(when mode
(setq predicate-body
(persp--generate-predicate-loop-any-all
mode '(cond
((symbolp item) (eq item major-mode))
((persp-regexp-p item)
(persp-string-match-p item (symbol-name major-mode)))
(t nil))
predicate-body)))
(eval `(lambda (buffer &rest rest-args)
(when (buffer-live-p buffer)
(with-current-buffer buffer ,predicate-body))))))
(defun persp--auto-persp-default-on-match (state)
(persp-add-buffer (alist-get 'buffer state)
(alist-get 'persp state)
nil nil)
state)
(defun persp--auto-persp-default-after-match (state)
(let ((persp (alist-get 'persp state))
(noauto (alist-get :noauto state))
(weak (alist-get :weak state))
(parameters (alist-get :parameters state)))
(when persp
(when (not noauto)
(setf (persp-auto persp) t))
(when weak
(setf (persp-weak persp) t))
(modify-persp-parameters parameters persp)))
(let ((persp-name (alist-get 'persp-name state))
(switch (alist-get :switch state)))
(persp-unhide persp-name)
(cl-case switch
('nil nil)
(window (persp-window-switch persp-name))
(frame (persp-frame-switch persp-name))
(t (persp-switch persp-name)))
(when switch
(persp-switch-to-buffer (alist-get 'buffer state))))
state)
;;;###autoload
(cl-defun persp-def-auto-persp
(name &rest keyargs
&key buffer-name file-name mode mode-name minor-mode minor-mode-name
predicate hooks dyn-env get-name get-buffer get-persp
switch parameters noauto weak user-data
on-match after-match dont-pick-up-buffers delete)
(if delete
(let ((ap-cons (assoc name persp-auto-persp-alist)))
(persp-auto-persp-deactivate-hooks name)
(setq persp-auto-persp-alist
(delq ap-cons persp-auto-persp-alist)))
(let (auto-persp-parameters
generated-predicate generated-hook
hook-body main-action)
(cl-loop for (key val) on keyargs by #'cddr
when (and val (not (or (eq key :dont-pick-up-buffers))))
do (push
(cons key
(if (and (functionp val)
(not (or (eq key :mode) (eq key :minor-mode)))
(null (byte-code-function-p val)))
val ;;(byte-compile val)
val))
auto-persp-parameters))
(unless get-name
(push (cons :get-name
(byte-compile
`(lambda (state)
(push (cons 'persp-name ,name) state)
state)))
auto-persp-parameters))
(unless get-persp
(push (cons :get-persp
#'(lambda (state)
(let ((name (alist-get 'persp-name state)))
(when name
(push (cons 'persp (persp-add-new name))
state)))
state))
auto-persp-parameters))
(unless get-buffer
(push (cons :get-buffer
#'(lambda (state)
(push (cons 'buffer (current-buffer))
state)
state))
auto-persp-parameters))
(unless on-match
(push (cons :on-match
#'persp--auto-persp-default-on-match)
auto-persp-parameters))
(unless after-match
(push (cons :after-match
#'persp--auto-persp-default-after-match)
auto-persp-parameters))
(when (or (null hooks) (not (consp hooks)))
(unless hooks
(setq hooks
(when minor-mode
(intern (concat (symbol-name minor-mode)
"-hook")))))
(unless hooks
(setq hooks
(cond
(mode
(intern (concat (symbol-name mode)
"-hook")))
(minor-mode
(intern (concat (symbol-name minor-mode)
"-hook")))
((or mode-name predicate buffer-name)
'after-change-major-mode-hook)
(file-name 'find-file-hook)
(t 'after-change-major-mode-hook))))
(when (and hooks (not (consp hooks)))
(setq hooks (list hooks)))
(push (cons :hooks hooks) auto-persp-parameters))
(setq generated-predicate
(apply #'persp--generate-buffer-predicate
(if predicate
keyargs
(cons :true-value (cons '(car rest-args) keyargs)))))
(push (cons :generated-predicate generated-predicate)
auto-persp-parameters)
(setq main-action
(eval
`(lambda (&optional buffer hook hook-args)
(let (,@dyn-env)
(let* ((state (copy-alist
(persp-auto-persp-parameters ,name))))
(push (cons 'hook hook) state)
(push (cons 'hook-args hook-args) state)
(if buffer
(push (cons 'buffer buffer) state)
(let ((get-buffer
(alist-get :get-buffer state)))
(setq state (funcall get-buffer state))))
(when
(setq state
(funcall (alist-get :generated-predicate state)
(alist-get 'buffer state) state))
(with-current-buffer (alist-get 'buffer state)
(let ((get-name
(alist-get :get-name state)))
(setq state (funcall get-name state)))
(let ((get-persp
(alist-get :get-persp state)))
(setq state (funcall get-persp state)))
(let ((on-match (alist-get :on-match state)))
(when on-match
(setq state (funcall on-match state))
(let ((after-match (alist-get :after-match state)))
(when after-match
(setq state (funcall after-match state)))))))))))))
(push (cons :main-action main-action) auto-persp-parameters)
(when hooks
(let ((aparams-hooks (assq :hooks auto-persp-parameters)))
(dolist (hook hooks)
(setq generated-hook
(with-no-warnings
(let ((warning-minimum-level :emergency)
byte-compile-warnings)
(byte-compile
`(lambda (&rest hook-args)
(when persp-mode
(funcall (with-no-warnings ',main-action)
nil ',hook hook-args)))))))
(setcdr aparams-hooks (delete hook (cdr aparams-hooks)))
(push (cons hook generated-hook) (cdr aparams-hooks)))))
(let ((auto-persp-definition (assoc name persp-auto-persp-alist)))
(if auto-persp-definition
(progn
(persp-auto-persp-deactivate-hooks name)
(setcdr auto-persp-definition auto-persp-parameters))
(setq auto-persp-definition (cons name auto-persp-parameters))
(push auto-persp-definition persp-auto-persp-alist)))
(persp-auto-persp-activate-hooks name)
(unless dont-pick-up-buffers
(persp-auto-persp-pickup-buffers-for name)))))
;;;###autoload
(define-obsolete-function-alias 'def-auto-persp 'persp-def-auto-persp
"persp-mode 2.9.6")
;; Custom save/load functions:
;;;###autoload
(cl-defun persp-def-buffer-save/load
(&rest
keyargs
&key buffer-name file-name mode mode-name minor-mode minor-mode-name
predicate tag-symbol save-vars save-function load-function after-load-function
mode-restore-function
append)
(let ((generated-save-predicate
(apply #'persp--generate-buffer-predicate keyargs))
save-body load-fun)
(when save-vars
(unless (listp save-vars) (setq save-vars (list save-vars)))
(when (and (or mode mode-name) (not (memq 'major-mode save-vars)))
(push 'major-mode save-vars)))
(unless tag-symbol (setq tag-symbol 'def-buffer-with-vars))
(setq save-body
`(let ((vars-list
(with-current-buffer buffer
(cl-delete-if-not
#'(lambda (lvar)
(and
,(persp--generate-predicate-loop-any-all
save-vars
'(if (persp-regexp-p item)
(persp-string-match-p item
(symbol-name lvar))
(eq item lvar))
t)
(persp-elisp-object-readable-p
(symbol-value lvar))))
(buffer-local-variables)
:key #'car-safe))))
,(if save-function
`(funcall (with-no-warnings ',save-function)
buffer ',tag-symbol vars-list)
`(list ',tag-symbol (buffer-name buffer) vars-list)))
save-body `(when (funcall (with-no-warnings ',generated-save-predicate)
buffer)
,save-body))
(setq load-fun
`(lambda (savelist)
(cl-destructuring-bind
(buffer-name vars-list &rest _rest) (cdr savelist)
(let ((buf-file (alist-get 'buffer-file-name vars-list))
(buf-mmode (alist-get 'major-mode vars-list)))
,(when mode-restore-function
`(push (cons 'persp-load-buffer-mode-restore-function
(with-no-warnings ',mode-restore-function))
vars-list))
(let ((persp-loaded-buffer
(persp-buffer-from-savelist
(list 'def-buffer buffer-name buf-file buf-mmode
(list (cons 'local-vars vars-list)))))
(persp-after-load-function (with-no-warnings
',after-load-function))
persp-after-load-lambda)
(when (and persp-loaded-buffer persp-after-load-function)
(setq persp-after-load-lambda
#'(lambda (&rest pall-args)
(apply persp-after-load-function
persp-loaded-buffer pall-args)
(remove-hook 'persp-after-load-state-functions
persp-after-load-lambda)))
(add-hook 'persp-after-load-state-functions
persp-after-load-lambda t))
persp-loaded-buffer)))))
(add-hook 'persp-save-buffer-functions
(eval `(lambda (buffer) ,save-body)) append)
(add-hook 'persp-load-buffer-functions
(eval
`(lambda (savelist)
(when (eq (car savelist) ',tag-symbol)
(let ((default-load-fun (with-no-warnings ',load-fun)))
,(if load-function
`(funcall (with-no-warnings ',load-function)
savelist default-load-fun
(with-no-warnings ',after-load-function))
`(funcall default-load-fun savelist))))))
append)))
;;;###autoload
(define-obsolete-function-alias
'def-persp-buffer-save/load 'persp-def-buffer-save/load
"persp-mode 2.9.6")
;; Mode itself:
;;;###autoload
(define-minor-mode persp-mode
"Toggle the persp-mode.
When active, keeps track of multiple 'perspectives',
named collections of buffers and window configurations.
Here is a keymap of this minor mode:
\\{persp-mode-map}"
:require 'persp-mode
:group 'persp-mode
:keymap persp-mode-map
:init-value nil
:global t
:lighter (:eval persp-lighter)
(if persp-mode
(when (or (eq 'persp-force-restart persp-mode) (null *persp-hash*))
(setq persp-special-last-buffer nil)
(add-hook 'find-file-hook #'persp-special-last-buffer-make-current)
(setq *persp-hash* (make-hash-table :test #'equal :size 10))
(setq persp-buffer-props-hash (make-hash-table :test #'eq :size 10))
(setq persp-names-cache nil)
(push '(persp . writable) window-persistent-parameters)
(persp-add-minor-mode-menu)
(persp-add-new persp-nil-name)
(add-hook 'find-file-hook #'persp-add-or-not-on-find-file)
(add-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function)
(add-hook 'kill-buffer-hook #'persp-kill-buffer-h)
(add-hook 'before-make-frame-hook #'persp-before-make-frame)
(add-hook 'after-make-frame-functions #'persp-init-new-frame)
(add-hook 'delete-frame-functions #'persp-delete-frame)
(add-hook 'kill-emacs-query-functions #'persp-kill-emacs-query-function)
(add-hook 'kill-emacs-hook #'persp-kill-emacs-h)
(add-hook 'server-switch-hook #'persp-server-switch)
(add-hook 'after-change-major-mode-hook #'persp-after-change-major-mode-h)
(persp-set-ido-hooks persp-set-ido-hooks)
(persp-set-read-buffer-function persp-set-read-buffer-function)
(persp-update-completion-system persp-interactive-completion-system)
(condition-case-unless-debug err
(mapc #'persp-init-frame (persp-frame-list-without-daemon))
(error
(message "[persp-mode] Error: Can not initialize frame -- %s"
err)))
(when (fboundp 'tabbar-mode)
(setq tabbar-buffer-list-function #'persp-buffer-list))
(persp-auto-persps-activate-hooks)
(if (or noninteractive
(and (daemonp)
(null (cdr (frame-list)))
(eq (selected-frame) terminal-frame)))
(add-hook 'after-make-frame-functions
#'persp-mode-restore-and-remove-from-make-frame-hook)
(persp-mode-restore-and-remove-from-make-frame-hook)))
(run-hooks 'persp-mode-deactivated-hook)
(unless (memq #'persp-mode-restore-and-remove-from-make-frame-hook
after-make-frame-functions)
(persp-asave-on-exit t 1))
(remove-hook 'find-file-hook #'persp-add-or-not-on-find-file)
(remove-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function)
(remove-hook 'kill-buffer-hook #'persp-kill-buffer-h)
(remove-hook 'before-make-frame-hook #'persp-before-make-frame)
(remove-hook 'after-make-frame-functions #'persp-init-new-frame)
(remove-hook 'delete-frame-functions #'persp-delete-frame)
(remove-hook 'kill-emacs-query-functions #'persp-kill-emacs-query-function)
(remove-hook 'kill-emacs-hook #'persp-kill-emacs-h)
(remove-hook 'server-switch-hook #'persp-server-switch)
(remove-hook 'after-change-major-mode-hook #'persp-after-change-major-mode-h)
(persp-set-ido-hooks)
(persp-set-read-buffer-function)
(persp-update-frames-buffer-predicate t)
(persp-update-completion-system nil t)
(persp-auto-persps-deactivate-hooks)
(when (fboundp 'tabbar-mode)
(setq tabbar-buffer-list-function #'tabbar-buffer-list))
(setq window-persistent-parameters
(delq (assq 'persp window-persistent-parameters)
window-persistent-parameters))
;; TODO: do it properly -- remove buffers, kill perspectives
(setq *persp-hash* nil)
(setq persp-buffer-props-hash nil)
(setq persp-names-cache nil)))
;; Hooks:
(defun persp--kill-buffer-query-function-foreign-check (persp buf)
(let ((opt persp-kill-foreign-buffer-behaviour))
(cond
((functionp opt) (funcall opt))
(t
(if (cl-case opt
((kill nil) t)
(dont-ask-weak (persp-buffer-free-p buf t))
(t (persp-buffer-filtered-out-p buf)))
'kill
(let ((curwin (selected-window))
(prompt (format "You are going to kill a buffer(%s) \
which is not in the current(%s) perspective. It will be removed from \
%s perspectives and then killed.\nWhat do you really want to do? "
(buffer-name buf)
(safe-persp-name persp)
(mapcar #'persp-name
(persp--buffer-in-persps buf)))))
(cl-macrolet
((clwin (w)
`(run-at-time 1 nil #'(lambda (ww)
(when (window-live-p ww)
(delete-window ww)))
,w))
(swb (b w)
`(run-at-time
1 nil
#'(lambda (bb ww)
(with-selected-window ww
(persp-set-another-buffer-for-window
bb ww)))
,b ,w)))
(cl-destructuring-bind (char &rest _)
(let ((variants
(list '(?q "do nothing")
'(?k "kill")
'(?K "kill and close window")
'(?c "close window")
'(?s "switch to another buffer")))
(cwin (selected-window)))
(when (minibuffer-window-active-p cwin)
(setq cwin (minibuffer-selected-window)))
(unless (eq buf (window-buffer cwin))
(setq variants
(delq (assq ?K variants)
(delq (assq ?c variants)
(delq (assq ?s variants) variants)))))
(read-multiple-choice prompt variants))
(cl-case char
((?q ?\C-g ?\C-\[) nil)
(?k 'kill)
(?K (clwin curwin) 'kill)
(?c (clwin curwin) nil)
(?s (swb buf curwin) nil)
(t t))))))))))
(defun persp-kill-buffer-query-function ()
"This must be the last hook in the `kill-buffer-query-functions'.
Otherwise if next function in the list returns nil -- the buffer will not be
killed, but just removed from a perspective(s)."
(if persp-mode
(let ((buffer (current-buffer)))
(if (persp--buffer-in-persps buffer)
(let* ((persp (get-current-persp))
(foreign-check
(if (and persp
(persp-contain-buffer-p buffer persp))
'not-foreign
(persp--kill-buffer-query-function-foreign-check
persp buffer))))
(cl-case foreign-check
(kill
(let (persp-autokill-buffer-on-remove)
(persp--remove-buffer-2 nil buffer))
t)
(not-foreign
(if (persp-buffer-in-other-p* buffer persp)
(progn (persp--remove-buffer-2 persp buffer)
nil)
(if (or (not (buffer-live-p buffer))
(persp--buffer-in-persps buffer))
nil
t)
t))
(t
nil)))
t))
t))
(defun persp-kill-buffer-h ()
(let ((buffer (current-buffer)))
(when (and persp-mode (persp--buffer-in-persps buffer))
(let (persp-autokill-buffer-on-remove
(persp-when-remove-buffer-switch-to-other-buffer
(unless persp-set-frame-buffer-predicate
persp-when-remove-buffer-switch-to-other-buffer)))
(persp--remove-buffer-2 nil buffer)))))
(defun persp--restore-buffer-on-find-file ()
(when (buffer-live-p persp-special-last-buffer)
(set-window-buffer (or (get-buffer-window) (selected-window))
persp-special-last-buffer))
(setq persp-special-last-buffer nil)
(remove-hook 'window-configuration-change-hook
#'persp--restore-buffer-on-find-file))
(defun persp-add-or-not-on-find-file ()
(let ((no-select
(not (funcall persp-backtrace-frame-function 0 'find-file))))
(and
(cl-case persp-add-buffer-on-find-file
('nil nil)
(if-not-autopersp
(let ((ret (not (persp-buffer-match-auto-persp-p (current-buffer)))))
(unless (or ret no-select)
(setq persp-special-last-buffer (window-buffer))
(add-hook 'window-configuration-change-hook
#'persp--restore-buffer-on-find-file))
ret))
(add-but-not-switch-if-autopersp
(when (and (not no-select)
(persp-buffer-match-auto-persp-p (current-buffer)))
(setq no-select t)
(setq persp-special-last-buffer (window-buffer))
(add-hook 'window-configuration-change-hook
#'persp--restore-buffer-on-find-file))
t)
(t t))
(persp-add-buffer
(current-buffer) (get-current-persp) (not no-select) nil))))
(defun persp-after-change-major-mode-h ()
(let ((buf (current-buffer)))
(persp-find-and-set-persps-for-buffer buf)
(when
(and
(cl-case persp-add-buffer-on-after-change-major-mode
('nil nil)
(free (persp-buffer-free-p buf))
(t t))
(not
(persp-buffer-filtered-out-p
buf persp-add-buffer-on-after-change-major-mode-filter-functions)))
(persp-add-buffer buf (get-current-persp) nil nil))))
(defun persp-server-switch ()
(condition-case-unless-debug err
(let* ((frame (selected-frame))
(persp-server-switch-hook (frame-parameter
frame 'persp-server-switch-hook)))
(when persp-server-switch-hook
(unless (string-match-p "^.*magit.*$" (symbol-name last-command))
(funcall persp-server-switch-hook frame))
(set-frame-parameter frame 'persp-server-switch-hook nil)))
(error
(message "[persp-mode] Error: error in server-switch-hook -- %s"
err))))
;; Misc funcs:
(cl-defun persp-get-by-name
(name &optional (phash *persp-hash*) (default persp-not-persp))
(gethash name phash default))
(cl-defun persp-with-name-exists-p (name &optional (phash *persp-hash*))
(persp-p (persp-get-by-name name phash)))
(cl-defun persp-by-name-and-exists (name &optional (phash *persp-hash*))
(let ((persp (persp-get-by-name name phash)))
(cons (persp-p persp) persp)))
(cl-defun persp-gen-random-name (&optional name (phash *persp-hash*))
(unless name (setq name (number-to-string (random))))
(cl-macrolet ((namegen () `(format "%s:%s" name (random 9))))
(cl-do ((nname name (namegen)))
((not (persp-with-name-exists-p nname phash))
nname))))
(defsubst persp-is-frame-daemons-frame (f)
(and (daemonp) (eq f terminal-frame)))
(defun persp-frame-list-without-daemon ()
"Return a list of frames without the daemon's frame."
(if (daemonp)
(filtered-frame-list
#'(lambda (f) (not (persp-is-frame-daemons-frame f))))
(frame-list)))
;; TODO: rename
(defun set-frame-persp (persp &optional frame)
(set-frame-parameter frame 'persp persp))
;; TODO: rename
(defun get-frame-persp (&optional frame)
(frame-parameter frame 'persp))
(cl-defun persp-names (&optional (phash *persp-hash*) (reverse t))
(let (ret)
(maphash #'(lambda (k p)
(push k ret))
phash)
(if reverse
(nreverse ret)
ret)))
;; TODO: rename
(defun set-window-persp* (persp-name &optional window)
(when persp-name
(set-window-parameter window 'persp persp-name)))
;; TODO: rename
(defun get-window-persp* (&optional window)
(window-parameter window 'persp))
;; TODO: rename
(defun set-window-persp (persp &optional window)
(let ((frame (window-frame window)))
(if (eq persp (get-frame-persp frame))
(clear-window-persp window)
(set-window-persp* (safe-persp-name persp) window))))
;; TODO: rename
(defun window-persp-set-p (&optional window)
(get-window-persp* window))
;; TODO: rename
(defun get-window-persp (&optional window)
(let ((pn (get-window-persp* window)))
(when pn
(cl-destructuring-bind (e . p)
(persp-by-name-and-exists pn)
(and e p)))))
;; TODO: rename
(defun clear-window-persp (&optional window)
(set-window-parameter window 'persp nil))
;; TODO: rename
(defun get-current-persp (&optional frame window)
(with-selected-frame (or frame (selected-frame))
(if (window-persp-set-p window)
(get-window-persp window)
(get-frame-persp frame))))
;; TODO: rename
(defun set-current-persp (persp)
(if (window-persp-set-p)
(set-window-persp persp)
(set-frame-persp persp)))
(defun persp-names-current-frame-fast-ordered ()
(cl-copy-list persp-names-cache))
;; TODO: remove this
(cl-defsubst persp-names-sorted (&optional (phash *persp-hash*))
(sort (persp-names phash nil) #'string<))
(make-obsolete 'persp-names-sorted "it will be removed." "persp-mode 2.9.6")
(defun persp-group-by (keyf lst &optional reverse)
(let (result)
(mapc #'(lambda (pd)
(let* ((key (funcall keyf pd))
(kv (assoc key result)))
(if kv
(setcdr kv (cons pd (cdr kv)))
(push (cons key (list pd)) result))))
lst)
(if reverse
(nreverse
(mapcar #'(lambda (gr)
(cl-destructuring-bind (key . pd) gr
(cons key (nreverse pd))))
result))
result)))
(defun persp-regexp-p (obj)
(or (stringp obj) (and (consp obj) (stringp (cdr obj)))))
(defun persp-string-match-p (regexp string &optional start)
(when (and regexp (not (consp regexp)))
(setq regexp (cons t regexp)))
(let ((ret (string-match-p (cdr regexp) string start)))
(if (eq :not (car regexp))
(not ret)
ret)))
(cl-defun persp-persps (&optional (phash *persp-hash*) names-regexp reverse)
(when (and names-regexp (not (consp names-regexp)))
(setq names-regexp (cons t names-regexp)))
(let (ret)
(maphash #'(lambda (k p)
(if names-regexp
(when (persp-string-match-p names-regexp k)
(push p ret))
(push p ret)))
phash)
(if reverse
(nreverse ret)
ret)))
(cl-defun persp-other-not-hidden-persps (&optional persp (phash *persp-hash*))
(cl-delete-if #'safe-persp-hidden (delq persp (persp-persps phash))))
(cl-defun persp-other-persps-with-buffer-except-nil
(&optional (buff-or-name (current-buffer)) (persp (get-current-persp))
(phash *persp-hash*) del-weak)
(let ((buf (persp-get-buffer-or-null buff-or-name))
ret)
(when buf
(setq ret (cl-delete-if-not
(apply-partially #'memq buf)
(delq persp (delq nil (persp-persps phash)))
:key #'persp-buffers))
(when del-weak
(setq ret (cl-delete-if #'persp-weak ret))))
ret))
(cl-defun persp-other-persps-with-buffer-except-nil*
(&optional
(buff-or-name (current-buffer)) (persp (get-current-persp)) del-weak)
(let ((persps (persp--buffer-in-persps
(persp-get-buffer-or-null buff-or-name))))
(when persp
(setq persps (remq persp persps)))
(when del-weak
(setq persps (cl-remove-if #'persp-weak persps)))
persps))
(cl-defun persp-buffer-in-other-p
(&optional (buff-or-name (current-buffer)) (persp (get-current-persp))
(phash *persp-hash*) del-weak)
(persp-other-persps-with-buffer-except-nil buff-or-name persp phash del-weak))
(cl-defun persp-buffer-in-other-p*
(&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) del-weak)
(persp-other-persps-with-buffer-except-nil* buff-or-name persp del-weak))
(cl-defun persp-frames-with-persp (&optional (persp (get-frame-persp)))
(cl-delete-if-not (apply-partially #'eq persp)
(persp-frame-list-without-daemon)
:key #'get-frame-persp))
(cl-defun persp-frames-and-windows-with-persp (&optional (persp (get-current-persp)))
(let (frames windows)
(dolist (frame (persp-frame-list-without-daemon))
(when (eq persp (get-frame-persp frame))
(push frame frames))
(dolist (window (window-list frame 'no-minibuf))
(when (and (window-persp-set-p window)
(eq persp (get-window-persp window)))
(push window windows))))
(cons frames windows)))
(cl-defun persp-do-buffer-list-by-regexp (&key func regexp blist noask
(rest-args nil rest-args-p))
(interactive)
(unless func
(let ((fs (completing-read "What function to apply: " obarray 'functionp t)))
(when (and fs (not (string= fs "")))
(setq func (read fs)))))
(when func
(unless regexp
(setq regexp (read-regexp "Regexp: ")))
(when regexp
(unless blist
(setq blist (eval (read--expression "Buffer list expression: " "nil"))))
(when blist
(unless rest-args-p
(setq rest-args (read--expression "Rest arguments: " "nil")))
(setq blist
(cl-remove-if-not
(apply-partially #'persp-string-match-p regexp)
(mapcar #'get-buffer blist)
:key #'buffer-name))
(when (and blist
(or noask (y-or-n-p (format "Do %s on these buffers:\n%s?\n"
func
(mapconcat #'buffer-name blist ", ")))))
(mapcar #'(lambda (b) (apply func b rest-args)) blist))))))
;; Perspective funcs:
(defun persp-next ()
"Switch to next perspective (to the right)."
(interactive)
(let* ((persp-list (persp-names-current-frame-fast-ordered))
(persp-list-length (length persp-list))
(only-perspective? (equal persp-list-length 1))
(pos (cl-position (safe-persp-name (get-current-persp)) persp-list)))
(cond
((null pos) nil)
(only-perspective? nil)
((= pos (1- persp-list-length))
(if persp-switch-wrap (persp-switch (nth 0 persp-list))))
(t (persp-switch (nth (1+ pos) persp-list))))))
(defun persp-prev ()
"Switch to previous perspective (to the left)."
(interactive)
(let* ((persp-list (persp-names-current-frame-fast-ordered))
(persp-list-length (length persp-list))
(only-perspective? (equal persp-list-length 1))
(pos (cl-position (safe-persp-name (get-current-persp)) persp-list)))
(cond
((null pos) nil)
(only-perspective? nil)
((= pos 0)
(if persp-switch-wrap
(persp-switch (nth (1- persp-list-length) persp-list))))
(t (persp-switch (nth (1- pos) persp-list))))))
(cl-defun persp-add (persp &optional (phash *persp-hash*))
"Insert `PERSP' to `PHASH'.
If we adding to the `*persp-hash*' add entries to the mode menu.
Return `PERSP'."
(let ((name (safe-persp-name persp)))
(puthash name persp phash)
(when (eq phash *persp-hash*)
(persp-add-to-menu persp)))
persp)
(cl-defun persp-remove-by-name (name &optional (phash *persp-hash*))
"Remove a perspective with name `NAME' from `PHASH'.
Save it's state before removing.
If we removing from the `*persp-hash*' remove also the menu entries.
Switch all frames with that perspective to another one.
Return the removed perspective."
(interactive "i")
(unless name
(setq name (persp-read-persp
"to remove" nil
(and (eq phash *persp-hash*)
(safe-persp-name (get-current-persp)))
t t)))
(let ((persp (persp-get-by-name name phash))
(persp-to-switch persp-nil-name))
(when (persp-p persp)
(persp-save-state persp)
(if (and (eq phash *persp-hash*) (null persp))
(message "[persp-mode] Error: Can't remove the 'nil' perspective")
(when (eq phash *persp-hash*)
(persp-remove-from-menu persp)
(cl-destructuring-bind (frames . windows)
(persp-frames-and-windows-with-persp persp)
(dolist (w windows) (clear-window-persp w))
;; (setq persp-to-switch (or (car (persp-names phash nil))
;; persp-nil-name))
(dolist (f frames)
(persp-frame-switch persp-to-switch f))))
(remhash name phash)))
persp))
(cl-defun persp-add-new (name &optional (phash *persp-hash*))
"Create a new perspective with the given `NAME'. Add it to `PHASH'.
Return the created perspective."
(interactive "sA name for the new perspective: ")
(if (and name (not (equal "" name)))
(cl-destructuring-bind (e . p)
(persp-by-name-and-exists name phash)
(if e p
(setq p (if (equal persp-nil-name name)
nil (make-persp :name name)))
(persp-add p phash)
(run-hook-with-args 'persp-created-functions p phash)
p))
(message "[persp-mode] Error: Can't create a perspective with empty name.")
nil))
(defun persp-find-and-set-persps-for-buffer (&optional buffer-or-name)
(setq buffer-or-name (if buffer-or-name
(persp-get-buffer-or-null buffer-or-name)
(current-buffer)))
(mapc #'(lambda (p)
(when p
(persp-add-buffer buffer-or-name p nil nil)))
(persp--buffer-in-persps buffer-or-name))
(persp--buffer-in-persps-set
buffer-or-name
(cl-delete-if-not (apply-partially #'memq buffer-or-name)
(delq nil (persp-persps))
:key #'persp-buffers)))
(cl-defun persp-contain-buffer-p
(&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) delweak)
(if (and delweak (safe-persp-weak persp))
nil
(if persp
(memq (persp-get-buffer-or-null buff-or-name)
(persp-buffers persp))
t)))
(cl-defun persp-contain-buffer-p*
(&optional (buff-or-name (current-buffer)) (persp (get-current-persp)) delweak)
(if (and delweak (safe-persp-weak persp))
nil
(if persp
(memq persp (persp--buffer-in-persps
(persp-get-buffer-or-null buff-or-name)))
t)))
(cl-defun persp-add-buffer
(&optional buffs-or-names (persp (get-current-persp))
(switchorno persp-switch-to-added-buffer)
(called-interactively-p (called-interactively-p 'any)))
(interactive "i")
(when (and called-interactively-p current-prefix-arg)
(setq switchorno (not switchorno)))
(unless buffs-or-names
(setq buffs-or-names
(when called-interactively-p
(let ((*persp-restrict-buffers-to* 1)
persp-restrict-buffers-to-if-foreign-buffer)
(persp-read-buffer (concat
"Add buffers to the perspective"
(and switchorno
" and switch to first added buffer")
": ")
(current-buffer) t nil t)))))
(unless (listp buffs-or-names) (setq buffs-or-names (list buffs-or-names)))
(mapc
#'(lambda (bon)
(let ((buffer (persp-get-buffer-or-null bon)))
(when (and persp buffer)
(unless (persp-contain-buffer-p buffer persp)
(push buffer (persp-buffers persp)))
(unless (persp-contain-buffer-p* buffer persp)
(persp--buffer-in-persps-add buffer persp)))
(when (and buffer switchorno (eq persp (get-current-persp)))
(persp-switch-to-buffer buffer))
buffer))
buffs-or-names)
buffs-or-names)
(cl-defun persp-add-buffers-by-regexp (&optional regexp (persp (get-current-persp)))
(interactive)
(when persp
(persp-do-buffer-list-by-regexp
:regexp regexp :func 'persp-add-buffer :rest-args (list persp nil)
:blist (persp-buffer-list-restricted (selected-frame) 1))))
(cl-defun persp-temporarily-display-buffer
(&optional buff-or-name (called-interactively-p (called-interactively-p 'any)))
(interactive "i")
(let ((persp-temporarily-display-buffer t))
(unless buff-or-name
(setq buff-or-name
(if called-interactively-p
(let ((*persp-restrict-buffers-to*
(if (and called-interactively-p current-prefix-arg) 0 1))
(persp-restrict-buffers-to-if-foreign-buffer
(if (= 0 *persp-restrict-buffers-to*) -1 nil)))
(persp-read-buffer
(if (= 0 *persp-restrict-buffers-to*)
"Remove a buffer from the perspective, but still display it: "
"Temporarily display a buffer, not adding it to the current perspective: ")
nil t))
(current-buffer))))
(let ((buffer (persp-get-buffer-or-null buff-or-name)))
(when buffer
(let ((persp (get-current-persp)))
(when (and persp (persp-contain-buffer-p* buffer persp))
(let (persp-autokill-buffer-on-remove
persp-autokill-persp-when-removed-last-buffer)
(persp-remove-buffer buffer persp nil nil nil nil))))
(persp-switch-to-buffer buffer t)))))
(defun persp--buffer-do-auto-action-if-needed (buffer)
(when (and persp-autokill-buffer-on-remove
(persp-buffer-free-p
buffer
(eq 'kill-weak persp-autokill-buffer-on-remove)))
(let (persp-autokill-buffer-on-remove)
(persp-kill-buffer buffer))))
(defun persp--remove-buffer-1 (buffer &optional persp)
(if persp
(progn
(when persp-when-remove-buffer-switch-to-other-buffer
(persp-switch-to-prev-buffer buffer persp))
(persp--buffer-in-persps-remove buffer persp)
(setf (persp-buffers persp) (delq buffer (persp-buffers persp)))
persp)
(mapcar (apply-partially #'persp--remove-buffer-1 buffer)
(persp-other-persps-with-buffer-except-nil buffer persp))))
(defun persp--remove-buffer-2 (&optional persp buffer-or-name)
(let ((buffer (if buffer-or-name
(persp-get-buffer-or-null buffer-or-name)
(current-buffer))))
(when buffer
(persp--remove-buffer-1 buffer persp)
(persp--buffer-do-auto-action-if-needed buffer)
(persp--do-auto-action-if-needed persp))
buffer))
(defun persp--remove-buffers-from-nil-p (buffs-or-names)
(cl-typecase persp-remove-buffers-from-nil-persp-behaviour
(function
(funcall persp-remove-buffers-from-nil-persp-behaviour
buffs-or-names))
(symbol
(cl-macrolet
((ask () `(yes-or-no-p
(format "Remove %s buffers from all perspectives?"
buffs-or-names))))
(cl-case persp-remove-buffers-from-nil-persp-behaviour
(ask-to-rem-from-all
(if (cl-find-if-not #'persp-buffer-free-p buffs-or-names)
(ask) t))
(ask-if-in-non-weak-persp
(if (cl-find-if-not
#'(lambda (bon)
(persp-buffer-free-p bon t))
buffs-or-names)
(ask) t))
(t t))))
(t t)))
(cl-defun persp-remove-buffer
(&optional buffs-or-names (persp (get-current-persp))
(rem-from-nil-opt persp-remove-buffers-from-nil-persp-behaviour)
(switch persp-when-remove-buffer-switch-to-other-buffer)
called-from-kill-buffer-hook
(called-interactively-p (called-interactively-p 'any)))
"Remove BUFFS-OR-NAMES(which may be a single buffer or a list of buffers)
from the PERSP. On success return removed buffers otherwise nil."
(interactive "i")
;; TODO: remove these parameters
(ignore called-from-kill-buffer-hook rem-from-nil-opt switch)
(unless (listp buffs-or-names) (setq buffs-or-names (list buffs-or-names)))
(unless buffs-or-names
(setq buffs-or-names
(if called-interactively-p
(let ((*persp-restrict-buffers-to* 0)
persp-restrict-buffers-to-if-foreign-buffer)
(persp-read-buffer "Remove buffers from the perspective: "
(current-buffer) t nil t))
(current-buffer))))
(when (or persp
(persp--remove-buffers-from-nil-p buffs-or-names))
(let ((persp-autokill-buffer-on-remove
(if (and called-interactively-p current-prefix-arg)
(not persp-autokill-buffer-on-remove)
persp-autokill-buffer-on-remove)))
(mapcar (apply-partially #'persp--remove-buffer-2 persp)
buffs-or-names))))
(defun persp-kill-buffer (&optional buffers-or-names)
"Kill buffers, read buffer with restriction to current perspective."
(interactive (list
(let ((*persp-restrict-buffers-to* 0)
persp-restrict-buffers-to-if-foreign-buffer)
(if persp-mode
(persp-read-buffer
"Kill buffers: " (current-buffer) t nil t)
(read-buffer "Kill buffer: " (current-buffer) t)))))
(unless (listp buffers-or-names)
(setq buffers-or-names (list buffers-or-names)))
(mapc #'kill-buffer
(cl-remove-if-not #'persp-get-buffer-or-null buffers-or-names))
buffers-or-names)
(defun persp-switch-to-buffer (buffer-or-name
&optional norecord force-same-window)
"Switch to buffer, read buffer with restriction to current perspective."
(interactive (list
(let ((*persp-restrict-buffers-to* 0)
persp-restrict-buffers-to-if-foreign-buffer)
(if persp-mode
(let ((dflt (other-buffer (current-buffer))))
(unless (memq dflt (safe-persp-buffers
(get-current-persp)))
(cl-psetq dflt (current-buffer)))
(persp-read-buffer "Switch to buffer: " dflt t))
(read-buffer-to-switch "Switch to buffer: ")))))
(when (and buffer-or-name
(persp-get-buffer-or-null (get-buffer buffer-or-name)))
(switch-to-buffer buffer-or-name norecord force-same-window)))
(cl-defun persp-remove-buffers-by-regexp
(&optional regexp (persp (get-current-persp)))
(interactive)
(when persp
(persp-do-buffer-list-by-regexp
:regexp regexp :func 'persp-remove-buffer
:blist (persp-buffers persp) :rest-args (list persp))))
(cl-defun persp-import-buffers-from (persp-from
&optional (persp-to (get-current-persp)))
(if persp-to
(mapc #'(lambda (b) (persp-add-buffer b persp-to nil nil))
(safe-persp-buffers persp-from))
(message "[persp-mode] Error: Can't import buffers to the 'nil' perspective, \
cause it already contain all buffers.")))
(cl-defun persp-import-buffers
(names
&optional (persp-to (get-current-persp)) (phash *persp-hash*))
"Import buffers from perspectives with the given names to another one."
(interactive "i")
(unless (listp names) (setq names (list names)))
(unless names
(setq names (persp-read-persp "to import buffers from" t nil t nil t)))
(mapc #'(lambda (persp-from)
(persp-import-buffers-from persp-from persp-to))
(mapcar #'(lambda (pn) (persp-get-by-name pn phash)) names)))
(cl-defun persp-import-win-conf
(name
&optional (persp-to (get-current-persp)) (phash *persp-hash*)
no-update-frames)
(interactive "i")
(unless name
(setq name (persp-read-persp
"to import window configuration from" nil nil t nil t)))
(let ((persp-from (persp-get-by-name name phash)))
(unless (or (eq persp-to persp-from)
(not (persp-p persp-from)))
(if persp-to
(setf (persp-window-conf persp-to) (safe-persp-window-conf persp-from))
(setq persp-nil-wconf (persp-window-conf persp-from)))
(unless no-update-frames
(persp-update-frames-window-confs (list (safe-persp-name persp-to)))))))
(cl-defun persp-copy
(new-name
&optional switch (called-interactively-p (called-interactively-p 'any)))
(interactive "i")
(unless new-name
(setq new-name
(read-string "Copy current persp with name: ")))
(if (member new-name (persp-names))
(progn
(message
"[persp-mode] Error: There is already a perspective with that name %s"
new-name)
nil)
(let* ((new-persp (persp-add-new new-name))
(current-persp (get-current-persp))
(new-buffers (when new-persp
(if current-persp
(cl-copy-list (persp-buffers current-persp))
(safe-persp-buffers current-persp)))))
(when new-persp
(when (and called-interactively-p current-prefix-arg)
(setq new-buffers
(let (choosen-buffers)
(cl-delete-if-not
(cl-destructuring-bind (char &rest _)
(read-multiple-choice
"What buffers to copy? "
'((?a "all")
(?d "displayed")
(?f "free and displayed")
(?F "free")
(?c "choose")
(?n "none")))
(cl-case char
(?d #'(lambda (b) (get-buffer-window-list b 'no-minibuf)))
(?f #'(lambda (b) (or (persp-buffer-free-p b t)
(get-buffer-window-list b 'no-minibuf))))
(?F #'(lambda (b) (persp-buffer-free-p b t)))
(?c (setq choosen-buffers
(mapcar #'get-buffer
(persp-read-buffer
"" (current-buffer) t nil t 'push)))
#'(lambda (b) (memq b choosen-buffers)))
(?n #'not)
(?a nil)
(t nil)))
new-buffers))))
(persp-save-state current-persp)
(setf (persp-window-conf new-persp)
(safe-persp-window-conf current-persp)
(persp-parameters new-persp)
(cl-copy-list (safe-persp-parameters current-persp))
(persp-weak new-persp)
(if current-persp (persp-weak current-persp) nil))
(persp-add-buffer new-buffers new-persp nil nil)
(cl-case switch
(window (persp-window-switch new-name))
(frame (persp-frame-switch new-name))
(no-switch nil)
(t (persp-switch new-name)))
new-persp))))
(cl-defun persp-get-buffer
(&optional (buff-or-name (current-buffer)) (persp (get-current-persp)))
"Like `get-buffer', but constrained to the perspective's list of buffers.
Return the buffer if it's in the perspective or the first buffer from the
perspective buffers or nil."
(let ((buffer (persp-get-buffer-or-null buff-or-name)))
(or (cl-find buffer (safe-persp-buffers persp))
(cl-first (safe-persp-buffers persp)))))
(defun persp-get-buffer-or-null (buff-or-name)
"Safely return a buffer or the nil without errors."
(cl-typecase buff-or-name
((or string buffer)
(let ((buf (get-buffer buff-or-name)))
(and (buffer-live-p buf)
buf)))
(otherwise nil)))
(defun persp-buffer-filtered-out-p (buff-or-name &rest filters)
(setq filters (if filters
(cons
persp-common-buffer-filter-functions
filters)
persp-common-buffer-filter-functions)
buff-or-name (get-buffer buff-or-name))
(cl-find-if #'(lambda (filter)
(if (functionp filter)
(funcall filter buff-or-name)
(cl-find-if #'(lambda (f) (funcall f buff-or-name)) filter)))
filters))
(defun persp-buffer-free-p (&optional buff-or-name del-weak)
(unless buff-or-name (setq buff-or-name (current-buffer)))
(let ((persps (persp--buffer-in-persps
(persp-get-buffer-or-null buff-or-name))))
(if persps
(if del-weak
(not
(cl-find-if-not #'persp-weak persps))
nil)
t)))
(cl-defun persp-set-another-buffer-for-window
(&optional (old-buff-or-name (current-buffer)) (window (selected-window))
(persp (get-current-persp nil window)))
(unless (window-minibuffer-p window)
(let* ((old-buf (persp-get-buffer-or-null old-buff-or-name))
(new-buf (if persp-set-frame-buffer-predicate
(other-buffer old-buf)
(cl-find-if #'(lambda (bc)
(and (bufferp bc) (not (eq bc old-buf))
(persp-contain-buffer-p bc persp)))
(append (mapcar #'car
(window-prev-buffers window))
(window-next-buffers window))))))
(set-window-buffer
window
(or (and (buffer-live-p new-buf) new-buf)
(car (persp-buffer-list-restricted (window-frame window) 2.5))
(car (buffer-list)))))))
(cl-defun persp-switch-to-prev-buffer
(&optional (old-buff-or-name (current-buffer)) (persp (get-current-persp)))
"Switch all windows in all frames with a perspective displaying that buffer
to some previous buffer in the perspective.
Return that old buffer."
(let ((old-buf (persp-get-buffer-or-null old-buff-or-name)))
(cl-destructuring-bind (frames . windows)
(persp-frames-and-windows-with-persp persp)
(dolist (w windows)
(persp-set-another-buffer-for-window old-buf w))
(dolist (f frames)
(dolist (w (get-buffer-window-list old-buf 'no-minibuf f))
(persp-set-another-buffer-for-window old-buf w))))
old-buf))
(cl-defsubst persp-filter-out-bad-buffers (&optional (persp (get-current-persp)))
;; filter out killed buffers
(when persp
(setf (persp-buffers persp)
(cl-delete-if-not #'persp-get-buffer-or-null (persp-buffers persp)))))
(defun persp-hide (names)
(interactive "i")
(unless (listp names) (setq names (list names)))
(unless names
(setq names (persp-read-persp
"to hide" t (safe-persp-name (get-current-persp)) t)))
(let ((persp-to-switch (get-current-persp))
(hidden-persps
(mapcar #'(lambda (pn)
(let ((persp (persp-get-by-name pn)))
(when (persp-p persp)
(if persp
(setf (persp-hidden persp) t)
(setq persp-nil-hidden t)))
persp))
names)))
(when (safe-persp-hidden persp-to-switch)
(setq persp-to-switch
(car (persp-other-not-hidden-persps persp-to-switch))))
(mapc #'(lambda (p)
(when (persp-p p)
(cl-destructuring-bind (frames . windows)
(persp-frames-and-windows-with-persp p)
(dolist (w windows) (clear-window-persp w))
(dolist (f frames)
(persp-frame-switch (safe-persp-name persp-to-switch) f)))))
hidden-persps)))
(defun persp-unhide (names)
(interactive "i")
(unless (listp names) (setq names (list names)))
(unless names
(let ((hidden-persps
(mapcar #'safe-persp-name
(cl-delete-if-not #'safe-persp-hidden
(persp-persps)))))
(setq names
(persp-read-persp
"to unhide" t (car hidden-persps) t nil nil hidden-persps t))))
(when names
(mapc #'(lambda (pn)
(let ((persp (persp-get-by-name pn)))
(when (persp-p persp)
(if persp
(setf (persp-hidden persp) nil)
(setq persp-nil-hidden nil)))))
names)))
(cl-defun persp-kill (names &optional dont-kill-buffers
(called-interactively-p (called-interactively-p 'any)))
(interactive "i")
(when (and called-interactively-p current-prefix-arg)
(setq dont-kill-buffers (not dont-kill-buffers)))
(unless (listp names) (setq names (list names)))
(unless names
(setq names (persp-read-persp
(concat "to kill"
(and dont-kill-buffers " not killing buffers"))
t (safe-persp-name (get-current-persp)) t)))
(mapc #'(lambda (pn)
(let ((persp (persp-get-by-name pn)))
(when (persp-p persp)
(when (or (not called-interactively-p)
(not (null persp))
(yes-or-no-p
"Really kill the 'nil' perspective (It'l kill all buffers)?"))
(let ((pfile (persp-parameter 'persp-file persp)))
(cl-case persp-auto-save-persps-to-their-file-before-kill
(persp-file nil)
('nil (setq pfile nil))
(t (unless pfile
(setq pfile persp-auto-save-fname))))
(when pfile
(persp-save-to-file-by-names
pfile *persp-hash* (list pn) t nil)))
(run-hook-with-args 'persp-before-kill-functions persp)
(let (persp-autokill-persp-when-removed-last-buffer)
(if dont-kill-buffers
(let (persp-autokill-buffer-on-remove)
(mapc #'(lambda (b)
(persp-remove-buffer b persp t t nil nil))
(safe-persp-buffers persp)))
(mapc #'(lambda (b)
(persp-remove-buffer b persp t t nil nil))
(safe-persp-buffers persp))))
(when persp
(persp-remove-by-name pn))))))
names))
(defun persp-kill-without-buffers (names)
(interactive "i")
(persp-kill names t nil))
(cl-defun persp-save-and-kill
(names &optional dont-kill-buffers
(called-interactively-p (called-interactively-p 'any)))
(interactive "i")
(when (and called-interactively-p current-prefix-arg)
(setq dont-kill-buffers (not dont-kill-buffers)))
(unless (listp names) (setq names (list names)))
(unless names
(setq names (persp-read-persp
(concat "to save and kill"
(and dont-kill-buffers " not killing buffers"))
t (safe-persp-name (get-current-persp)) t)))
(let ((temphash (make-hash-table :test 'equal :size 10)))
(mapc #'(lambda (p)
(persp-add p temphash))
(mapcar #'(lambda (pn) (persp-get-by-name pn)) names))
(persp-save-state-to-file persp-auto-save-fname temphash
persp-auto-save-persps-to-their-file
'yes)))
(cl-defun persp-rename (new-name
&optional (persp (get-current-persp)) (phash *persp-hash*))
"Change the name field of the `PERSP'.
Return old name on success, otherwise nil."
(interactive "i")
(if persp
(let ((opersp (persp-get-by-name new-name phash))
(old-name (safe-persp-name persp)))
(unless new-name
(setq new-name
(read-string
(concat "New name for the " old-name " perspective: ") old-name)))
(if (and (not (persp-p opersp)) new-name
(not (equal old-name new-name)))
(progn
(when (eq phash *persp-hash*)
(persp-remove-from-menu persp))
(remhash old-name phash)
(setf (persp-name persp) new-name)
(puthash new-name persp phash)
(when (eq phash *persp-hash*)
(persp-add-to-menu persp)
(run-hook-with-args
'persp-renamed-functions persp old-name new-name))
old-name)
(message
"[persp-mode] Error: There is already a perspective with that name: %s."
new-name)
nil))
(message
"[persp-mode] Error: You can't rename the `nil' perspective, use \
M-x: customize-variable RET persp-nil-name RET")
nil))
(cl-defun persp-switch
(name &optional frame (window (selected-window))
(called-interactively-p (called-interactively-p 'any)))
"Switch to the perspective with name `NAME'.
If there is no perspective with that name it will be created.
Return `NAME'."
(interactive "i")
(let ((switch-type 'frame))
(if (or (window-persp-set-p window)
(and called-interactively-p current-prefix-arg))
(setq switch-type 'window)
(unless frame (setq frame (window-frame window))))
(if (eq 'window switch-type)
(persp-window-switch name window)
(persp-frame-switch name frame))))
(cl-defun persp-frame-switch (name &optional (frame (selected-frame)))
(interactive "i")
(unless name
(setq name (persp-read-persp "to switch(in frame)" nil nil nil nil t)))
(unless (memq frame persp-inhibit-switch-for)
(run-hook-with-args 'persp-before-switch-functions name frame)
(let ((persp-inhibit-switch-for (cons frame persp-inhibit-switch-for)))
(persp-activate (persp-add-new name) frame)))
name)
(cl-defun persp-window-switch (name &optional (window (selected-window)))
(interactive "i")
(unless name
(setq name (persp-read-persp "to switch(in window)" nil nil nil nil t)))
(unless (memq window persp-inhibit-switch-for)
(run-hook-with-args 'persp-before-switch-functions name window)
(let ((persp-inhibit-switch-for (cons window persp-inhibit-switch-for)))
(persp-activate (persp-add-new name) window)))
name)
(defun persp-before-make-frame ()
(let ((persp (persp-get-by-name
(or (and persp-set-last-persp-for-new-frames
persp-last-persp-name)
persp-nil-name))))
(unless (persp-p persp)
(when persp-set-last-persp-for-new-frames
(setq persp-last-persp-name persp-nil-name))
(setq persp (persp-add-new persp-nil-name)))
(persp-save-state persp nil t)))
(defun persp--do-auto-action-if-needed (persp)
(when (and (safe-persp-auto persp)
persp-autokill-persp-when-removed-last-buffer
(null (safe-persp-buffers persp)))
(cond
((functionp persp-autokill-persp-when-removed-last-buffer)
(funcall persp-autokill-persp-when-removed-last-buffer persp))
((or
(eq 'hide persp-autokill-persp-when-removed-last-buffer)
(and (eq 'hide-auto persp-autokill-persp-when-removed-last-buffer)
(safe-persp-auto persp)))
(persp-hide (safe-persp-name persp)))
((or
(eq t persp-autokill-persp-when-removed-last-buffer)
(eq 'kill persp-autokill-persp-when-removed-last-buffer)
(and
(eq 'kill-auto persp-autokill-persp-when-removed-last-buffer)
(safe-persp-auto persp)))
(persp-kill (safe-persp-name persp) nil nil)))))
(defsubst persp--deactivate (frame-or-window &optional new-persp)
(let (persp)
(cl-typecase frame-or-window
(frame
(setq persp (get-frame-persp frame-or-window))
(unless (eq persp new-persp)
(with-selected-frame frame-or-window
(run-hook-with-args 'persp-before-deactivate-functions 'frame))
(persp-frame-save-state
frame-or-window
(if persp-set-last-persp-for-new-frames
(equal (safe-persp-name persp) persp-last-persp-name)
(null persp)))))
(window
(setq persp (get-window-persp frame-or-window))
(unless (eq persp new-persp)
(with-selected-window frame-or-window
(run-hook-with-args 'persp-before-deactivate-functions 'window)))))
(let ((persp-inhibit-switch-for
(cons frame-or-window persp-inhibit-switch-for)))
(persp--do-auto-action-if-needed persp))))
(cl-defun persp-activate
(persp &optional (frame-or-window (selected-frame)) new-frame-p)
(when frame-or-window
(let (old-persp type)
(cl-typecase frame-or-window
(frame
(setq old-persp (get-frame-persp frame-or-window)
type 'frame))
(window
(setq old-persp (get-window-persp frame-or-window)
type 'window)))
(when (or new-frame-p
(not (eq old-persp persp)))
(unless new-frame-p
(persp--deactivate frame-or-window persp))
(cl-case type
(frame
(setq persp-last-persp-name (safe-persp-name persp))
(set-frame-persp persp frame-or-window)
(when persp-init-frame-behaviour
(persp-restore-window-conf frame-or-window persp new-frame-p))
(with-selected-frame frame-or-window
(run-hook-with-args 'persp-activated-functions 'frame)))
(window
(set-window-persp persp frame-or-window)
(let ((cbuf (window-buffer frame-or-window)))
(unless (persp-contain-buffer-p cbuf persp)
(persp-set-another-buffer-for-window cbuf frame-or-window persp)))
(with-selected-window frame-or-window
(run-hook-with-args 'persp-activated-functions 'window))))))))
(defun persp-init-new-frame (frame)
(condition-case-unless-debug err
(persp-init-frame frame t (frame-parameter frame 'client))
(error
(message "[persp-mode] Error: Can not initialize frame -- %s"
err))))
(cl-defun persp-init-frame (frame &optional new-frame-p client)
(let ((persp-init-frame-behaviour
(cond
((and client
(not (eql -1 persp-emacsclient-init-frame-behaviour-override)))
persp-emacsclient-init-frame-behaviour-override)
((and (eq this-command 'make-frame)
(not (eql -1 persp-interactive-init-frame-behaviour-override)))
persp-interactive-init-frame-behaviour-override)
((and new-frame-p (not (eql -1 persp-init-new-frame-behaviour-override)))
persp-init-new-frame-behaviour-override)
(t persp-init-frame-behaviour))))
(let (persp-name persp)
(cl-macrolet
((set-default-persp
()
`(progn
(setq persp-name (or (and persp-set-last-persp-for-new-frames
persp-last-persp-name)
persp-nil-name)
persp (persp-get-by-name persp-name))
(unless (persp-p persp)
(setq persp-name persp-nil-name
persp (persp-add-new persp-name))))))
(cl-typecase persp-init-frame-behaviour
(function
(funcall persp-init-frame-behaviour frame new-frame-p))
(string
(setq persp-name persp-init-frame-behaviour
persp (persp-add-new persp-name)))
(symbol
(cl-case persp-init-frame-behaviour
(auto-temp (setq persp-name (persp-gen-random-name)
persp (persp-add-new persp-name))
(when persp
(setf (persp-auto persp) t)))
(prompt (select-frame frame)
(setq persp-name
(persp-read-persp "to switch" nil nil nil nil t)
persp (persp-add-new persp-name)))
(t (set-default-persp))))
(t (set-default-persp))))
(when persp-name
(modify-frame-parameters frame `((persp . nil)))
(when persp-set-frame-buffer-predicate
(persp-set-frame-buffer-predicate frame))
(persp-set-frame-server-switch-hook frame)
(when (or (eq persp-init-frame-behaviour 'persp-ignore-wconf)
(eq persp-init-frame-behaviour 'persp-ignore-wconf-once))
(set-frame-parameter frame persp-init-frame-behaviour t))
(persp-activate persp frame new-frame-p)))))
(defun persp-delete-frame (frame)
(condition-case-unless-debug err
(persp--deactivate frame persp-not-persp)
(error
(message "[persp-mode] Error: Can not deactivate frame -- %s"
err))))
;; TODO: rename
(cl-defun find-other-frame-with-persp (&optional (persp (get-frame-persp))
(exframe (selected-frame))
for-save)
(let ((flist (delq exframe (persp-frames-with-persp persp))))
(cl-find-if
#'(lambda (f)
(and f
(if for-save
(and (not (frame-parameter f 'persp-ignore-wconf))
(not (frame-parameter f 'persp-ignore-wconf-once)))
t)
(eq persp (get-frame-persp f))))
flist)))
;; Helper funcs:
(defun persp-add-minor-mode-menu ()
(easy-menu-define persp-minor-mode-menu
persp-mode-map
"The menu for the `persp-mode'."
'("Perspectives"
"-")))
(defun persp-remove-from-menu (persp)
(let ((name (safe-persp-name persp)))
(cl-psetq persp-names-cache (cl-delete name persp-names-cache :count 1))
(easy-menu-remove-item persp-minor-mode-menu nil name)
(when persp
(easy-menu-remove-item persp-minor-mode-menu '("kill") name))))
(defun persp-add-to-menu (persp)
(let ((name (safe-persp-name persp)))
(cl-psetq persp-names-cache
(append persp-names-cache (list name)))
(let ((str_name name))
(easy-menu-add-item persp-minor-mode-menu nil
(vector str_name #'(lambda () (interactive)
(persp-switch str_name))))
(when persp
(easy-menu-add-item persp-minor-mode-menu '("kill")
(vector str_name #'(lambda () (interactive)
(persp-kill str_name))))))))
(cl-defun persp-read-persp
(&optional action multiple default require-match delnil delcur persp-list
show-hidden (default-mode t))
"Read perspective name(s)."
(when persp-names-sort-before-read-function
(cl-psetq persp-names-cache
(funcall persp-names-sort-before-read-function
persp-names-cache)))
(cl-psetq persp-list
(if persp-list
(cl-delete-if-not #'(lambda (pn) (member pn persp-list))
(persp-names-current-frame-fast-ordered))
(persp-names-current-frame-fast-ordered)))
(when delnil
(setq persp-list (cl-delete persp-nil-name persp-list :count 1)))
(when delcur
(setq persp-list (cl-delete (safe-persp-name (get-current-persp)) persp-list :count 1)))
(unless show-hidden
(setq persp-list
(cl-delete-if #'safe-persp-hidden persp-list :key #'persp-get-by-name)))
(when (and default (not (member default persp-list)))
(setq default nil))
(let (retlst)
(cl-macrolet
((call-pif
()
`(funcall
persp-interactive-completion-function
(concat
"Perspective name" (and multiple "s") (and action " ") action
(if default (concat " (default " default ")") "")
(when retlst
(concat "< " (mapconcat #'identity retlst " ") " > "))
": ")
persp-list nil require-match nil nil default)))
(if multiple
(let ((done_str "[>done<]") (not-finished default-mode)
exit-minibuffer-function mb-local-key-map
(push-keys (alist-get 'push-item persp-read-multiple-keys))
(pop-keys (alist-get 'pop-item persp-read-multiple-keys))
push-keys-backup pop-keys-backup)
(while (member done_str persp-list)
(setq done_str (concat ">" done_str)))
(let ((persp-minibuffer-setup
#'(lambda ()
(setq mb-local-key-map (current-local-map))
(when (keymapp mb-local-key-map)
(unless exit-minibuffer-function
(setq exit-minibuffer-function
(or (lookup-key mb-local-key-map (kbd "RET"))
persp-read-multiple-exit-minibuffer-function)))
(unless push-keys-backup
(setq push-keys-backup
(lookup-key mb-local-key-map push-keys)))
(define-key mb-local-key-map push-keys
#'(lambda () (interactive)
(setq not-finished 'push)
(funcall exit-minibuffer-function)))
(unless pop-keys-backup
(setq pop-keys-backup
(lookup-key mb-local-key-map pop-keys)))
(define-key mb-local-key-map pop-keys
#'(lambda () (interactive)
(setq not-finished 'pop)
(funcall exit-minibuffer-function))))))
cp)
(unwind-protect
(progn
(add-hook 'minibuffer-setup-hook persp-minibuffer-setup t)
(while not-finished
(setq cp (call-pif))
(cl-case not-finished
(push
(when (and cp (member cp persp-list))
(if retlst
(when (string= cp done_str)
(setq not-finished nil))
(push done_str persp-list))
(when not-finished
(if (eq 'reverse multiple)
(setq retlst (append retlst (list cp)))
(push cp retlst))
(setq persp-list (cl-delete cp persp-list :count 1)
default done_str)))
(when not-finished
(setq not-finished default-mode)))
(pop
(let ((last-item (pop retlst)))
(unless retlst (setq persp-list (cl-delete done_str persp-list :count 1)
default nil))
(when last-item
(push last-item persp-list)))
(setq not-finished default-mode))
(t
(when (and cp (not (string= cp done_str))
(member cp persp-list))
(push cp retlst))
(setq not-finished nil)))))
(remove-hook 'minibuffer-setup-hook persp-minibuffer-setup)
(when (keymapp mb-local-key-map)
(when (lookup-key mb-local-key-map push-keys)
(define-key mb-local-key-map push-keys push-keys-backup))
(when (lookup-key mb-local-key-map pop-keys)
(define-key mb-local-key-map pop-keys pop-keys-backup)))))
retlst)
(call-pif)))))
(define-obsolete-function-alias 'persp-prompt 'persp-read-persp "persp-mode 2.9")
(defsubst persp--set-frame-buffer-predicate-buffer-list-cache (buflist)
(prog1
(setq persp-frame-buffer-predicate-buffer-list-cache buflist)
(unless persp-frame-buffer-predicate-buffer-list-cache
(setq persp-frame-buffer-predicate-buffer-list-cache :nil))
(run-at-time
2 nil #'(lambda ()
(setq persp-frame-buffer-predicate-buffer-list-cache nil)))))
(defmacro persp--get-frame-buffer-predicate-buffer-list-cache (buflist)
`(if persp-frame-buffer-predicate-buffer-list-cache
(if (eq :nil persp-frame-buffer-predicate-buffer-list-cache)
nil
persp-frame-buffer-predicate-buffer-list-cache)
(persp--set-frame-buffer-predicate-buffer-list-cache ,buflist)))
(defun persp-generate-frame-buffer-predicate (opt)
(if opt
(eval
`(lambda (b)
(if (string-prefix-p " *temp*" (buffer-name (current-buffer)))
t
,(cl-typecase opt
(function
`(funcall (with-no-warnings ',opt) b))
(number
`(let ((*persp-restrict-buffers-to* ,opt))
(memq
b (persp--get-frame-buffer-predicate-buffer-list-cache
(let ((ret
(persp-buffer-list-restricted
(selected-frame) ,opt
persp-restrict-buffers-to-if-foreign-buffer t)))
(if (get-current-persp)
ret
(cl-delete-if #'persp-buffer-filtered-out-p ret)))))))
(symbol
(cl-case opt
('nil t)
(restricted-buffer-list
'(progn
(memq
b (persp--get-frame-buffer-predicate-buffer-list-cache
(let ((ret
(persp-buffer-list-restricted
(selected-frame)
*persp-restrict-buffers-to*
persp-restrict-buffers-to-if-foreign-buffer
t)))
(if (get-current-persp)
ret
(cl-delete-if #'persp-buffer-filtered-out-p ret)))))))
(t '(memq
b (persp--get-frame-buffer-predicate-buffer-list-cache
(let ((ret (safe-persp-buffers (get-current-persp))))
(if (get-current-persp)
ret
(cl-delete-if #'persp-buffer-filtered-out-p ret))))))))
(t t)))))
nil))
(defun persp-set-frame-buffer-predicate (frame &optional off)
(let ((old-pred (frame-parameter frame 'persp-buffer-predicate-old))
(cur-pred (frame-parameter frame 'buffer-predicate))
(last-persp-pred
(frame-parameter frame 'persp-buffer-predicate-generated)))
(let (new-pred)
(if off
(progn
(set-frame-parameter frame 'persp-buffer-predicate-old nil)
(set-frame-parameter frame 'persp-buffer-predicate-generated nil)
(setq new-pred (if (eq cur-pred last-persp-pred) old-pred cur-pred))
(set-frame-parameter frame 'buffer-predicate new-pred))
(unless persp-frame-buffer-predicate
(setq persp-frame-buffer-predicate
(persp-generate-frame-buffer-predicate
persp-set-frame-buffer-predicate)))
(if persp-frame-buffer-predicate
(progn
(set-frame-parameter frame 'persp-buffer-predicate-old
(if (eq cur-pred last-persp-pred)
old-pred (setq old-pred cur-pred)))
(setq new-pred
(cl-case old-pred
('nil persp-frame-buffer-predicate)
(t `(lambda (b)
(and
(funcall (with-no-warnings
',persp-frame-buffer-predicate)
b)
(funcall (with-no-warnings ',old-pred) b))))))
(unless (symbolp new-pred)
(setq new-pred (with-no-warnings
(let ((warning-minimum-level :emergency)
byte-compile-warnings)
(byte-compile new-pred)))))
(set-frame-parameter
frame 'persp-buffer-predicate-generated new-pred)
(set-frame-parameter frame 'buffer-predicate new-pred))
(persp-set-frame-buffer-predicate frame t))))))
(defun persp-update-frames-buffer-predicate (&optional off)
(unless off
(setq persp-frame-buffer-predicate nil)
(persp-update-frames-buffer-predicate t))
(mapc #'(lambda (f) (persp-set-frame-buffer-predicate f off))
(persp-frame-list-without-daemon)))
(defun persp-generate-frame-server-switch-hook (opt)
(if opt
(eval
`(lambda (frame)
,(if (functionp opt)
`(funcall (with-no-warnings ',opt) frame)
`(let* ((frame-client (frame-parameter frame 'client))
(frame-client-bl (when (processp frame-client)
(process-get frame-client 'buffers))))
,(cl-case opt
(only-file-windows
`(if frame-client
(when frame-client-bl
(mapc #'(lambda (w)
(unless (memq (window-buffer w)
frame-client-bl)
(delete-window w)))
(window-list frame 'no-minibuf)))
(let (frame-server-bl)
(mapc #'(lambda (proc)
(setq frame-server-bl
(append frame-server-bl
(process-get proc 'buffers))))
(server-clients-with 'frame nil))
(when frame-server-bl
(mapc #'(lambda (w)
(unless (memq (window-buffer w)
frame-server-bl)
(delete-window w)))
(window-list frame 'no-minibuf))))))
(only-file-windows-for-client-frame
`(when frame-client-bl
(mapc #'(lambda (w)
(unless (memq (window-buffer w) frame-client-bl)
(delete-window w)))
(window-list frame 'no-minibuf))))
(t nil))))))
nil))
(defun persp-set-frame-server-switch-hook (frame)
(when (frame-parameter frame 'client)
(set-frame-parameter
frame 'persp-server-switch-hook persp-frame-server-switch-hook)))
(defun persp-update-frame-server-switch-hook ()
(setq persp-frame-server-switch-hook
(persp-generate-frame-server-switch-hook persp-server-switch-behaviour))
(mapc #'persp-set-frame-server-switch-hook
(persp-frame-list-without-daemon)))
(defun persp-ido-setup ()
(when (eq ido-cur-item 'buffer)
(setq persp-disable-buffer-restriction-once nil)))
(defun persp-restrict-ido-buffers ()
"Support for the `ido-mode'."
(let ((buffer-names-sorted
(if persp-disable-buffer-restriction-once
(mapcar #'buffer-name (persp-buffer-list-restricted nil -1 nil))
(mapcar #'buffer-name (persp-buffer-list-restricted))))
(indices (make-hash-table)))
(let ((i 0))
(dolist (elt ido-temp-list)
(puthash elt i indices)
(setq i (1+ i))))
(setq ido-temp-list
(sort buffer-names-sorted #'(lambda (a b)
(< (gethash a indices 10000)
(gethash b indices 10000)))))))
;; TODO: rename
(defun ido-toggle-persp-filter ()
(interactive)
(setq persp-disable-buffer-restriction-once
(not persp-disable-buffer-restriction-once)
ido-text-init ido-text ido-exit 'refresh)
(exit-minibuffer))
(cl-defun persp-read-buffer
(prompt &optional default require-match predicate multiple (default-mode t))
"Read buffers with restriction."
(setq persp-disable-buffer-restriction-once nil)
(when default
(unless (stringp default)
(if (and (bufferp default) (buffer-live-p default))
(setq default (buffer-name default))
(setq default nil))))
(if prompt
(setq prompt (car (split-string prompt ": *$" t)))
(setq prompt "Please provide a buffer name: "))
(let* ((buffer-names (mapcar #'buffer-name (persp-buffer-list-restricted)))
cp retlst
(done_str "[>done<]") (not-finished default-mode)
(push-keys (alist-get 'push-item persp-read-multiple-keys))
(pop-keys (alist-get 'pop-item persp-read-multiple-keys))
push-keys-backup pop-keys-backup
(toggle-filter-keys
(alist-get 'toggle-persp-buffer-filter persp-read-multiple-keys))
toggle-filter-keys-backup
exit-minibuffer-function mb-local-key-map
(persp-minibuffer-setup
#'(lambda ()
(setq mb-local-key-map (current-local-map))
(when (keymapp mb-local-key-map)
(unless exit-minibuffer-function
(setq exit-minibuffer-function
(or (lookup-key mb-local-key-map (kbd "RET"))
persp-read-multiple-exit-minibuffer-function)))
(unless toggle-filter-keys-backup
(setq toggle-filter-keys-backup
(lookup-key mb-local-key-map toggle-filter-keys)))
(define-key mb-local-key-map toggle-filter-keys
#'(lambda () (interactive)
(setq not-finished 'toggle-filter)
(funcall exit-minibuffer-function))))))
(persp-multiple-minibuffer-setup
#'(lambda ()
(when (keymapp mb-local-key-map)
(unless push-keys-backup
(setq push-keys-backup
(lookup-key mb-local-key-map push-keys)))
(define-key mb-local-key-map push-keys
#'(lambda () (interactive)
(setq not-finished 'push)
(funcall exit-minibuffer-function)))
(unless pop-keys-backup
(setq pop-keys-backup
(lookup-key mb-local-key-map pop-keys)))
(define-key mb-local-key-map pop-keys
#'(lambda () (interactive)
(setq not-finished 'pop)
(funcall exit-minibuffer-function)))))))
(while (member done_str buffer-names)
(setq done_str (concat ">" done_str)))
(unwind-protect
(progn
(when (and default (not (member default buffer-names)))
(push default buffer-names)
;; TODO: remove this
;; (setq default nil)
)
(when multiple
(add-hook 'minibuffer-setup-hook persp-multiple-minibuffer-setup))
(add-hook 'minibuffer-setup-hook persp-minibuffer-setup)
(while not-finished
(setq cp
(funcall
persp-interactive-completion-function
(concat prompt
(and default (concat "(default " default ")"))
(and retlst
(concat
"< " (mapconcat #'identity retlst " ") " >"))
": ")
buffer-names predicate require-match nil nil default))
(cl-case not-finished
(push
(when (and cp (member cp buffer-names))
(if retlst
(when (string= cp done_str)
(setq not-finished nil))
(push done_str buffer-names))
(when not-finished
(if (eq 'reverse multiple)
(setq retlst (append retlst (list cp)))
(push cp retlst))
(setq buffer-names (cl-delete cp buffer-names :count 1)
default done_str)))
(when not-finished
(setq not-finished default-mode)))
(pop
(let ((last-item (pop retlst)))
(unless retlst (setq buffer-names (cl-delete done_str buffer-names :count 1)
default nil))
(when last-item
(push last-item buffer-names)))
(setq not-finished default-mode))
(toggle-filter
(setq persp-disable-buffer-restriction-once
(not persp-disable-buffer-restriction-once))
(setq buffer-names
(cl-delete-if
#'(lambda (bn) (member bn retlst))
(mapcar #'buffer-name
(if persp-disable-buffer-restriction-once
(funcall persp-buffer-list-function)
(cl-delete-if #'persp-buffer-filtered-out-p
(persp-buffer-list-restricted))))))
(setq not-finished default-mode))
(t
(when (and cp (not (string= cp done_str))
(member cp buffer-names))
(push cp retlst))
(setq not-finished nil))))
(if multiple retlst (car retlst)))
(remove-hook 'minibuffer-setup-hook persp-multiple-minibuffer-setup)
(remove-hook 'minibuffer-setup-hook persp-minibuffer-setup)
(when (keymapp mb-local-key-map)
(when multiple
(when (lookup-key mb-local-key-map push-keys)
(define-key mb-local-key-map push-keys push-keys-backup))
(when (lookup-key mb-local-key-map pop-keys)
(define-key mb-local-key-map pop-keys pop-keys-backup)))
(when (lookup-key mb-local-key-map toggle-filter-keys)
(define-key mb-local-key-map toggle-filter-keys
toggle-filter-keys-backup)))
(setq persp-disable-buffer-restriction-once nil))))
;; Save/Load funcs:
(defun persp-delete-other-windows ()
(let ((win (selected-window)))
(when (window-parameter win 'window-side)
(setq win (cl-loop
for win in (window-list nil 1)
unless (window-parameter win 'window-side)
return win)))
(when win
(let ((ignore-window-parameters t))
(delete-other-windows win)))))
(cl-defun persp-restore-window-conf (&optional (frame (selected-frame))
(persp (get-frame-persp frame))
new-frame-p)
(when new-frame-p (sit-for 0.01))
(unless (run-hook-with-args-until-success 'persp-restore-window-conf-filter-functions
frame persp new-frame-p)
(with-selected-frame frame
(let ((pwc (safe-persp-window-conf persp))
(split-width-threshold 2)
(split-height-threshold 2)
(window-safe-min-height 1)
(window-safe-min-width 1)
(window-min-height 1)
(window-min-width 1)
(window-resize-pixelwise t)
(gr-mode (and (boundp 'golden-ratio-mode) golden-ratio-mode)))
(when gr-mode
(golden-ratio-mode -1))
(unwind-protect
(cond
((functionp persp-restore-window-conf-method)
(funcall persp-restore-window-conf-method frame persp new-frame-p))
((null persp-restore-window-conf-method) nil)
(t
(if pwc
(progn
(persp-delete-other-windows)
(set-window-dedicated-p nil nil)
(condition-case-unless-debug err
(funcall persp-window-state-put-function pwc frame)
(error
(message
"[persp-mode] Warning: Can not restore the window \
configuration, because of the error -- %s" err)
(let* ((cw (selected-window))
(cwb (window-buffer cw)))
(unless (persp-contain-buffer-p cwb persp)
(persp-set-another-buffer-for-window
cwb cw persp)))))
(when (and new-frame-p persp-is-ibc-as-f-supported)
(setq initial-buffer-choice
#'(lambda () persp-special-last-buffer))))
(when persp-reset-windows-on-nil-window-conf
(if (functionp persp-reset-windows-on-nil-window-conf)
(funcall persp-reset-windows-on-nil-window-conf)
(persp-delete-other-windows)
(set-window-dedicated-p nil nil)
(let* ((pbs (safe-persp-buffers persp))
(w (selected-window))
(wb (window-buffer w)))
(when (and pbs (not (memq wb pbs)))
(persp-set-another-buffer-for-window wb w persp))))))))
(when gr-mode
(golden-ratio-mode 1)))))))
;; Save funcs
(cl-defun persp-frame-save-state
(&optional (frame (selected-frame)) set-persp-special-last-buffer)
(when (and (frame-live-p frame)
(not (persp-is-frame-daemons-frame frame))
(not (frame-parameter frame 'persp-ignore-wconf))
(not (frame-parameter frame 'persp-ignore-wconf-once)))
(let ((persp (get-frame-persp frame)))
(with-selected-frame frame
(when set-persp-special-last-buffer
(persp-special-last-buffer-make-current))
(if persp
(setf (persp-window-conf persp)
(funcall persp-window-state-get-function frame))
(setq persp-nil-wconf
(funcall persp-window-state-get-function frame)))))))
(cl-defun persp-save-state
(&optional (persp (get-frame-persp)) exfr set-persp-special-last-buffer)
(let ((frame (selected-frame)))
(when (eq frame exfr) (setq frame nil))
(unless (and frame (eq persp (get-frame-persp frame)))
(setq frame (find-other-frame-with-persp persp exfr t)))
(when frame (persp-frame-save-state frame set-persp-special-last-buffer))))
(defun persp-buffers-to-savelist (persp)
(cl-delete-if
#'symbolp
(let (find-ret)
(mapcar #'(lambda (b)
(setq find-ret nil)
(cl-find-if #'(lambda (sl) (when sl (setq find-ret sl)))
persp-save-buffer-functions
:key #'(lambda (s-f) (with-current-buffer b
(funcall s-f b))))
find-ret)
(if persp
(persp-buffers persp)
(cl-delete-if-not #'persp-buffer-free-p
(funcall persp-buffer-list-function)))))))
(defun persp-window-conf-to-savelist (persp)
`(def-wconf ,(if (or persp-use-workgroups
(not (version< emacs-version "24.4")))
(safe-persp-window-conf persp)
nil)))
(defun persp-elisp-object-readable-p (obj)
(let (print-length print-level)
(or (stringp obj)
(not (string-match-p "#<.*?>" (prin1-to-string obj))))))
(defun persp-parameters-to-savelist (persp)
`(def-params ,(cl-remove-if
#'(lambda (param)
(and (not (persp-elisp-object-readable-p param))
(message "[persp-mode] Info: The parameter %S \
of the perspective %s can't be saved."
param (safe-persp-name persp))
t))
(safe-persp-parameters persp))))
(defun persp-to-savelist (persp)
`(def-persp ,(and persp (persp-name persp))
,(persp-buffers-to-savelist persp)
,(persp-window-conf-to-savelist persp)
,(persp-parameters-to-savelist persp)
,(safe-persp-weak persp)
,(safe-persp-auto persp)
,(safe-persp-hidden persp)))
(defun persps-to-savelist (&optional phash names-regexp)
(mapcar
#'persp-to-savelist
(cl-delete-if
(apply-partially #'persp-parameter 'dont-save-to-file)
(if (eq phash *persp-hash*)
(mapcar #'(lambda (pn)
(when (or (not names-regexp)
(persp-string-match-p names-regexp pn))
(persp-get-by-name pn *persp-hash* nil)))
(persp-names-current-frame-fast-ordered))
(persp-persps (or phash *persp-hash*) names-regexp t)))))
(defsubst persp-save-with-backups (fname)
(when (and (string= fname
(concat (expand-file-name persp-save-dir)
persp-auto-save-fname))
(> persp-auto-save-num-of-backups 0))
(cl-do ((cur persp-auto-save-num-of-backups (1- cur))
(prev (1- persp-auto-save-num-of-backups) (1- prev)))
((> 1 cur) nil)
(let ((cf (concat fname (number-to-string cur)))
(pf (concat fname (if (> prev 0)
(number-to-string prev)
""))))
(when (file-exists-p pf)
(when (file-exists-p cf)
(delete-file cf))
(rename-file pf cf t))))
(when (file-exists-p fname)
(rename-file fname (concat fname (number-to-string 1)) t)))
(write-file fname nil)
t)
(cl-defun persp-save-state-to-file
(&optional
(fname persp-auto-save-fname) (phash *persp-hash*)
(respect-persp-file-parameter persp-auto-save-persps-to-their-file)
(keep-others-in-non-parametric-file 'no))
(interactive (list (read-file-name "Save perspectives to a file: "
persp-save-dir "")))
(when (and (stringp fname) phash)
(when (< (string-width (file-name-nondirectory fname)) 1)
(message "[persp-mode] Error: You must provide nonempty filename to save perspectives.")
(cl-return-from persp-save-state-to-file nil))
(let* ((p-save-dir (or (file-name-directory fname)
(expand-file-name persp-save-dir)))
(p-save-file (concat p-save-dir (file-name-nondirectory fname))))
(unless (and (file-exists-p p-save-dir)
(file-directory-p p-save-dir))
(message "[persp-mode] Info: Trying to create the `persp-conf-dir'.")
(make-directory p-save-dir t))
(if (not (and (file-exists-p p-save-dir)
(file-directory-p p-save-dir)))
(progn
(message "[persp-mode] Error: Can't save perspectives -- \
`persp-save-dir' does not exists or not a directory %S." p-save-dir)
nil)
(mapc #'persp-save-state (persp-persps phash))
(run-hook-with-args 'persp-before-save-state-to-file-functions
fname phash respect-persp-file-parameter)
(if (and respect-persp-file-parameter
(cl-member-if (apply-partially #'persp-parameter 'persp-file)
(persp-persps phash nil)))
(let (persp-auto-save-persps-to-their-file
persp-before-save-state-to-file-functions)
(mapc #'(lambda (gr)
(cl-destructuring-bind (pfname . pl) gr
(let ((names (mapcar #'safe-persp-name pl)))
(if pfname
(persp-save-to-file-by-names
pfname phash names 'yes nil)
(persp-save-to-file-by-names
p-save-file phash names
keep-others-in-non-parametric-file nil)))))
(persp-group-by
(apply-partially #'persp-parameter 'persp-file)
(persp-persps phash nil t) t)))
(with-temp-buffer
(buffer-disable-undo)
(erase-buffer)
(goto-char (point-min))
(insert
";; -*- mode: emacs-lisp; eval: (progn (pp-buffer) (indent-buffer)) -*-")
(newline)
(insert (let (print-length print-level)
(pp-to-string (persps-to-savelist phash))))
(persp-save-with-backups p-save-file)))))))
(cl-defun persp-save-to-file-by-names
(&optional (fname persp-auto-save-fname) (phash *persp-hash*) names
keep-others (called-interactively-p (called-interactively-p 'any)))
(interactive)
(unless names
(setq names
(persp-read-persp
"to save" 'reverse (safe-persp-name (get-current-persp))
t nil nil nil nil 'push)))
(when (or (not fname) called-interactively-p)
(setq fname (read-file-name
(format "Save a subset of perspectives%s to a file: " names)
persp-save-dir)))
(when names
(unless keep-others
(setq keep-others
(if (and (file-exists-p fname)
(yes-or-no-p "Keep other perspectives in the file?"))
'yes 'no)))
(let ((temphash (make-hash-table :test 'equal :size 10))
(persp-nil-wconf persp-nil-wconf)
(persp-nil-parameters (copy-tree persp-nil-parameters))
(persp-nil-hidden persp-nil-hidden)
bufferlist-diff)
(when (or (eq keep-others 'yes) (eq keep-others t))
(let ((bufferlist-pre
(mapcar #'(lambda (b) (cons b (persp--buffer-in-persps b)))
(funcall persp-buffer-list-function))))
(persp-load-state-from-file
fname temphash (cons :not (regexp-opt names)))
(setq bufferlist-diff
(cl-delete-if #'(lambda (bcons)
(when bcons
(cl-destructuring-bind (buf . buf-persps) bcons
(when buf
(persp--buffer-in-persps-set buf buf-persps)
t))))
(funcall persp-buffer-list-function)
:key #'(lambda (b) (assq b bufferlist-pre))))))
(mapc #'(lambda (p)
(persp-add p temphash)
(when (and p persp-auto-save-persps-to-their-file)
(set-persp-parameter 'persp-file fname p)))
(mapcar #'(lambda (pn) (persp-get-by-name pn phash)) names))
(persp-save-state-to-file fname temphash nil)
(mapc #'kill-buffer bufferlist-diff))))
(defun persp-tramp-save-buffer (b)
(let* ((buf-f-name (buffer-file-name b))
(persp-tramp-file-name
(when (and (or (featurep 'tramp) (require 'tramp nil t))
(tramp-tramp-file-p buf-f-name))
(let ((dissected-f-name (tramp-dissect-file-name buf-f-name))
tmh)
(if (tramp-file-name-hop dissected-f-name)
(when (and
(or (featurep 'tramp-sh) (require 'tramp-sh nil t))
(fboundp 'tramp-compute-multi-hops)
(setq tmh
(condition-case-unless-debug err
(tramp-compute-multi-hops dissected-f-name)
(error nil))))
(let ((persp-tramp-file-name tramp-prefix-format))
(while tmh
(let* ((hop (car tmh))
(method (tramp-file-name-method hop))
(user (tramp-file-name-user hop))
(host (tramp-file-name-host hop))
(filename (tramp-file-name-localname hop)))
(setq persp-tramp-file-name
(concat
persp-tramp-file-name
method tramp-postfix-method-format
user (when user tramp-postfix-user-format)
host (if (= (string-width filename) 0)
tramp-postfix-hop-format
(concat
tramp-postfix-host-format filename)))
tmh (cdr tmh))))
persp-tramp-file-name))
buf-f-name)))))
(when persp-tramp-file-name
`(def-buffer ,(buffer-name b)
,persp-tramp-file-name
,(buffer-local-value 'major-mode b)))))
;; Load funcs
(defun persp-update-frames-window-confs (&optional persp-names)
(mapc #'persp-restore-window-conf
(if persp-names
(cl-delete-if-not
#'(lambda (pn) (member pn persp-names))
(persp-frame-list-without-daemon)
:key #'(lambda (f) (safe-persp-name (get-frame-persp f))))
(persp-frame-list-without-daemon))))
(defmacro persp-car-as-fun-cdr-as-args (lst)
(let ((kar (gensym "lst-car")))
`(let* ((,kar (car-safe ,lst))
(args (cdr-safe ,lst))
(fun (or (condition-case-unless-debug err
(symbol-function ,kar)
(error nil))
(symbol-value ,kar))))
(if (functionp fun)
(apply fun args)
(message "[persp-mode] Error: %s is not a function." fun)))))
(defvar def-buffer nil)
(defun persp-buffer-from-savelist (savelist)
(when (eq (car savelist) 'def-buffer)
(let (persp-add-buffer-on-find-file
(def-buffer
#'(lambda (name fname mode &optional parameters)
(let ((buf (persp-get-buffer-or-null name)))
(if buf
(if (or (null fname)
(string= fname (buffer-file-name buf)))
buf
(if (file-exists-p fname)
(setq buf (find-file-noselect fname))
(message
"[persp-mode] Warning: The file %s no longer exists."
fname)
(setq buf nil)))
(if (and fname (file-exists-p fname))
(setq buf (find-file-noselect fname))
(when fname
(message
"[persp-mode] Warning: The file %s no longer exists."
fname))
(setq buf (get-buffer-create name))))
(when (buffer-live-p buf)
(cl-macrolet
((restorevars
()
`(mapc
#'(lambda (varcons)
(cl-destructuring-bind (vname . vvalue) varcons
(unless (or (eq vname 'buffer-file-name)
(eq vname 'major-mode))
(set (make-local-variable vname) vvalue))))
(alist-get 'local-vars parameters))))
(with-current-buffer buf
(restorevars)
(cond
((and (boundp 'persp-load-buffer-mode-restore-function)
(variable-binding-locus 'persp-load-buffer-mode-restore-function)
(functionp persp-load-buffer-mode-restore-function))
(funcall persp-load-buffer-mode-restore-function mode)
(restorevars))
((functionp mode)
(when (and (not (eq major-mode mode))
(not (eq major-mode 'not-loaded-yet)))
(funcall mode)
(restorevars)))))))
buf))))
(persp-car-as-fun-cdr-as-args savelist))))
(defun persp-buffers-from-savelist-0 (savelist)
(cl-delete-if-not
#'persp-get-buffer-or-null
(let (find-ret)
(mapcar #'(lambda (saved-buf)
(setq find-ret nil)
(cl-find-if #'(lambda (lb) (when lb (setq find-ret lb)))
persp-load-buffer-functions
:key #'(lambda (l-f) (funcall l-f saved-buf)))
find-ret)
savelist))))
(defvar def-wconf nil)
(defun persp-window-conf-from-savelist-0 (savelist)
(let ((def-wconf #'identity))
(persp-car-as-fun-cdr-as-args savelist)))
(defvar def-params nil)
(defun persp-parameters-from-savelist-0 (savelist)
(let ((def-params #'identity))
(persp-car-as-fun-cdr-as-args savelist)))
(defvar def-persp nil)
(defun persp-from-savelist-0 (savelist phash persp-file)
(let ((def-persp
#'(lambda (name dbufs dwc &optional dparams weak auto hidden)
(let* ((pname (or name persp-nil-name))
(persp (persp-add-new pname phash)))
(mapc #'(lambda (b)
(persp-add-buffer b persp nil nil))
(persp-buffers-from-savelist-0 dbufs))
(if persp
(setf (persp-window-conf persp)
(persp-window-conf-from-savelist-0 dwc))
(setq persp-nil-wconf
(persp-window-conf-from-savelist-0 dwc)))
(modify-persp-parameters
(persp-parameters-from-savelist-0 dparams) persp)
(when persp
(setf (persp-weak persp) weak
(persp-auto persp) auto))
(if persp
(setf (persp-hidden persp) hidden)
(setq persp-nil-hidden hidden))
(when persp-file
(set-persp-parameter 'persp-file persp-file persp))
pname))))
(persp-car-as-fun-cdr-as-args savelist)))
(defun persps-from-savelist-0
(savelist phash persp-file set-persp-file names-regexp)
(when (and names-regexp (not (consp names-regexp)))
(setq names-regexp (cons t names-regexp)))
(mapcar #'(lambda (pd)
(persp-from-savelist-0 pd phash (and set-persp-file persp-file)))
(if names-regexp
(cl-delete-if-not
(apply-partially #'persp-string-match-p names-regexp)
savelist
:key #'(lambda (pd) (or (cadr pd) persp-nil-name)))
savelist)))
(defun persp-names-from-savelist-0 (savelist)
(mapcar #'(lambda (pd) (or (cadr pd) persp-nil-name)) savelist))
(defun persps-savelist-version-string (savelist)
(let* ((version-list (car savelist))
(version (or (and (eq (car version-list)
'def-persp-save-format-version)
(cadr version-list))
0)))
(list
(format "%S" version)
(if (eql version 0)
savelist
(cdr savelist)))))
(defun persp-dispatch-loadf-version (funsym savelist)
(cl-destructuring-bind (version s-list)
(persps-savelist-version-string savelist)
(let ((funame (intern (concat (symbol-name funsym) "-" version))))
(if (fboundp funame)
(list funame s-list)
(message
"[persp-mode] Warning: Can not find load function for this version: %S."
version)
(list nil s-list)))))
(defun persps-from-savelist
(savelist phash persp-file set-persp-file names-regexp)
(cl-destructuring-bind (fun s-list)
(persp-dispatch-loadf-version 'persps-from-savelist savelist)
(if fun
(let ((persp-names
(funcall fun s-list phash persp-file set-persp-file names-regexp)))
(run-hook-with-args 'persp-after-load-state-functions persp-file phash
persp-names)
persp-names)
(message
"[persp-mode] Error: Can not load perspectives from savelist: %s
\tloaded from %s" savelist persp-file)
nil)))
(defun persp-list-persp-names-in-file (fname)
(when (and fname (file-exists-p fname))
(let* ((pslist (with-temp-buffer
(buffer-disable-undo)
(insert-file-contents fname nil nil nil t)
(goto-char (point-min))
(read (current-buffer)))))
(cl-destructuring-bind (fun s-list)
(persp-dispatch-loadf-version 'persp-names-from-savelist pslist)
(if fun
(funcall fun s-list)
(message
"[persp-mode] Error: Can not list perspective names in file %S."
fname))))))
(cl-defun persp-load-state-from-file
(&optional (fname persp-auto-save-fname) (phash *persp-hash*)
names-regexp set-persp-file)
(interactive (list (read-file-name "Load perspectives from a file: "
persp-save-dir)))
(when fname
(let ((p-save-file (concat (or (file-name-directory fname)
(expand-file-name persp-save-dir))
(file-name-nondirectory fname))))
(if (not (file-exists-p p-save-file))
(progn (message "[persp-mode] Error: No such file -- %S." p-save-file)
nil)
(let ((readed-list
(with-temp-buffer
(buffer-disable-undo)
(insert-file-contents p-save-file nil nil nil t)
(goto-char (point-min))
(read (current-buffer)))))
(persps-from-savelist
readed-list phash p-save-file set-persp-file names-regexp))))))
(cl-defun persp-load-from-file-by-names (&optional (fname persp-auto-save-fname)
(phash *persp-hash*)
names)
(interactive
(list (read-file-name "Load a subset of perspectives from a file: "
persp-save-dir)))
(unless names
(let* ((p-save-file (concat (or (file-name-directory fname)
(expand-file-name persp-save-dir))
(file-name-nondirectory fname)))
(available-names (persp-list-persp-names-in-file p-save-file)))
(setq names
(persp-read-persp
"to load" 'reverse nil t nil nil available-names nil 'push))))
(when names
(let ((names-regexp (regexp-opt names)))
(persp-load-state-from-file fname phash names-regexp t))))
(provide 'persp-mode)
;;; persp-mode.el ends here