Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions eca-chat.el
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ are not currently selected."

;; Internal

(defvar-local eca-chat--closed nil)

(defvar-local eca-chat--history '())
(defvar-local eca-chat--history-index -1)
(defvar-local eca-chat--id nil)
Expand Down Expand Up @@ -637,13 +637,6 @@ A plist with :session :request :question :options :tool-call-id :allow-freeform.
(defvar eca--chat-init-session nil
"Dynamically bound session during `eca-chat-mode' initialization.")

(defun eca-chat-new-buffer-name (session)
"Return the chat buffer name for SESSION."
(format "<eca-chat[%s]:%s:%s>"
(eca--session-project-name session)
(eca--session-id session)
eca-chat--new-chat-id))

(defvar eca-chat-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map markdown-mode-map)
Expand Down Expand Up @@ -700,11 +693,11 @@ A plist with :session :request :question :options :tool-call-id :allow-freeform.
(or (when-let (last-buff (eca--session-last-chat-buffer session))
(when (buffer-live-p last-buff)
last-buff))
(get-buffer (eca-chat-new-buffer-name session))))
(get-buffer (funcall eca-generate-buffer-name-function "eca-chat" :session session :chat-id eca-chat--new-chat-id))))

(defun eca-chat--create-buffer (session)
"Create the eca chat buffer for SESSION."
(get-buffer-create (generate-new-buffer-name (eca-chat-new-buffer-name session))))
(get-buffer-create (generate-new-buffer-name (funcall eca-generate-buffer-name-function "eca-chat" :session session :chat-id eca-chat--new-chat-id))))

