diff --git a/init.el b/init.el index cdf2daa..647b07c 100644 --- a/init.el +++ b/init.el @@ -5,6 +5,7 @@ ;;; and maintainability. ;;; Code: +(server-mode 1) ;; Add lisp subdirectory to load path (avoids load-path warning) (add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory)) diff --git a/lisp/init-completion.el b/lisp/init-completion.el index 7fbe846..7f9df47 100644 --- a/lisp/init-completion.el +++ b/lisp/init-completion.el @@ -20,6 +20,26 @@ ;; Enable cycling (setq vertico-cycle t)) +;; Load vertico extensions +(add-to-list 'load-path + (expand-file-name "extensions" + (file-name-directory (locate-library "vertico")))) + +;; Vertico directory navigation - arrow keys for file browsing +(use-package vertico-directory + :ensure nil ; Bundled with vertico + :after vertico + :bind (:map vertico-map + ;; Navigate up with left arrow + ("" . vertico-directory-up) + ;; Complete/enter with right arrow + ("" . vertico-directory-enter) + ;; Backspace goes up if at beginning + ("DEL" . vertico-directory-delete-char) + ("M-DEL" . vertico-directory-delete-word)) + ;; Tidy shadowed file names + :hook (rfn-eshadow-update-overlay . vertico-directory-tidy)) + ;;; Savehist - Persist history over Emacs restarts (use-package savehist :ensure nil diff --git a/lisp/init-perspective.el b/lisp/init-perspective.el new file mode 100644 index 0000000..cd57e63 --- /dev/null +++ b/lisp/init-perspective.el @@ -0,0 +1,157 @@ +;;; 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