2019-04-21 19:59:44 -04:00
|
|
|
;;; ui/workspaces/config.el -*- lexical-binding: t; -*-
|
2017-02-04 03:21:04 -05:00
|
|
|
|
2017-02-08 01:54:24 -05:00
|
|
|
;; `persp-mode' gives me workspaces, a workspace-restricted `buffer-list', and
|
2017-06-14 21:03:20 +02:00
|
|
|
;; file-based session persistence. I used workgroups2 before this, but abandoned
|
|
|
|
;; it because it was unstable and slow; `persp-mode' is neither (and still
|
|
|
|
;; maintained).
|
2017-02-11 00:52:25 -05:00
|
|
|
;;
|
2017-09-02 16:39:51 +02:00
|
|
|
;; NOTE persp-mode requires `workgroups' for file persistence in Emacs 24.4.
|
2017-02-11 00:52:25 -05:00
|
|
|
|
2017-04-10 18:21:42 -04:00
|
|
|
(defvar +workspaces-main "main"
|
2018-07-09 15:33:31 +02:00
|
|
|
"The name of the primary and initial workspace, which cannot be deleted.")
|
2017-04-10 18:21:42 -04:00
|
|
|
|
2018-02-20 17:56:38 -05:00
|
|
|
(defvar +workspaces-switch-project-function #'doom-project-find-file
|
|
|
|
"The function to run after `projectile-switch-project' or
|
|
|
|
`counsel-projectile-switch-project'. This function must take one argument: the
|
|
|
|
new project directory.")
|
|
|
|
|
2018-10-03 00:00:56 -04:00
|
|
|
(defvar +workspaces-on-switch-project-behavior 'non-empty
|
|
|
|
"Controls the behavior of workspaces when switching to a new project.
|
|
|
|
|
|
|
|
Can be one of the following:
|
|
|
|
|
|
|
|
t Always create a new workspace for the project
|
2019-03-08 12:49:15 +10:00
|
|
|
'non-empty Only create a new workspace if the current one already has buffers
|
2018-10-03 00:00:56 -04:00
|
|
|
associated with it.
|
|
|
|
nil Never create a new workspace on project switch.")
|
|
|
|
|
2019-03-02 01:04:41 -05:00
|
|
|
;; FIXME actually use this for wconf bookmark system
|
|
|
|
(defvar +workspaces-data-file "_workspaces"
|
|
|
|
"The basename of the file to store single workspace perspectives. Will be
|
|
|
|
stored in `persp-save-dir'.")
|
2018-06-23 16:48:58 +02:00
|
|
|
|
2019-06-25 11:21:39 +02:00
|
|
|
(defvar +workspace--old-uniquify-style nil)
|
|
|
|
|
2017-02-04 03:21:04 -05:00
|
|
|
|
2017-04-11 08:31:00 -04:00
|
|
|
;;
|
2018-09-07 19:36:16 -04:00
|
|
|
;; Packages
|
2017-04-11 08:31:00 -04:00
|
|
|
|
2019-07-23 12:44:03 +02:00
|
|
|
(use-package! persp-mode
|
2021-10-18 00:42:17 +02:00
|
|
|
:unless noninteractive
|
2019-07-22 00:52:05 +02:00
|
|
|
:commands persp-switch-to-buffer
|
2021-10-18 00:42:17 +02:00
|
|
|
:hook (doom-init-ui . persp-mode)
|
2017-02-19 18:40:39 -05:00
|
|
|
:config
|
2017-02-08 01:54:24 -05:00
|
|
|
(setq persp-autokill-buffer-on-remove 'kill-weak
|
2020-04-29 14:44:51 -04:00
|
|
|
persp-reset-windows-on-nil-window-conf nil
|
2017-04-12 11:27:31 -04:00
|
|
|
persp-nil-hidden t
|
2017-02-11 00:52:25 -05:00
|
|
|
persp-auto-save-fname "autosave"
|
2017-11-04 22:34:55 +01:00
|
|
|
persp-save-dir (concat doom-etc-dir "workspaces/")
|
2018-01-20 02:44:12 -05:00
|
|
|
persp-set-last-persp-for-new-frames t
|
2017-02-22 04:28:20 -05:00
|
|
|
persp-switch-to-added-buffer nil
|
2020-06-13 17:12:06 -04:00
|
|
|
persp-kill-foreign-buffer-behaviour 'kill
|
2017-04-10 18:21:42 -04:00
|
|
|
persp-remove-buffers-from-nil-persp-behaviour nil
|
2018-02-01 19:01:49 -05:00
|
|
|
persp-auto-resume-time -1 ; Don't auto-load on startup
|
|
|
|
persp-auto-save-opt (if noninteractive 0 1)) ; auto-save on kill
|
2017-02-04 03:21:04 -05:00
|
|
|
|
2019-07-22 00:52:05 +02:00
|
|
|
|
2021-08-04 01:31:48 -04:00
|
|
|
;;;; Create main workspace
|
|
|
|
;; The default perspective persp-mode creates is special and doesn't represent
|
|
|
|
;; a real persp object, so buffers can't really be assigned to it, among other
|
|
|
|
;; quirks, so I replace it with a "main" perspective.
|
2019-07-22 00:52:05 +02:00
|
|
|
(add-hook! '(persp-mode-hook persp-after-load-state-functions)
|
2019-12-22 19:59:22 -05:00
|
|
|
(defun +workspaces-ensure-no-nil-workspaces-h (&rest _)
|
|
|
|
(when persp-mode
|
|
|
|
(dolist (frame (frame-list))
|
|
|
|
(when (string= (safe-persp-name (get-current-persp frame)) persp-nil-name)
|
|
|
|
;; Take extra steps to ensure no frame ends up in the nil perspective
|
|
|
|
(persp-frame-switch (or (cadr (hash-table-keys *persp-hash*))
|
|
|
|
+workspaces-main)
|
|
|
|
frame))))))
|
|
|
|
|
|
|
|
(add-hook! 'persp-mode-hook
|
|
|
|
(defun +workspaces-init-first-workspace-h (&rest _)
|
|
|
|
"Ensure a main workspace exists."
|
2019-07-22 00:52:05 +02:00
|
|
|
(when persp-mode
|
|
|
|
(let (persp-before-switch-functions)
|
2021-08-04 01:31:48 -04:00
|
|
|
;; Try our best to hide the nil perspective.
|
2020-02-06 13:39:21 -05:00
|
|
|
(when (equal (car persp-names-cache) persp-nil-name)
|
|
|
|
(pop persp-names-cache))
|
2020-02-28 16:29:49 -05:00
|
|
|
;; ...and create a *real* main workspace to fill this role.
|
2019-12-22 19:59:22 -05:00
|
|
|
(unless (or (persp-get-by-name +workspaces-main)
|
|
|
|
;; Start from 2 b/c persp-mode counts the nil workspace
|
|
|
|
(> (hash-table-count *persp-hash*) 2))
|
2019-07-22 00:52:05 +02:00
|
|
|
(persp-add-new +workspaces-main))
|
2019-12-22 19:59:22 -05:00
|
|
|
;; HACK Fix #319: the warnings buffer gets swallowed when creating
|
|
|
|
;; `+workspaces-main', so display it ourselves, if it exists.
|
|
|
|
(when-let (warnings (get-buffer "*Warnings*"))
|
|
|
|
(save-excursion
|
|
|
|
(display-buffer-in-side-window
|
|
|
|
warnings '((window-height . shrink-window-if-larger-than-buffer))))))))
|
2019-07-22 00:52:05 +02:00
|
|
|
(defun +workspaces-init-persp-mode-h ()
|
|
|
|
(cond (persp-mode
|
|
|
|
;; `uniquify' breaks persp-mode. It renames old buffers, which causes
|
|
|
|
;; errors when switching between perspective (their buffers are
|
|
|
|
;; serialized by name and persp-mode expects them to have the same
|
|
|
|
;; name when restored).
|
|
|
|
(when uniquify-buffer-name-style
|
|
|
|
(setq +workspace--old-uniquify-style uniquify-buffer-name-style))
|
|
|
|
(setq uniquify-buffer-name-style nil)
|
|
|
|
;; Ensure `persp-kill-buffer-query-function' is last
|
|
|
|
(remove-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function)
|
|
|
|
(add-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function t)
|
|
|
|
;; Restrict buffer list to workspace
|
|
|
|
(advice-add #'doom-buffer-list :override #'+workspace-buffer-list))
|
|
|
|
(t
|
|
|
|
(when +workspace--old-uniquify-style
|
|
|
|
(setq uniquify-buffer-name-style +workspace--old-uniquify-style))
|
|
|
|
(advice-remove #'doom-buffer-list #'+workspace-buffer-list)))))
|
2018-03-22 06:30:49 -04:00
|
|
|
|
2020-11-18 19:59:21 -05:00
|
|
|
;; Per-workspace `winner-mode' history
|
|
|
|
(add-to-list 'window-persistent-parameters '(winner-ring . t))
|
|
|
|
|
|
|
|
(add-hook! 'persp-before-deactivate-functions
|
|
|
|
(defun +workspaces-save-winner-data-h (_)
|
|
|
|
(when (and (bound-and-true-p winner-mode)
|
|
|
|
(get-current-persp))
|
|
|
|
(set-persp-parameter
|
|
|
|
'winner-ring (list winner-currents
|
|
|
|
winner-ring-alist
|
|
|
|
winner-pending-undo-ring)))))
|
|
|
|
|
|
|
|
(add-hook! 'persp-activated-functions
|
|
|
|
(defun +workspaces-load-winner-data-h (_)
|
|
|
|
(when (bound-and-true-p winner-mode)
|
|
|
|
(cl-destructuring-bind
|
|
|
|
(currents alist pending-undo-ring)
|
|
|
|
(or (persp-parameter 'winner-ring) (list nil nil nil))
|
|
|
|
(setq winner-undo-frame nil
|
|
|
|
winner-currents currents
|
|
|
|
winner-ring-alist alist
|
|
|
|
winner-pending-undo-ring pending-undo-ring)))))
|
|
|
|
|
2021-05-28 11:43:40 -04:00
|
|
|
;;;; Registering buffers to perspectives
|
|
|
|
(add-hook! 'doom-switch-buffer-hook
|
2019-07-22 00:52:05 +02:00
|
|
|
(defun +workspaces-add-current-buffer-h ()
|
|
|
|
"Add current buffer to focused perspective."
|
2020-02-11 17:47:39 -05:00
|
|
|
(or (not persp-mode)
|
|
|
|
(persp-buffer-filtered-out-p
|
|
|
|
(or (buffer-base-buffer (current-buffer))
|
|
|
|
(current-buffer))
|
|
|
|
persp-add-buffer-on-after-change-major-mode-filter-functions)
|
|
|
|
(persp-add-buffer (current-buffer) (get-current-persp) nil nil))))
|
2019-06-25 11:21:39 +02:00
|
|
|
|
2019-07-27 17:08:03 +02:00
|
|
|
(add-hook 'persp-add-buffer-on-after-change-major-mode-filter-functions
|
|
|
|
#'doom-unreal-buffer-p)
|
2018-05-13 23:16:06 +02:00
|
|
|
|
2019-07-23 17:24:56 +02:00
|
|
|
(defadvice! +workspaces--evil-alternate-buffer-a (&optional window)
|
2019-05-18 21:27:23 -04:00
|
|
|
"Make `evil-alternate-buffer' ignore buffers outside the current workspace."
|
2019-07-22 00:52:05 +02:00
|
|
|
:override #'evil-alternate-buffer
|
|
|
|
(let* ((prev-buffers
|
|
|
|
(if persp-mode
|
|
|
|
(cl-remove-if-not #'persp-contain-buffer-p (window-prev-buffers)
|
|
|
|
:key #'car)
|
|
|
|
(window-prev-buffers)))
|
2019-05-18 21:27:23 -04:00
|
|
|
(head (car prev-buffers)))
|
|
|
|
(if (eq (car head) (window-buffer window))
|
|
|
|
(cadr prev-buffers)
|
|
|
|
head)))
|
|
|
|
|
2020-11-01 23:38:42 -05:00
|
|
|
;; HACK Fixes #4196, #1525: selecting deleted buffer error when quitting Emacs
|
|
|
|
;; or on some buffer listing ops.
|
|
|
|
(defadvice! +workspaces-remove-dead-buffers-a (persp)
|
|
|
|
:before #'persp-buffers-to-savelist
|
2020-11-02 23:49:42 -05:00
|
|
|
(when (perspective-p persp)
|
|
|
|
;; HACK Can't use `persp-buffers' because of a race condition with its gv
|
|
|
|
;; getter/setter not being defined in time.
|
|
|
|
(setf (aref persp 2)
|
2020-11-01 23:38:42 -05:00
|
|
|
(cl-delete-if-not #'persp-get-buffer-or-null (persp-buffers persp)))))
|
|
|
|
|
2018-07-09 15:33:31 +02:00
|
|
|
;; Delete the current workspace if closing the last open window
|
2018-06-05 19:50:56 +02:00
|
|
|
(define-key! persp-mode-map
|
|
|
|
[remap delete-window] #'+workspace/close-window-or-workspace
|
2019-08-08 23:58:41 -04:00
|
|
|
[remap evil-window-delete] #'+workspace/close-window-or-workspace)
|
2017-06-28 15:16:30 +02:00
|
|
|
|
2018-01-03 13:24:11 -05:00
|
|
|
;; per-frame workspaces
|
2018-01-31 01:13:56 -05:00
|
|
|
(setq persp-init-frame-behaviour t
|
|
|
|
persp-init-new-frame-behaviour-override nil
|
2019-07-22 00:52:05 +02:00
|
|
|
persp-interactive-init-frame-behaviour-override #'+workspaces-associate-frame-fn
|
|
|
|
persp-emacsclient-init-frame-behaviour-override #'+workspaces-associate-frame-fn)
|
|
|
|
(add-hook 'delete-frame-functions #'+workspaces-delete-associated-workspace-h)
|
2021-03-05 20:07:32 -05:00
|
|
|
(add-hook 'server-done-hook #'+workspaces-delete-associated-workspace-h)
|
2017-06-28 15:16:30 +02:00
|
|
|
|
2018-03-22 06:30:49 -04:00
|
|
|
;; per-project workspaces, but reuse current workspace if empty
|
2021-04-29 14:32:02 +03:00
|
|
|
;; HACK?? needs review
|
|
|
|
(setq projectile-switch-project-action (lambda () (+workspaces-set-project-action-fn) (+workspaces-switch-to-project-h))
|
2018-07-04 23:59:36 +08:00
|
|
|
counsel-projectile-switch-project-action
|
2019-07-22 00:52:05 +02:00
|
|
|
'(1 ("o" +workspaces-switch-to-project-h "open project in new workspace")
|
2018-07-04 23:59:36 +08:00
|
|
|
("O" counsel-projectile-switch-project-action "jump to a project buffer or file")
|
|
|
|
("f" counsel-projectile-switch-project-action-find-file "jump to a project file")
|
|
|
|
("d" counsel-projectile-switch-project-action-find-dir "jump to a project directory")
|
2020-08-19 17:37:00 +05:30
|
|
|
("D" counsel-projectile-switch-project-action-dired "open project in dired")
|
2018-07-04 23:59:36 +08:00
|
|
|
("b" counsel-projectile-switch-project-action-switch-to-buffer "jump to a project buffer")
|
|
|
|
("m" counsel-projectile-switch-project-action-find-file-manually "find file manually from project root")
|
|
|
|
("w" counsel-projectile-switch-project-action-save-all-buffers "save all project buffers")
|
|
|
|
("k" counsel-projectile-switch-project-action-kill-buffers "kill all project buffers")
|
|
|
|
("r" counsel-projectile-switch-project-action-remove-known-project "remove project from known projects")
|
|
|
|
("c" counsel-projectile-switch-project-action-compile "run project compilation command")
|
|
|
|
("C" counsel-projectile-switch-project-action-configure "run project configure command")
|
|
|
|
("e" counsel-projectile-switch-project-action-edit-dir-locals "edit project dir-locals")
|
|
|
|
("v" counsel-projectile-switch-project-action-vc "open project in vc-dir / magit / monky")
|
2019-12-05 14:56:16 -05:00
|
|
|
("s" (lambda (project)
|
|
|
|
(let ((projectile-switch-project-action
|
|
|
|
(lambda () (call-interactively #'+ivy/project-search))))
|
|
|
|
(counsel-projectile-switch-project-by-name project))) "search project")
|
2018-07-04 23:59:36 +08:00
|
|
|
("xs" counsel-projectile-switch-project-action-run-shell "invoke shell from project root")
|
|
|
|
("xe" counsel-projectile-switch-project-action-run-eshell "invoke eshell from project root")
|
|
|
|
("xt" counsel-projectile-switch-project-action-run-term "invoke term from project root")
|
|
|
|
("X" counsel-projectile-switch-project-action-org-capture "org-capture into project")))
|
|
|
|
|
2020-02-29 00:03:40 -05:00
|
|
|
(when (featurep! :completion helm)
|
|
|
|
(after! helm-projectile
|
|
|
|
(setcar helm-source-projectile-projects-actions
|
|
|
|
'("Switch to Project" . +workspaces-switch-to-project-h))))
|
|
|
|
|
2021-08-04 01:31:48 -04:00
|
|
|
;; Don't bother auto-saving the session if no real buffers are open.
|
|
|
|
(advice-add #'persp-asave-on-exit :around #'+workspaces-autosave-real-buffers-a)
|
|
|
|
|
2019-10-28 12:13:58 -04:00
|
|
|
;; Fix #1973: visual selection surviving workspace changes
|
|
|
|
(add-hook 'persp-before-deactivate-functions #'deactivate-mark)
|
|
|
|
|
2019-06-25 11:55:00 +02:00
|
|
|
;; Fix #1017: stop session persistence from restoring a broken posframe
|
|
|
|
(after! posframe
|
2019-07-28 14:52:59 +02:00
|
|
|
(add-hook! 'persp-after-load-state-functions
|
2019-07-22 00:52:05 +02:00
|
|
|
(defun +workspaces-delete-all-posframes-h (&rest _)
|
|
|
|
(posframe-delete-all))))
|
2019-06-25 11:55:00 +02:00
|
|
|
|
2021-08-04 01:31:48 -04:00
|
|
|
;; Don't try to persist dead/remote buffers. They cause errors.
|
2020-08-11 19:38:42 -04:00
|
|
|
(add-hook! 'persp-filter-save-buffers-functions
|
|
|
|
(defun +workspaces-dead-buffer-p (buf)
|
|
|
|
;; Fix #1525: Ignore dead buffers in PERSP's buffer list
|
|
|
|
(not (buffer-live-p buf)))
|
|
|
|
(defun +workspaces-remote-buffer-p (buf)
|
|
|
|
;; And don't save TRAMP buffers; they're super slow to restore
|
|
|
|
(let ((dir (buffer-local-value 'default-directory buf)))
|
|
|
|
(ignore-errors (file-remote-p dir)))))
|
2019-08-21 00:22:52 -04:00
|
|
|
|
2020-07-28 16:06:37 -04:00
|
|
|
;; Otherwise, buffers opened via bookmarks aren't treated as "real" and are
|
|
|
|
;; excluded from the buffer list.
|
|
|
|
(add-hook 'bookmark-after-jump-hook #'+workspaces-add-current-buffer-h)
|
|
|
|
|
2021-08-04 01:31:48 -04:00
|
|
|
;;; eshell
|
2018-04-21 21:04:34 -04:00
|
|
|
(persp-def-buffer-save/load
|
|
|
|
:mode 'eshell-mode :tag-symbol 'def-eshell-buffer
|
|
|
|
:save-vars '(major-mode default-directory))
|
|
|
|
;; compile
|
|
|
|
(persp-def-buffer-save/load
|
|
|
|
:mode 'compilation-mode :tag-symbol 'def-compilation-buffer
|
2021-08-04 01:31:48 -04:00
|
|
|
:save-vars '(major-mode default-directory compilation-directory
|
|
|
|
compilation-environment compilation-arguments))
|
2021-09-29 17:59:51 +02:00
|
|
|
;; magit
|
|
|
|
(persp-def-buffer-save/load
|
|
|
|
:mode 'magit-status-mode :tag-symbol 'def-magit-status-buffer
|
|
|
|
:save-vars '(default-directory)
|
|
|
|
:load-function (fn! ((_ _ vars-list &rest _) &rest _)
|
|
|
|
(magit-status (alist-get 'default-directory vars-list))))
|
2018-06-19 14:50:27 +02:00
|
|
|
;; Restore indirect buffers
|
|
|
|
(defvar +workspaces--indirect-buffers-to-restore nil)
|
|
|
|
(persp-def-buffer-save/load
|
|
|
|
:tag-symbol 'def-indirect-buffer
|
|
|
|
:predicate #'buffer-base-buffer
|
|
|
|
:save-function (lambda (buf tag vars)
|
|
|
|
(list tag (buffer-name buf) vars
|
2019-07-08 21:21:02 +02:00
|
|
|
(buffer-name (buffer-base-buffer buf))))
|
2018-06-19 14:50:27 +02:00
|
|
|
:load-function (lambda (savelist &rest _rest)
|
2018-06-30 02:53:35 +02:00
|
|
|
(cl-destructuring-bind (buf-name _vars base-buf-name &rest _)
|
2018-06-26 01:46:34 +02:00
|
|
|
(cdr savelist)
|
2018-06-19 14:50:27 +02:00
|
|
|
(push (cons buf-name base-buf-name)
|
|
|
|
+workspaces--indirect-buffers-to-restore)
|
|
|
|
nil)))
|
2019-08-17 16:04:41 -04:00
|
|
|
(add-hook! 'persp-after-load-state-functions
|
2019-07-22 00:52:05 +02:00
|
|
|
(defun +workspaces-reload-indirect-buffers-h (&rest _)
|
|
|
|
(dolist (ibc +workspaces--indirect-buffers-to-restore)
|
|
|
|
(cl-destructuring-bind (buffer-name . base-buffer-name) ibc
|
2020-10-05 16:35:58 -04:00
|
|
|
(let ((base-buffer (get-buffer base-buffer-name)))
|
|
|
|
(when (buffer-live-p base-buffer)
|
|
|
|
(when (get-buffer buffer-name)
|
|
|
|
(setq buffer-name (generate-new-buffer-name buffer-name)))
|
|
|
|
(make-indirect-buffer base-buffer buffer-name t)))))
|
2019-07-22 00:52:05 +02:00
|
|
|
(setq +workspaces--indirect-buffers-to-restore nil))))
|