diff --git a/core/autoload/buffers.el b/core/autoload/buffers.el index a43ecbdc0..797574a05 100644 --- a/core/autoload/buffers.el +++ b/core/autoload/buffers.el @@ -54,10 +54,11 @@ Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/" If ALL-P is non-nil, get all buffers across all projects in current workgroup." - (let ((buffers (if (featurep 'workgroups2) - (let ((assoc-bufs (wg-workgroup-associated-buffers nil))) - (--filter (memq it assoc-bufs) (buffer-list))) - (buffer-list))) + (let ((buffers (cond ((and (featurep 'workgroups2) workgroups-mode) + (wg-workgroup-associated-buffers nil)) + ((and (featurep 'persp-mode) persp-mode) + (persp-buffer-list-restricted)) + (t (buffer-list)))) (project-root (and (not all-p) (doom-project-root t)))) (append (if project-root (funcall (if (eq all-p 'not) '-remove '-filter) diff --git a/modules/feature/workspaces/autoload.el b/modules/feature/workspaces/autoload.el index e6b5c25ec..199cafe8c 100644 --- a/modules/feature/workspaces/autoload.el +++ b/modules/feature/workspaces/autoload.el @@ -1,273 +1,430 @@ ;;; feature/workspaces/autoload.el -;;;###autoload -(defun +workspace-cleanup () - "Remove unsavable windows and buffers before we save the window -configuration." - (let (doom-buffer-inhibit-refresh) - (doom/popup-close-all) - (when (and (featurep 'neotree) (neo-global--window-exists-p)) - (neotree-hide)))) +(defvar +workspace-session-file "_sessions" + "The file basename in which to store entire perspective sessions.") + +(defvar +workspace-workspace-file "_workspaces" + "The file basename in which to store single workspace perspectives.") + +(defvar +workspace--counter 1 + "") +(add-to-list 'savehist-additional-variables '+workspace-names) + +(defface +workspace-tab-selected-face + '((((class color) (background light)) + (:background "#333333" :foreground "#000000")) ;; FIXME + (((class color) (background dark)) + (:background "#51afef" :foreground "#181e26"))) + "The face for selected tabs displayed by `+workspace/display'") + +(defface +workspace-tab-face + '((((class color) (background light)) + (:background "#333333" :foreground "#000000")) ;; FIXME + (((type graphic) (class color) (background dark)) + (:background "#23272e" :foreground "#5B6268")) + (((class color) (background dark)) + (:background "#262626" :foreground "#525252"))) + "The face for selected tabs displayed by `+workspace/display'") ;;;###autoload -(defun +workspace-save (session-file) - "Save the current workspace as SESSION-FILE. Ensure a workgroup exists to be -saved." - (+workspace-cleanup) - (unless (wg-workgroup-list) - (wg-create-workgroup wg-first-wg-name)) - (wg-save-session-as session-file)) +(defun +workspace-list (&optional exclude-nil-p) + "Retrieve a list of names of open workspaces (strings)." + (let ((persps (persp-names))) + (if exclude-nil-p + (delete persp-nil-name persps) + persps))) ;;;###autoload -(defun +workspace-load (session-file) - "Load a workspace from SESSION-FILE." - (+workspace-cleanup) - (when (not (and session-file (file-exists-p session-file))) - (user-error "No session found")) - (wg-open-session session-file)) +(defun +workspace-p (obj) + "Return t if OBJ is a perspective hash table." + (and (hash-table-p obj) + (perspective-p obj))) ;;;###autoload -(defun +workspace-new (&optional session-name overwrite-p) - "Create a new workspace, named SESSION-NAME (optional) and return it (without -switching to it). Otherwise, it's named #N, where N is the number of current -workgroups + 1." - (unless session-name - (setq session-name (format "#%s" (1+ (length (wg-session-workgroup-list (wg-current-session t))))))) - (awhen (and overwrite-p (wg-get-workgroup session-name t)) - (wg-delete-workgroup new-wg)) - (let ((new-wg (wg-make-and-add-workgroup session-name t))) - (add-to-list '+workspace-names (wg-workgroup-uid new-wg)) - new-wg)) +(defun +workspace-exists-p (name) + "Returns t if NAME is the name of an existing workspace." + (when (symbolp name) + (setq name (symbol-name name))) + (unless (stringp name) + (error "Expected a string, got a %s" (type-of name))) + (member name (+workspace-list))) ;;;###autoload -(defun +workspace-rename (new-name) - "Rename the current workspace to NEW-NAME." - (unless new-name - (user-error "You didn't enter in a name")) - (let ((wg (wg-current-workgroup))) - (wg-rename-workgroup new-name wg) - (add-to-list '+workspace-names (wg-workgroup-uid wg)) - wg)) +(defun +workspace-get (name &optional noerror) + "Returns a workspace (perspective hash table) named NAME." + (unless (equal name persp-nil-name) + (let ((persp (persp-get-by-name name))) + (unless (or persp noerror) + (error "%s is not an available workspace" name)) + persp))) ;;;###autoload -(defun +workspace-delete (&optional session-name) - "Delete the current workspace." - (let* ((current-wg (wg-current-workgroup t)) - (wg (if session-name (wg-get-workgroup session-name t) current-wg))) - (unless wg - (user-error "No workgroup found with that name: %s" session-name)) - (setq +workspace-names (delete (wg-workgroup-uid wg) +workspace-names)) - (if (eq wg current-wg) - (wg-kill-workgroup) - (wg-delete-workgroup wg)))) +(defun +workspace-current-name () + "Get the name of the currently active workspace." + (safe-persp-name (get-current-persp))) +;;;###autoload +(defun +workspace-load (name) + "Loads and inserts a single workspace (named NAME) into the current session. +Can only retrieve perspectives that were explicitly saved with +`+workspace-save'. + +Returns t if successful, nil otherwise." + (unless (+workspace-exists-p name) + (persp-load-from-file-by-names +workspace-workspace-file *persp-hash* (list name))) + (+workspace-exists-p name)) + +;;;###autoload +(defun +workspace-load-session (&optional name) + "Replace current session with the entire session named NAME. If NAME is nil, +use `persp-auto-save-fname'." + (persp-load-state-from-file (or name persp-auto-save-fname))) + +;;;###autoload +(defun +workspace-save (name) + "Saves a single workspace (TARGET) from the current session. Can be loaded +again with `+workspace-load'. TARGET can be the string name of a workspace or +its perspective hash table. + +Returns t on success, nil otherwise." + (unless (+workspace-exists-p name) + (error "%s is not an available workspace" name)) + (persp-save-to-file-by-names + +workspace-workspace-file *persp-hash* (list name) t) + (memq name (persp-list-persp-names-in-file +workspace-workspace-file))) + +;;;###autoload +(defun +workspace-save-session (&optional name) + "Save a whole session as NAME. If NAME is nil, use `persp-auto-save-fname'. +Return t on success, nil otherwise." + (let ((fname (or name persp-auto-save-fname))) + (and (persp-save-state-to-file fname) t))) + +;;;###autoload +(defun +workspace-new (name) + "Create a new workspace named NAME. If one already exists, return nil. +Otherwise return t on success, nil otherwise." + (when (equal name persp-nil-name) + (error "Can't create a new %s workspace" name)) + (unless (+workspace-exists-p name) + (and (persp-add-new name) t))) + +;;;###autoload +(defun +workspace-rename (name new-name) + "Rename the current workspace named NAME to NEW-NAME. Returns old name on +success, nil otherwise." + (when (equal name persp-nil-name) + (error "Can't rename main workspace")) + (persp-rename new-name (+workspace-get name))) + +;;;###autoload +(defun +workspace-delete (name) + "Delete the workspace denoted by TARGET, which can be the name of a +perspective or its hash table." + (when (equal name persp-nil-name) + (error "Can't delete main workspace")) + (+workspace-get name) ;; error checking + (persp-remove-by-name name)) + +;;;###autoload +(defun +workspace-switch (name) + "Switch to another workspace." + (unless (+workspace-exists-p name) + (error "%s is not an available workspace" name)) + (persp-frame-switch name)) + +(defun +workspace--generate-id () + (let ((numbers (--map (string-to-number (substring it 1)) + (--filter (string-match-p "^#[0-9]+$" it) + (+workspace-list))))) + (if numbers + (1+ (-max numbers)) + 1))) + + +;; +;; Interactive commands +;; + +;;;###autoload +(defun +workspace/load (name) + "Load a workspace and switch to it. If called with C-u, try to reload the +current workspace (by name) from session files." + (interactive + (list + (if current-prefix-arg + (+workspace-current-name) + (completing-read "Workspace to load: " + (persp-list-persp-names-in-file + +workspace-workspace-file))))) + (if (not (+workspace-load name)) + (+workspace-error (format "Couldn't load workspace %s" name)) + (+workspace/switch-to name) + (+workspace/display))) + +;;;###autoload +(defun +workspace/load-session (name) + "Load a session and switch to it. If called with C-u, try to load the last +session." + (interactive + (list + (unless current-prefix-arg + (completing-read + "Session to load: " + (-map 'f-filename + (f--files persp-save-dir + (not (string-prefix-p "_" (f-filename it))))))))) + (+workspace-load-session name) + (+workspace/display)) + +;;;###autoload +(defun +workspace/save (name) + "Save the current workspace. If called with C-u, autosave the current +workspace." + (interactive + (list + (if current-prefix-arg + (+workspace-current-name) + (completing-read "Workspace to save: " (+workspace-list))))) + (if (+workspace-save name) + (+workspace-message (format "%s workspace saved" name) 'success) + (+workspace-error (format "Couldn't save workspace %s" name)))) + +;;;###autoload +(defun +workspace/save-session (&optional name) + "Save the current session. If called with C-u, prompt you for the name to save +the session as." + (interactive + (list + (when current-prefix-arg + (completing-read + "Save session as: " + (-map 'f-filename + (f--files persp-save-dir + (not (string-prefix-p "_" (f-filename it))))))))) + (condition-case ex + (if (+workspace-save-session name) + (+workspace-message (format "Saved session as %s" name) 'success) + (error "Couldn't save session as %s" name)) + '(error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(defun +workspace/rename (new-name) + "Rename the current workspace." + (interactive) + (condition-case ex + (let* ((current-name (+workspace-current-name)) + (old-name (+workspace-rename current-name new-name))) + (unless old-name + (error "Failed to rename %s" current-name)) + (+workspace-message (format "Renamed '%s'->'%s'" old-name new-name) 'success)) + ('error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(defun +workspace/delete (name) + "Delete this workspace. If called with C-u, prompts you for the name of the +workspace to delete." + (interactive + (let ((current-name (+workspace-current-name))) + (list + (if current-prefix-arg + (completing-read (format "Delete workspace (default: %s): " current-name) + (+workspace-list) + nil nil current-name) + current-name)))) + (condition-case ex + (if (+workspace-delete name) + (+workspace-message (format "Deleted %s workspace" name) 'success) + (error "Couldn't delete %s workspace" name)) + ('error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(defun +workspace/kill-session () + "Delete the current session, clears all workspaces, windows and buffers." + (interactive) + (unless (-all-p '+workspace-delete (+workspace-list t)) + (+workspace-error "Could not clear session")) + (+workspace-switch persp-nil-name) + (delete-other-windows-internal) + (switch-to-buffer doom-fallback-buffer) + (--each (unless (eq (buffer-name it) doom-fallback-buffer) + (kill-buffer it)) + (buffer-list))) + +;;;###autoload +(defun +workspace/new (&optional name clone-p) + "Create a new workspace named NAME. If OVERWRITE-P is non-nil, clear any +pre-existing workspace." + (interactive "iP") + (unless name + (setq name (format "#%s" (+workspace--generate-id)))) + (condition-case ex + (let ((exists-p (+workspace-exists-p name))) + (if exists-p + (error "%s already exists" name) + (persp-frame-switch name) + (if clone-p + (--each (persp-add-buffer (window-buffer it) persp nil) + (window-list)) + (delete-other-windows-internal) + (switch-to-buffer doom-fallback-buffer)) + (+workspace/display))) + ('error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(defun +workspace/switch-to (index) + "Switch to a workspace at a given INDEX. A negative number will start from the +end of the workspace list." + (interactive + (list (or current-prefix-arg + (completing-read "Switch to workspace: " (+workspace-list))))) + (when (and (stringp index) (s-numeric? index)) + (setq index (string-to-number index))) + (condition-case ex + (let ((names (+workspace-list))) + (cond ((numberp index) + (let ((dest (nth index names))) + (unless dest + (error "No workspace at #%s" (1+ index))) + (persp-switch dest))) + ((stringp index) + (unless (member index names) + (error "No workspace named %s" index)) + (persp-frame-switch index))) + (unless (called-interactively-p 'interactive) + (+workspace/display))) + ('error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(defun +workspace/switch-to-last () + "Switch to the last workspace." + (interactive) + (+workspace/switch-to (car (reverse (+workspace-list))))) + +;;;###autoload +(defun +workspace/cycle (n) + "Cycle n workspaces to the right (default) or left." + (interactive (list 1)) + (condition-case ex + (let ((spaces (+workspace-list)) + (current-name (+workspace-current-name)) + index new-index) + (unless (and space current-name) + (error "No spaces or current workspace to cycle to/from")) + (setq index (--find-index (eq it current-name) spaces) + new-index (mod (+ index n) (length spaces))) + (unless (= index new-index) + (+workspace-switch new-index)) + (unless (called-interactively-p 'interactive) + (+workpace/display))) + ('error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(defun +workspace/close-window-or-workspace () + "Close the selected window. If it's the last window in the workspace, close +the workspace and move to the next." + (interactive) + (if (doom-popup-p) + (doom/popup-close) + (let ((current-persp-name (+workspace-current-name))) + (cond ((or (equal current-persp-name persp-nil-name) + (not (one-window-p t))) + (delete-window)) + ((> (length (+workspace-list)) 1) + (let* ((names (+workspace-list)) + (index (--find-index (equal current-persp-name it) names))) + (+workspace/delete current-persp-name) + (+workspace-switch (nth (1- index) names)))))))) + + +;; +;; Tabs display in minibuffer +;; + +(defun +workspace--tabline (&optional names) + (let ((names (or names (+workspace-list))) + (current-name (+workspace-current-name))) + (s-join + " " (--map-indexed + (propertize (format " [%s] %s " (1+ it-index) it) + 'face (if (equal current-name it) + '+workspace-tab-selected-face + '+workspace-tab-face)) + names)))) + +(defun +workspace--message-body (message &optional type) + (concat (+workspace--tabline) + (propertize " | " 'face 'font-lock-comment-face) + (propertize message 'face (pcase type + ('error 'error) + ('warn 'warning) + ('success 'success) + ('info 'font-lock-comment-face))))) + +;;;###autoload +(defun +workspace-message (message &optional type) + (message "%s" (+workspace--message-body message type))) + +;;;###autoload +(defun +workspace-error (message &optional noerror) + (funcall (if noerror 'message 'error) "%s" (+workspace--message-body message 'error))) + +;;;###autoload +(defun +workspace/display () + (interactive) + (message "%s" (+workspace--tabline))) + + +;; +;; Evil commands +;; + +;;;###autoload (autoload '+workspace:save-session "feature/workspaces/autoload" nil t) +;;;###autoload (autoload '+workspace:load-session "feature/workspaces/autoload" nil t) ;;;###autoload (autoload '+workspace:save "feature/workspaces/autoload" nil t) ;;;###autoload (autoload '+workspace:load "feature/workspaces/autoload" nil t) ;;;###autoload (autoload '+workspace:new "feature/workspaces/autoload" nil t) ;;;###autoload (autoload '+workspace:rename "feature/workspaces/autoload" nil t) ;;;###autoload (autoload '+workspace:delete "feature/workspaces/autoload" nil t) -;;;###autoload (autoload '+workspace:jump-to "feature/workspaces/autoload" nil t) -;;;###autoload (autoload '+workspace:switch-left "feature/workspaces/autoload" nil t) -;;;###autoload (autoload '+workspace:switch-right "feature/workspaces/autoload" nil t) +;;;###autoload (autoload '+workspace:switch-next "feature/workspaces/autoload" nil t) +;;;###autoload (autoload '+workspace:switch-previous "feature/workspaces/autoload" nil t) (after! evil - (evil-define-command +workspace:save (&optional bang name) - (interactive "") - (+workspace-save - (cond (name (concat wg-workgroup-directory session-name)) - (bang (concat wg-workgroup-directory (f-filename (doom-project-root)))) - (t wg-session-file)))) + (evil-define-command +workspace:save-session (&optional name) + "Ex wrapper around `+workspace/save-session'." + (interactive "") (+workspace/save-session name)) - (evil-define-command +workspace:load (&optional bang name) - (interactive "") - (+workspace-load - (cond (name (concat wg-workgroup-directory session-name)) - (bang - (concat wg-workgroup-directory (f-filename (doom-project-root)))) - (t wg-session-file))) - (+workspace/display t)) + (evil-define-command +workspace:load-session (&optional name) + "Ex wrapper around `+workspace/load-session'." + (interactive "") (+workspace/load-session name)) + + (evil-define-command +workspace:save (&optional name) + "Ex wrapper around `+workspace/save-session'." + (interactive "") (+workspace/save name)) + + (evil-define-command +workspace:load (&optional name) + "Ex wrapper around `+workspace/load-session'." + (interactive "") (+workspace/load name)) (evil-define-command +workspace:new (bang name) - "Create a new workgroup named NAME. If BANG, overwrite any workgroup named -NAME. If NAME is omitted, autogenerate a name." - (interactive "") - (let* ((wg (+workspace-new name bang)) - (wg-name (wg-workgroup-name wg))) - (wg-switch-to-workgroup wg) - (workspace--display-inline (wg-previous-workgroup t) - (format "Created %s" wg-name) - 'success))) + "Ex wrapper around `+workspace/new'. If BANG, clone the current workspace." + (interactive "") (+workspace/new name bang)) - (evil-define-command +workspace:rename (&optional bang new-name) - "Rename the current workgroup to NEW-NAME. If BANG and this workgroup has a -fixed name, un-fix it." - (interactive "") - (let* ((wg (wg-current-workgroup)) - (wg-uid (wg-workgroup-uid wg)) - (old-name (wg-workgroup-name wg))) - (if bang - (progn - (setq +workspace-names (delete wg-uid +workspace-names)) - (workspace--display-inline t (format "Unfixed '%s'" old-name) 'success)) - (+workspace-rename new-name) - (workspace--display-inline nil (format "Renamed '%s'->'%s'" old-name new-name) 'success)))) + (evil-define-command +workspace:rename (new-name) + "Ex wrapper around `+workspace/rename'." + (interactive "") (+workspace/rename new-name)) - (evil-define-command +workspace:delete (&optional bang name) - "Delete the workgroup specified by NAME. If NAME is omitted, delete the -current workgroup. If BANG, prompts the user for which workgroup to delete." - (interactive "") - (when bang - (setq name (wg-read-workgroup-name)) - (unless name - (user-error "You didn't select a workgroup."))) - (let ((wg-name (ignore-errors (wg-workgroup-name (wg-current-workgroup t))))) - (+workspace-delete name) - (workspace--display-inline nil (format "Deleted %s" wg-name) 'success))) + (evil-define-command +workspace:delete () + "Ex wrapper around `+workspace/delete'." + (interactive "") (+workspace/delete (+workspace-current-name))) - (evil-define-command +workspace:jump-to (&optional search) - (interactive "") - (if search - (progn) ;; TODO Fuzzy matching - (awhen (wg-read-workgroup-name) - (wg-switch-to-workgroup it)))) - - (evil-define-command +workspace:switch-left (&optional count) - "Switch to the previous workspace on the right. If COUNT, switch to the workspace -at that index counting from the end." + (evil-define-command +workspace:switch-next (&optional count) + "Switch to next workspace. If COUNT, switch to COUNT-th workspace." (interactive "") - (if count - (+workspace/activate-at (- 0 count)) - (+workspace--switch-in-direction 'left))) + (if count (+workspace/switch-to count) (+workspace/cycle +1))) - (evil-define-command +workspace:switch-right (&optional count) - "Switch to the next workspace on the right. If COUNT, switch to the workspace -at that index." + (evil-define-command +workspace:switch-previous (&optional count) + "Switch to previous workspace. If COUNT, switch to COUNT-th workspace." (interactive "") - (if count - (+workspace/activate-at (max 0 (1- count))) - (+workspace--switch-in-direction 'right)))) + (if count (+workspace/switch-to count) (+workspace/cycle -1)))) -;;;###autoload -(defun +workspace/kill-others () - "Kill all other workspaces, besides the current one." - (interactive) - (let (workgroup (wg-current-workgroup)) - (dolist (w (wg-session-workgroup-list (wg-current-session t))) - (unless (wg-current-workgroup-p w) - (wg-kill-workgroup w))))) - -(defun workspace--display-inline (&optional suppress-update message message-face) - (message "%s%s" (+workspace/display suppress-update t) - (propertize message 'face message-face))) - -;;;###autoload -(defun +workspace/display (&optional suppress-update return-p message) - "Display all the open workspaces in the minibuffer, like tabs." - (interactive) - (awhen (wg-current-session t) - (unless (eq suppress-update t) - (+workgroup--update-names (if (wg-workgroup-p suppress-update) suppress-update))) - (let ((output (wg-display-internal - (lambda (workgroup index) - (if (not workgroup) wg-nowg-string - (wg-element-display - workgroup - (format " [%d] %s " (1+ index) (wg-workgroup-name workgroup)) - 'wg-current-workgroup-p))) - (wg-session-workgroup-list it)))) - (if return-p - output - (message "%s%s" output (or message "")))))) - -(defun +workgroup--update-names (&optional wg) - (let ((wg (or wg (wg-current-workgroup)))) - (unless (member (wg-workgroup-uid wg) +workspace-names) - (ignore-errors - (let ((old-name (wg-workgroup-name wg)) - (new-name (f-filename (doom-project-root)))) - (unless (string= new-name old-name) - (wg-rename-workgroup new-name wg))))))) - -(defun +workspace--switch-in-direction (direction &optional count) - (interactive "") - (assert (memq direction '(left right))) - (condition-case err - (progn - (if count - (wg-switch-to-workgroup-at-index (1- count)) - (funcall (intern (format "wg-switch-to-workgroup-%s" direction)))) - (+workspace/display t)) - (error (+workspace/display t nil (format "Nope! %s" (cadr err)))))) - -;;;###autoload -(defun +workspace-switch-last () - "Switch to the last workspace." - (interactive) - (let ((count (length (wg-session-workgroup-list (wg-current-session t))))) - (+workspace/activate-at (max 0 (1- count))))) - -;;;###autoload -(defun +workspace/activate-at (index) - "Switch to a workspace at a given INDEX. A negative number will start from the -end of the workspace list." - (interactive) - (+workgroup--update-names) - (let ((wg-list (wg-workgroup-list-or-error))) - (when (< index 0) - (setq index (- (length wg-list) (abs index)))) - (let ((wg (nth index wg-list)) - msg) - (if wg - (unless (eq wg (wg-current-workgroup t)) - (wg-switch-to-workgroup-at-index index)) - (setq msg (format "No tab #%s" (1+ index)))) - (+workspace/display t nil msg)))) - -;;;###autoload -(defun +workspace/undo () - (interactive) - (call-interactively (if (wg-current-workgroup t) 'wg-undo-wconfig-change 'winner-undo))) - -;;;###autoload -(defun +workspace/redo () - (interactive) - (call-interactively (if (wg-current-workgroup t) 'wg-redo-wconfig-change 'winner-redo))) - -;;;###autoload -(defun +workspace/new-frame () - "Create a new frame, and create a new, blank workgroup within it. Also ensure -nlinum behaves in the process." - (interactive) - (let ((nlinum-p (and (featurep 'nlinum) - (memq 'nlinum--setup-window window-configuration-change-hook)))) - ;; Disable nlinum to fix elusive "invalid face linum" bug - (remove-hook 'window-configuration-change-hook 'nlinum--setup-window t) - (let ((frame (new-frame)) - (frame-name (format "*new-%s*" (length +workspace-frames)))) - (with-selected-frame frame - (wg-create-workgroup frame-name t) - (add-to-list '+workspace-frames (cons frame frame-name)))) - (when nlinum-p - (add-hook 'window-configuration-change-hook 'nlinum--setup-window nil t)))) - -;;;###autoload -(defun +workspace/close-frame () - (interactive) - (let ((frame (assq (selected-frame) +workspace-frames))) - (if frame - (progn (wg-delete-workgroup (wg-get-workgroup (cdr frame))) - (delete-frame (car frame))) - (delete-frame)))) - -;;;###autoload -(defun +workspace/or-window-close () - (interactive) - (if (doom-popup-p) - (doom/popup-close) - (when (doom/kill-real-buffer) - (if (and (one-window-p t) - (> (length (wg-workgroup-list)) 1)) - (if (string= (wg-workgroup-name (wg-current-workgroup)) wg-first-wg-name) - (evil-window-delete) - (+workspace:delete)) - (evil-window-delete))))) diff --git a/modules/feature/workspaces/config.el b/modules/feature/workspaces/config.el index ef03d5d17..c7364604c 100644 --- a/modules/feature/workspaces/config.el +++ b/modules/feature/workspaces/config.el @@ -1,71 +1,58 @@ ;;; feature/workspaces/config.el -(defvar +workspace-frames '() - "A list of all the frames opened as separate workgroups. See -defuns/defuns-workgroups.el.") +;; `persp-mode' gives me workspaces, a workspace-restricted `buffer-list', and +;; file-based session persistence. The switch from workgroups2 was motivated by +;; performance. Workgroups2 wasn't entirely stable either. -(defvar +workspace-names '() - "Keeps track of the fixed names for workgroups (set with :tabrename), so that -these workgroups won't be auto-renamed.") - -(defvar +workspaces-dir (concat doom-cache-dir "workgroups/") - "Path to workspaces.") - - -(use-package! workgroups2 :demand t +(use-package! persp-mode :demand t :init - (setq wg-workgroup-directory +workspaces-dir - wg-session-file (concat wg-workgroup-directory "last") - wg-first-wg-name "*untitled*" - wg-session-load-on-start nil - wg-mode-line-display-on nil - wg-mess-with-buffer-list nil - wg-emacs-exit-save-behavior 'save ; Options: 'save 'ask nil - wg-workgroups-mode-exit-save-behavior 'save - wg-log-level 0 - - ;; NOTE: Some of these make workgroup-restoration unstable - wg-restore-mark t - wg-restore-frame-position t - wg-restore-remote-buffers nil - wg-restore-scroll-bars nil - wg-restore-fringes nil - wg-restore-margins nil - wg-restore-point-max t ; Throws silent errors if non-nil - - wg-list-display-decor-divider " " - wg-list-display-decor-left-brace "" - wg-list-display-decor-right-brace "| " - wg-list-display-decor-current-left "" - wg-list-display-decor-current-right "" - wg-list-display-decor-previous-left "" - wg-list-display-decor-previous-right "") + (setq persp-autokill-buffer-on-remove 'kill-weak + persp-nil-name "main" + persp-auto-save-fname "_autosave" + persp-save-dir (concat doom-cache-dir "workspaces/") + persp-set-last-persp-for-new-frames nil + persp-auto-resume-time (if (display-graphic-p) 0.01 -1) + persp-switch-to-added-buffer nil) :config - (eval-when-compile - (unless (f-exists-p +workspaces-dir) - (make-directory +workspaces-dir t))) + ;; Ensure unreal/popup buffers aren't saved + (defun +workspaces--filter-unreal (buf) (not (doom-real-buffer-p buf))) + (setq persp-filter-save-buffers-functions (list '+workspaces--filter-unreal)) + (push '+workspaces--filter-unreal persp-common-buffer-filter-functions) - ;; Remember fixed workgroup names between sessions - (push '+workspace-names savehist-additional-variables) + ;; Auto-add buffers when opening them. Allows a perspective-specific buffer list. + (defun doom*persp-auto-add-buffer (buffer &rest _) + (when (and persp-mode (not persp-temporarily-display-buffer)) + (persp-add-buffer buffer (get-current-persp) nil))) + (advice-add 'switch-to-buffer :after 'doom*persp-auto-add-buffer) + (advice-add 'display-buffer :after 'doom*persp-auto-add-buffer) - ;; `wg-mode-line-display-on' wasn't enough - (advice-add 'wg-change-modeline :override 'ignore) - ;; Don't remember popup and neotree windows - (add-hook 'kill-emacs-hook '+workspace-cleanup) + ;; TODO Integration with projectile + ;; ;; Create a new workspace on project switch + ;; (defun doom|new-workspace-on-project-change () + ;; (+workspace-new (f-filename (doom-project-root)))) + ;; (add-hook 'projectile-before-switch-project-hook 'doom|new-workspace-on-project-change) - (after! projectile - ;; Create a new workspace on project switch - ;; FIXME Possibly bug? - (defun doom*workspace-projectile-switch-project () - (let ((project-root (doom-project-root))) - (doom:workspace-new nil (file-name-nondirectory (directory-file-name project-root)) t) - (doom-reload-scratch-buffer project-root) - (when (featurep 'neotree) - (neotree-projectile-action)))) - (setq projectile-switch-project-action 'doom*workspace-projectile-switch-project)) + ;; TODO Test per-frame perspectives + + (persp-mode 1)) + +(after! ivy + (defun +workspaces|ivy-ignore-non-persp-buffers (b) + (when persp-mode + (let ((persp (get-current-persp))) + (and persp (not (persp-contain-buffer-p b persp)))))) + (pushnew '+workspaces|ivy-ignore-non-persp-buffers ivy-ignore-buffers) + + (setq ivy-sort-functions-alist + (append ivy-sort-functions-alist + '((persp-kill-buffer . nil) + (persp-remove-buffer . nil) + (persp-add-buffer . nil) + (persp-switch . nil) + (persp-window-switch . nil) + (persp-frame-switch . nil) + (+workspace/switch-to . nil) + (+workspace/delete . nil))))) - (workgroups-mode +1) - ;; Ensure there is always a workgroup active - (wg-create-workgroup wg-first-wg-name)) diff --git a/modules/feature/workspaces/packages.el b/modules/feature/workspaces/packages.el index 835748527..f3c63aba6 100644 --- a/modules/feature/workspaces/packages.el +++ b/modules/feature/workspaces/packages.el @@ -1,5 +1,5 @@ ;; -*- no-byte-compile: t; -*- ;;; feature/workspaces/packages.el -(package! workgroups2) +(package! persp-mode)