;;; init-perspective.el --- Perspective and Projectile configuration -*- lexical-binding: t -*- ;;; Commentary: ;;; Workspace management with perspective.el integrated with Projectile ;;; Code: ;; Projectile - Project management (use-package projectile :ensure t :diminish projectile-mode :init (setq projectile-keymap-prefix (kbd "C-c p")) :config (projectile-mode +1) ;; Use hybrid indexing for best performance (setq projectile-indexing-method 'hybrid) ;; Enable caching (setq projectile-enable-caching t) ;; Project root markers (setq projectile-project-root-files-top-down-recurring '(".projectile" ".git" ".hg" ".svn")) (setq projectile-project-root-files '("Makefile" "package.json" "Cargo.toml" "go.mod" "pom.xml" "build.gradle" "requirements.txt" "setup.py" "pyproject.toml" "Gemfile" "composer.json")) ;; Sort files by recently opened (setq projectile-sort-order 'recentf) ;; Use completion system (works with vertico/consult) (setq projectile-completion-system 'default) ;; Where to store projectile cache (setq projectile-cache-file (expand-file-name "projectile.cache" user-emacs-directory)) (setq projectile-known-projects-file (expand-file-name "projectile-bookmarks.eld" user-emacs-directory)) ;; Integration with consult if available (with-eval-after-load 'consult (setq consult-project-function (lambda (_) (projectile-project-root))))) ;; Perspective - Workspace management (use-package perspective :ensure t :bind (("C-x x s" . persp-switch) ("C-x x k" . persp-kill) ("C-x x r" . persp-rename) ("C-x x a" . persp-add-buffer) ("C-x x A" . persp-set-buffer) ("C-x x b" . persp-switch-to-buffer) ("C-x x n" . persp-next) ("C-x x p" . persp-prev) ("C-x x i" . persp-import) ("C-x x m" . persp-merge) ("C-x x u" . persp-unmerge) ("C-x x g" . persp-add-buffer-to-frame-global) ("C-x x x" . persp-switch-last)) :custom ;; Show perspective name in mode line (persp-mode-prefix-key (kbd "C-x x")) (persp-initial-frame-name "main") ;; Don't auto-kill buffers when perspective is killed (persp-kill-foreign-buffer-behaviour nil) ;; Save state on exit (persp-state-default-file (expand-file-name "persp-state" user-emacs-directory)) :init (persp-mode +1) :config ;; Show perspective in mode line (setq persp-show-modestring t) ;; Remove/bury buffers not associated with the current perspective when switching (defun my/persp-remove-foreign-buffers () "Bury buffers not belonging to the current perspective." (when (and (boundp 'perspectives-hash) (hash-table-p perspectives-hash)) (let ((persp-buffers (persp-current-buffers))) (dolist (buf (buffer-list)) (unless (or (memq buf persp-buffers) (string-prefix-p " " (buffer-name buf)) ; internal buffers (string-prefix-p "*Messages*" (buffer-name buf)) (string-prefix-p "*scratch*" (buffer-name buf))) (bury-buffer buf)))))) (add-hook 'persp-activated-hook #'my/persp-remove-foreign-buffers) ;; Save and restore perspectives on Emacs quit/start (add-hook 'kill-emacs-hook #'persp-state-save) ;; Restore perspectives on startup (after init) (defun my/persp-restore-state () "Restore perspective state if file exists." (when (file-exists-p persp-state-default-file) (condition-case err (persp-state-load persp-state-default-file) (error (message "Failed to restore perspective state: %s. Resetting perspectives." (error-message-string err)) (my/persp-reset))))) (add-hook 'emacs-startup-hook #'my/persp-restore-state)) ;; Persp-Projectile - Integration between perspective and projectile (use-package persp-projectile :ensure t :after (perspective projectile) :bind (:map projectile-mode-map ("C-c p p" . projectile-persp-switch-project)) :config ;; When switching projects, create/switch to a perspective named after the project (setq projectile-switch-project-action #'projectile-find-file)) ;; Helper functions (defun my/persp-project-switch () "Switch to a project and its perspective." (interactive) (call-interactively #'projectile-persp-switch-project)) (defun my/persp-kill-project-buffers () "Kill all buffers in current perspective/project." (interactive) (if (and (boundp 'perspectives-hash) (hash-table-p perspectives-hash)) (when (y-or-n-p (format "Kill all buffers in perspective '%s'? " (persp-current-name))) (persp-kill-buffer*)) (message "Perspectives not initialized or corrupted. Use M-x my/persp-reset to reset."))) (defun my/persp-reset () "Reset perspectives if they become corrupted." (interactive) (when (boundp 'perspectives-hash) (setq perspectives-hash (make-hash-table :test 'equal))) (persp-mode +1) (message "Perspectives reset.")) (defun my/persp-list () "List all perspectives." (interactive) (if (and (boundp 'perspectives-hash) (hash-table-p perspectives-hash)) (let ((perspectives (persp-names))) (message "Perspectives: %s" (string-join perspectives ", "))) (message "Perspectives not initialized or corrupted. Use M-x my/persp-reset to reset."))) ;; Additional keybindings for convenience (with-eval-after-load 'projectile (define-key projectile-mode-map (kbd "C-c p P") #'my/persp-project-switch) (define-key projectile-mode-map (kbd "C-c p K") #'my/persp-kill-project-buffers) (define-key projectile-mode-map (kbd "C-c p L") #'my/persp-list)) ;; Integration with CUA mode - ensure perspective commands work in special modes (with-eval-after-load 'perspective (dolist (cmd '(persp-switch persp-kill persp-rename persp-next persp-prev)) (put cmd 'CUA 'move))) (provide 'init-perspective) ;;; init-perspective.el ends here