diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f4bfd..4487a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add `eca-chat-remove-workspace-root` command and `[-]` mode-line button to remove a workspace folder from a running session. Both 'add' and 'remove' is in the transient menu (`W a` / `W r`). - Add `eca-chat-mode-line-format` for customizable chat mode line layout. #184 - Fix top-level `(require 'tab-line)` causing side effects when `eca-chat-tab-line` is nil. #195 - Add support for `chat/opened` server notification, enabling the `/fork` command to open forked chats as new tabs. diff --git a/eca-chat.el b/eca-chat.el index 4df7fea..e7c2cc5 100644 --- a/eca-chat.el +++ b/eca-chat.el @@ -167,7 +167,7 @@ Must be a valid model supported by server, check `eca-chat-select-model`." :group 'eca) (defcustom eca-chat-mode-line-format - '(:workspace-folders :add-workspace-button :spacer :init-progress " " :bg-jobs " " :elapsed-time " " :usage " " :trust) + '(:workspace-folders :add-workspace-button :remove-workspace-button :spacer :init-progress " " :bg-jobs " " :elapsed-time " " :usage " " :trust) "Format for the ECA chat mode line. When set to a list, each element is a module keyword or a @@ -177,6 +177,7 @@ to separate left-aligned and right-aligned content. Available modules: `:workspace-folders' - project root paths `:add-workspace-button' - clickable [+] button + `:remove-workspace-button' - clickable [-] button `:title' - chat title `:elapsed-time' - turn duration timer `:usage' - token/cost info (see `eca-chat-usage-string-format') @@ -196,6 +197,7 @@ This gives full control for powerline or doom-modeline users." (string :tag "Literal string") (const :tag "Workspace folders" :workspace-folders) (const :tag "Add workspace button" :add-workspace-button) + (const :tag "Remove workspace button" :remove-workspace-button) (const :tag "Background jobs" :bg-jobs) (const :tag "Chat title" :title) (const :tag "Elapsed time" :elapsed-time) @@ -1762,6 +1764,24 @@ E is the mouse event." (eca--session-add-workspace-folder session folder) (force-mode-line-update)))) +(defun eca-chat-remove-workspace-root () + "Prompt for a workspace folder to remove from the current session. +Refuses when only one folder remains. In `merged' worktree mode, a +removed folder sharing its git-common-dir with another session folder +may be auto-re-added on the next buffer visit." + (interactive) + (when-let ((session (eca-session))) + (let ((folders (eca--session-workspace-folders session))) + (cond + ((null folders) + (user-error "No workspace folders to remove")) + ((<= (length folders) 1) + (user-error "Cannot remove the last workspace folder")) + (t + (let ((folder (completing-read "Remove workspace: " folders nil t))) + (eca--session-remove-workspace-folder session folder) + (force-mode-line-update))))))) + (defvar eca-chat--add-workspace-map (let ((map (make-sparse-keymap))) (define-key map [mode-line mouse-1] @@ -1769,6 +1789,13 @@ E is the mouse event." map) "Keymap for the modeline [+] workspace button.") +(defvar eca-chat--remove-workspace-map + (let ((map (make-sparse-keymap))) + (define-key map [mode-line mouse-1] + #'eca-chat-remove-workspace-root) + map) + "Keymap for the modeline [-] workspace button.") + (defvar eca-chat--trust-toggle-map (let ((map (make-sparse-keymap))) (define-key map [mode-line mouse-1] @@ -1813,6 +1840,12 @@ are in progress." 'mouse-face 'highlight 'help-echo "Add workspace folder" 'local-map eca-chat--add-workspace-map)) + (:remove-workspace-button + (propertize " [-]" + 'face 'shadow + 'mouse-face 'highlight + 'help-echo "Remove workspace folder" + 'local-map eca-chat--remove-workspace-map)) (:bg-jobs (when-let* ((jobs (eca--session-jobs session)) (running (seq-count (lambda (j) (string= "running" (plist-get j :status))) jobs))) diff --git a/eca-util.el b/eca-util.el index 81677e6..6c57bf3 100644 --- a/eca-util.el +++ b/eca-util.el @@ -192,6 +192,34 @@ workspace folders. Returns nil otherwise." :removed []))) (eca-info "Added workspace folder: %s" folder)))) +(defun eca--session-remove-workspace-folder (session folder) + "Remove FOLDER from SESSION's workspace-folders and notify the server. +Refuses to remove the last remaining folder, since the Emacs client +resolves buffers to sessions by matching against workspace-folders and +an empty list would make the session unreachable. In `merged' worktree +mode, a removed folder whose git-common-dir still matches another +folder in the session can be auto-re-added by `eca-session' the next +time a buffer under it is visited." + (let* ((folder (expand-file-name folder)) + (folders (eca--session-workspace-folders session))) + (cond + ((not (--first (string= it folder) folders)) + (eca-warn "Workspace folder not found: %s" folder)) + ((<= (length folders) 1) + (user-error "Cannot remove the last workspace folder")) + (t + (setf (eca--session-workspace-folders session) + (cl-remove-if (lambda (it) (string= it folder)) folders)) + (eca-api-notify + session + :method "workspace/didChangeWorkspaceFolders" + :params (list :event + (list :added [] + :removed (vector + (list :uri (eca--path-to-uri folder) + :name (file-name-nondirectory (directory-file-name folder))))))) + (eca-info "Removed workspace folder: %s" folder))))) + (defun eca-session () "Return the session related to root of current buffer otherwise nil." (or (eca-get eca--sessions eca--session-id-cache) @@ -327,7 +355,11 @@ Inheirits BASE-MAP." ["Server" ("S r" "Restart" eca-restart) - ("S s" "Stop" eca-stop)]])) + ("S s" "Stop" eca-stop)] + + ["Workspace" + ("W a" "Add folder" eca-chat-add-workspace-root) + ("W r" "Remove folder" eca-chat-remove-workspace-root)]])) (defun eca-transient-menu () "Open the ECA transient menu.