(defun eca-chat--get-chat-buffer (session chat-id)
"Get chat buffer for SESSION and CHAT-ID, or nil when none registered.
Expand All @@ -720,7 +713,7 @@ its real id and there is no `'empty' placeholder to migrate from."
(and (symbolp this-command)
(string-prefix-p "eca-" (symbol-name this-command))))
eca-chat--id
(not eca-chat--closed)
(not eca-chat-closed)
(yes-or-no-p "Delete chat from server side (otherwise it will just kill the buffer) ?"))
(eca-api-request-sync (eca-session)
:method "chat/delete"
Expand Down Expand Up @@ -1345,7 +1338,7 @@ Resteps a list of context plists found in the prompt field."

(defun eca-chat--send-prompt (session prompt)
"Send PROMPT to server for SESSION."
(when eca-chat--closed
(when eca-chat-closed
(user-error (eca-error "This chat is closed")))
(let* ((prompt-contexts (eca-chat--extract-contexts-from-prompt))
(refined-contexts (-map #'eca-chat--refine-context
Expand Down Expand Up @@ -2008,7 +2001,7 @@ are in progress."

(defun eca-chat--mode-line-string (session)
"Build mode-line string for SESSION from `eca-chat-mode-line-format'."
(if eca-chat--closed
(if eca-chat-closed
(propertize "*Closed session*"
'font-lock-face 'eca-chat-system-messages-face)
(let* ((fmt eca-chat-mode-line-format)
Expand Down Expand Up @@ -3576,15 +3569,15 @@ When ACTIVE is non-nil, show the question prefix; otherwise restore normal."
(when eca-chat--stopping-safety-timer
(cancel-timer eca-chat--stopping-safety-timer)
(setq-local eca-chat--stopping-safety-timer nil))
(setq eca-chat--closed t)
(setq-local eca-chat-closed t)
(force-mode-line-update)
(goto-char (point-max))
(rename-buffer (concat (buffer-name) ":closed") t)
;; Keep only the most recently closed chat buffer; kill older ones.
(let ((current (current-buffer)))
(dolist (b (buffer-list))
(when (and (not (eq b current))
(string-match-p "^<eca-chat:.*>:closed$" (buffer-name b)))
(buffer-local-value 'eca-chat-closed b))
(kill-buffer b))))
(when-let* ((window (get-buffer-window chat-buffer)))
(quit-window nil window))))))
Expand Down
27 changes: 8 additions & 19 deletions eca-process.el
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,6 @@ If current `gc-cons-threshold` is lower use that on filter server messages.'"
:type 'integer
:group 'eca)

(defun eca-process--buffer-name (session)
"Return the process buffer name for SESSION."
(format "<eca[%s]:%s>"
(eca--session-project-name session)
(eca--session-id session)))

(defun eca-process--stderr-buffer-name (session)
"Return the stderr buffer name for SESSION."
(format "<eca:stderr[%s]:%s>"
(eca--session-project-name session)
(eca--session-id session)))

(defcustom eca-server-releases-cache-ttl 3600
"Time-to-live (seconds) for the cached eca server releases list.
Once expired, the next call that needs the releases list will refetch
Expand Down Expand Up @@ -544,12 +532,12 @@ Call HANDLE-MSG for new msgs processed."
:connection-type 'pipe
:name "eca"
:command command
:buffer (eca-process--buffer-name session)
:stderr (get-buffer-create (eca-process--stderr-buffer-name session))
:buffer (funcall eca-generate-buffer-name-function "eca" :session session)
:stderr (get-buffer-create (funcall eca-generate-buffer-name-function "eca:stderr" :session session))
:filter (eca-process--make-filter handle-msg)
:sentinel (lambda (process exit-str)
(unless (process-live-p process)
(when-let* ((name (eca-process--stderr-buffer-name session))
(when-let* ((name (funcall eca-generate-buffer-name-function "eca:stderr" :session session))
(buf (get-buffer name)))
(with-current-buffer buf
(rename-buffer (concat (buffer-name) ":closed") t)
Expand Down Expand Up @@ -581,12 +569,13 @@ Call HANDLE-MSG for new msgs processed."
"Stop the eca process for SESSION if running."
(when session
(kill-process (eca--session-process session))
(kill-buffer (eca-process--buffer-name session))
(kill-buffer (funcall eca-generate-buffer-name-function "eca" :session session))
;; Rename stderr buffer to closed and clean up older closed ones
(let ((stderr-buffer (get-buffer (eca-process--stderr-buffer-name session))))
(let ((stderr-buffer (get-buffer (funcall eca-generate-buffer-name-function "eca:stderr" :session session))))
(when stderr-buffer
(with-current-buffer stderr-buffer
(rename-buffer (concat (buffer-name) ":closed") t)
(setq-local eca-chat-closed t)
(setq-local mode-line-format '("*Closed session*"))
(when-let ((win (get-buffer-window (current-buffer))))
(quit-window nil win))
Expand All @@ -596,12 +585,12 @@ Call HANDLE-MSG for new msgs processed."
(let ((current (current-buffer)))
(dolist (b (buffer-list))
(when (and (not (eq b current))
(string-match-p "^<eca:stderr:.*>:closed" (buffer-name b)))
(buffer-local-value 'eca-chat-closed b))
(kill-buffer b)))))))))

(defun eca-process-show-stderr (session)
"Open the eca process stderr buffer for SESSION."
(if-let ((buf (get-buffer (eca-process--stderr-buffer-name session))))
(if-let ((buf (get-buffer (funcall eca-generate-buffer-name-function "eca:stderr" :session session))))
(if (window-live-p (get-buffer-window buf))
(select-window (get-buffer-window buf))
(display-buffer buf))
Expand Down
11 changes: 2 additions & 9 deletions eca-settings.el
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,15 @@ preserving tab order."
(defvar-local eca-settings--tab-key nil
"The tab key for this settings buffer.")

(defun eca-settings--buffer-name (tab-key session)
"Return buffer name for TAB-KEY in SESSION."
(format "<eca-settings[%s]:%s:%s>"
(eca--session-project-name session)
tab-key
(eca--session-id session)))

(defun eca-settings--get-buffer (tab-key session)
"Get existing settings buffer for TAB-KEY in SESSION."
(get-buffer (eca-settings--buffer-name tab-key session)))
(get-buffer (funcall eca-generate-buffer-name-function "eca-settings" :session session :chat-id tab-key)))

(defun eca-settings--create-buffer (tab-key session)
"Create a new settings buffer for TAB-KEY in SESSION."
(get-buffer-create
(generate-new-buffer-name
(eca-settings--buffer-name tab-key session))))
(funcall eca-generate-buffer-name-function "eca-settings" :session session :chat-id tab-key))))

(defun eca-settings--get-or-create-tab-buffer (tab-key session)
"Get or create settings buffer for TAB-KEY in SESSION.
Expand Down
29 changes: 29 additions & 0 deletions eca-util.el
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,35 @@ workspace folder. Falls back to \"unknown\"."
"Display eca error message with FORMAT with ARGS."
(message "%s :: %s" (propertize "ECA" 'face 'error) (apply #'format format args)))

(defvar-local eca-chat-closed nil
"Non-nil when the chat buffer belongs to a closed session.")

(cl-defun eca-generate-buffer-name-default-function (mode &key session chat-id)
"Generate a buffer name for optional SESSION and optional CHAT-ID.
MODE is a string describing the buffer type (e.g. eca-chat)."
(cond
((null session)
(format "<%s>" mode))
(chat-id
(format "<%s[%s]:%s:%s>" mode
(eca--session-project-name session)
(eca--session-id session)
chat-id))
(t
(format "<%s[%s]:%s>" mode
(eca--session-project-name session)
(eca--session-id session)))))

(defcustom eca-generate-buffer-name-function
#'eca-generate-buffer-name-default-function
"The function used to generate the name for an ECA buffer.
The function is called with a required MODE (string), followed by
keyword arguments :session and :chat-id. It must return a string
to use as the buffer name."
:type `(radio (function-item ,#'eca-generate-buffer-name-default-function)
(function :tag "Function"))
:group 'eca)

(defun eca-buttonize (base-map text callback)
"Create a actionable TEXT that call CALLBACK when actioned.
Inheirits BASE-MAP."
Expand Down
50 changes: 21 additions & 29 deletions eca.el
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,12 @@ with `eca-process-wrapper-function' for a fully sandboxed setup."

;; Internal

(defvar eca-workspaces-buffer-name "*eca-workspaces*")

(defun eca--emacs-errors-buffer-name (session)
"Return the Emacs errors buffer name for SESSION."
(format "<eca:emacs-errors[%s]:%s>"
(eca--session-project-name session)
(eca--session-id session)))

(defun eca--log-error (session err &optional context backtrace)
"Log error ERR to the Emacs errors buffer for SESSION.
Optional CONTEXT is a string describing what was happening
when the error occurred. Optional BACKTRACE is a list of
frames captured via `backtrace-get-frames'."
(let ((buffer (get-buffer-create (eca--emacs-errors-buffer-name session))))
(let ((buffer (get-buffer-create (funcall eca-generate-buffer-name-function "eca:emacs-errors" :session session))))
(with-current-buffer buffer
(goto-char (point-max))
(insert (format "[%s] %s%s\n"
Expand All @@ -150,7 +142,7 @@ frames captured via `backtrace-get-frames'."

(defun eca-show-emacs-errors (session)
"Open the Emacs errors buffer for SESSION."
(let ((buffer (get-buffer (eca--emacs-errors-buffer-name session))))
(let ((buffer (get-buffer (funcall eca-generate-buffer-name-function "eca:emacs-errors" :session session))))
(if buffer
(with-current-buffer buffer
(if (window-live-p (get-buffer-window (buffer-name)))
Expand All @@ -166,20 +158,19 @@ frames captured via `backtrace-get-frames'."

(defun eca--emacs-errors-exit (session)
"Clean up the Emacs errors buffer for SESSION on stop."
(let ((buffer (get-buffer (eca--emacs-errors-buffer-name session))))
(let ((buffer (get-buffer (funcall eca-generate-buffer-name-function "eca:emacs-errors" :session session))))
(when buffer
(with-current-buffer buffer
(rename-buffer (concat (buffer-name) ":closed") t)
(setq-local mode-line-format '("*Closed session*"))
(when-let ((win (get-buffer-window (current-buffer))))
(quit-window nil win))
;; Keep only the most recently closed errors buffer; kill older ones.
(let ((current (current-buffer)))
(dolist (b (buffer-list))
(when (and (not (eq b current))
(or
(string-match-p "^<eca:emacs-errors:.*>:closed$" (buffer-name b))
(string-match-p "^<eca:emacs-errors:.*>$" (buffer-name b))))
(setq-local eca-chat-closed t)
(setq-local mode-line-format '("*Closed session*"))
(when-let ((win (get-buffer-window (current-buffer))))
(quit-window nil win))
;; Keep only the most recently closed errors buffer; kill older ones.
(let ((current (current-buffer)))
(dolist (b (buffer-list))
(when (and (not (eq b current))
(buffer-local-value 'eca-chat-closed b))
(kill-buffer b))))))))

(defun eca--get-message-type (json-data)
Expand Down Expand Up @@ -364,7 +355,7 @@ backtrace. On older Emacs, runs BODY without capture."
"Connect in eca nrepl port for development."
(interactive)
(eca-assert-session-running (eca-session))
(with-current-buffer (eca-process--stderr-buffer-name (eca-session))
(with-current-buffer (funcall eca-generate-buffer-name-function "eca:stderr" :session (eca-session))
(save-excursion
(goto-char (point-min))
(when (re-search-forward "started on port \\([0-9]+\\)" nil t)
Expand Down Expand Up @@ -466,13 +457,14 @@ When ARG is current prefix, ask for workspace roots to use."
(seq-doseq (chat-by-id (eca--session-chats session))
(when (buffer-live-p (cdr chat-by-id))
(hierarchy-add-tree h (cdr chat-by-id) parent-fn))))
(let ((b (or (when-let ((b (get-buffer eca-workspaces-buffer-name)))
(when (buffer-live-p b)
(with-current-buffer b
(let ((inhibit-read-only t))
(erase-buffer))))
b)
(generate-new-buffer eca-workspaces-buffer-name))))
(let* ((buffer-name (funcall eca-generate-buffer-name-function "eca-workspaces"))
(b (or (when-let ((b (get-buffer buffer-name)))
(when (buffer-live-p b)
(with-current-buffer b
(let ((inhibit-read-only t))
(erase-buffer))))
b)
(generate-new-buffer buffer-name))))
(with-current-buffer b
(setq-local tree-widget-image-enable nil)
(widget-create (eca--tree-widget-open-all
Expand Down
Loading