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
11 changes: 7 additions & 4 deletions pi-coding-agent-menu.el
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ Call this when starting a new session to ensure no stale state persists."
pi-coding-agent--in-code-block nil
pi-coding-agent--in-thinking-block nil
pi-coding-agent--line-parse-state 'line-start
pi-coding-agent--pending-tool-overlay nil)
pi-coding-agent--pending-tool-overlay nil
pi-coding-agent--activity-phase "idle")
;; Use accessors for cross-module state
(pi-coding-agent--set-last-usage nil)
(pi-coding-agent--clear-followup-queue)
Expand Down Expand Up @@ -510,14 +511,16 @@ Optional CUSTOM-INSTRUCTIONS provide guidance for the compaction summary."
(when-let ((proc (pi-coding-agent--get-process))
(chat-buf (pi-coding-agent--get-chat-buffer)))
(message "Pi: Compacting...")
(pi-coding-agent--spinner-start)
(with-current-buffer chat-buf
(pi-coding-agent--set-activity-phase "compact"))
(pi-coding-agent--rpc-async proc
(if custom-instructions
(list :type "compact" :customInstructions custom-instructions)
'(:type "compact"))
(lambda (response)
;; Pass chat-buf explicitly (callback may run in arbitrary context)
(pi-coding-agent--spinner-stop chat-buf)
(when (buffer-live-p chat-buf)
(with-current-buffer chat-buf
(pi-coding-agent--set-activity-phase "idle")))
(if (plist-get response :success)
(when (buffer-live-p chat-buf)
(with-current-buffer chat-buf
Expand Down
14 changes: 8 additions & 6 deletions pi-coding-agent-render.el
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ visual spacing when `markdown-hide-markup' is enabled."
(setq pi-coding-agent--line-parse-state 'line-start)
(setq pi-coding-agent--in-code-block nil)
(setq pi-coding-agent--in-thinking-block nil)
(pi-coding-agent--spinner-start)
(pi-coding-agent--fontify-timer-start)
(force-mode-line-update))
(pi-coding-agent--set-activity-phase "thinking")
(pi-coding-agent--fontify-timer-start))

(defun pi-coding-agent--process-streaming-char (char state in-block)
"Process CHAR with current STATE and IN-BLOCK flag.
Expand Down Expand Up @@ -250,7 +249,7 @@ Note: status is set to `idle' by the event handler."
(skip-chars-backward "\n")
(delete-region (point) (point-max))
(insert "\n"))))
(pi-coding-agent--spinner-stop)
(pi-coding-agent--set-activity-phase "idle")
(pi-coding-agent--fontify-timer-stop)
(pi-coding-agent--refresh-header)
;; Check follow-up queue and send next message if any (unless aborted)
Expand Down Expand Up @@ -566,6 +565,7 @@ Updates buffer-local state and renders display updates."
(event-type (plist-get msg-event :type)))
(pcase event-type
("text_delta"
(pi-coding-agent--set-activity-phase "replying")
(pi-coding-agent--display-message-delta (plist-get msg-event :delta)))
("thinking_start"
(pi-coding-agent--display-thinking-start))
Expand Down Expand Up @@ -627,6 +627,7 @@ Updates buffer-local state and renders display updates."
(pi-coding-agent--set-last-usage (plist-get message :usage))))
(pi-coding-agent--render-complete-message))
("tool_execution_start"
(pi-coding-agent--set-activity-phase "running")
(let ((tool-call-id (plist-get event :toolCallId))
(args (plist-get event :args)))
;; Cache args for tool_execution_end (which doesn't include args)
Expand All @@ -647,6 +648,7 @@ Updates buffer-local state and renders display updates."
(overlay-put ov 'pi-coding-agent-tool-path path)))
(pi-coding-agent--display-tool-start (plist-get event :toolName) args))))
("tool_execution_end"
(pi-coding-agent--set-activity-phase "thinking")
(let* ((tool-call-id (plist-get event :toolCallId))
(result (plist-get event :result))
;; Retrieve cached args since tool_execution_end doesn't include them
Expand All @@ -662,13 +664,13 @@ Updates buffer-local state and renders display updates."
(pi-coding-agent--display-tool-update (plist-get event :partialResult)))
("auto_compaction_start"
(setq pi-coding-agent--status 'compacting)
(pi-coding-agent--spinner-start)
(pi-coding-agent--set-activity-phase "compact")
(let ((reason (plist-get event :reason)))
(message "Pi: %sAuto-compacting... (C-c C-k to cancel)"
(if (equal reason "overflow") "Context overflow, " ""))))
("auto_compaction_end"
(pi-coding-agent--spinner-stop)
(setq pi-coding-agent--status 'idle)
(pi-coding-agent--set-activity-phase "idle")
(if (pi-coding-agent--normalize-boolean (plist-get event :aborted))
(progn
(message "Pi: Auto-compaction cancelled")
Expand Down
97 changes: 31 additions & 66 deletions pi-coding-agent-ui.el
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
;; - Buffer-local session variables (the shared mutable state)
;; - Buffer creation, naming, and navigation
;; - Display primitives (append-to-chat, scroll preservation, separators)
;; - Header-line formatting and spinner
;; - Header-line formatting and activity phases
;; - Sending infrastructure (send-prompt, abort-send)
;; - Major mode definitions (chat-mode, input-mode)

Expand Down Expand Up @@ -224,6 +224,11 @@ Subtle blue-tinted background derived from the current theme."
"Face for model name in header line."
:group 'pi-coding-agent)

(defface pi-coding-agent-activity-phase
'((t :inherit shadow))
"Face for activity phase label in header line."
:group 'pi-coding-agent)

(defface pi-coding-agent-retry-notice
'((t :inherit warning :slant italic))
"Face for retry notifications (rate limit, overloaded, etc.)."
Expand Down Expand Up @@ -566,6 +571,21 @@ Starts as `line-start' because content begins after separator newline.")
;; pi-coding-agent--status is defined in pi-coding-agent-core.el as the single source of truth
;; for session activity state (idle, sending, streaming, compacting)

(defvar-local pi-coding-agent--activity-phase "idle"
"Fine-grained activity phase for header-line display.
One of: `thinking', `replying', `running', `compact', or `idle'.
Always populated and rendered in a fixed-width slot.")

(defun pi-coding-agent--set-activity-phase (phase)
"Set activity PHASE for header-line display in current chat buffer.
PHASE should be one of: `thinking', `replying', `running', `compact', `idle'.
Returns non-nil when the phase changed.
Also triggers a header-line refresh when changed."
(unless (equal pi-coding-agent--activity-phase phase)
(setq pi-coding-agent--activity-phase phase)
(force-mode-line-update t)
t))

(defvar-local pi-coding-agent--cached-stats nil
"Cached session statistics for header-line display.
Updated after each agent turn completes.")
Expand Down Expand Up @@ -924,63 +944,6 @@ Removes common prefixes like \"Claude \" and suffixes like \" (latest)\"."
(replace-regexp-in-string " (latest)$" "")
(replace-regexp-in-string "^claude-" "")))

(defvar pi-coding-agent--spinner-frames ["⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏"]
"Frames for the busy spinner animation.")

(defvar pi-coding-agent--spinner-index 0
"Current frame index in the spinner animation (shared for sync).")

(defvar pi-coding-agent--spinner-timer nil
"Timer for animating spinners (shared across sessions).")

(defvar pi-coding-agent--spinning-sessions nil
"List of chat buffers currently spinning.")

(defun pi-coding-agent--spinner-start ()
"Start the spinner for current session."
(let ((chat-buf (pi-coding-agent--get-chat-buffer)))
(when (and chat-buf (not (memq chat-buf pi-coding-agent--spinning-sessions)))
(push chat-buf pi-coding-agent--spinning-sessions)
;; Start global timer if not running
(unless pi-coding-agent--spinner-timer
(setq pi-coding-agent--spinner-index 0)
(setq pi-coding-agent--spinner-timer
(run-with-timer 0 0.1 #'pi-coding-agent--spinner-tick))))))

(defun pi-coding-agent--spinner-stop (&optional chat-buf)
"Stop the spinner for current session.
CHAT-BUF is the buffer to stop spinning; if nil, uses current context.
Note: When called from async callbacks, pass CHAT-BUF explicitly."
(let ((chat-buf (or chat-buf (pi-coding-agent--get-chat-buffer))))
(setq pi-coding-agent--spinning-sessions (delq chat-buf pi-coding-agent--spinning-sessions))
;; Stop global timer if no sessions spinning
(when (and pi-coding-agent--spinner-timer (null pi-coding-agent--spinning-sessions))
(cancel-timer pi-coding-agent--spinner-timer)
(setq pi-coding-agent--spinner-timer nil))))

(defun pi-coding-agent--spinner-tick ()
"Advance spinner to next frame and update spinning sessions."
(setq pi-coding-agent--spinner-index
(mod (1+ pi-coding-agent--spinner-index) (length pi-coding-agent--spinner-frames)))
;; Only update windows showing spinning sessions
(dolist (buf pi-coding-agent--spinning-sessions)
(when (buffer-live-p buf)
(let ((input-buf (buffer-local-value 'pi-coding-agent--input-buffer buf)))
;; Update input buffer's header line (where spinner shows)
(when (and input-buf (buffer-live-p input-buf))
(dolist (win (get-buffer-window-list input-buf nil t))
(with-selected-window win
(force-mode-line-update))))))))

(defun pi-coding-agent--spinner-current ()
"Return current spinner frame if this session is spinning."
(let ((chat-buf (cond
((derived-mode-p 'pi-coding-agent-chat-mode) (current-buffer))
((derived-mode-p 'pi-coding-agent-input-mode) pi-coding-agent--chat-buffer)
(t nil))))
(when (and chat-buf (memq chat-buf pi-coding-agent--spinning-sessions))
(aref pi-coding-agent--spinner-frames pi-coding-agent--spinner-index))))

;;; Header-Line Formatting

(defvar pi-coding-agent--header-model-map
Expand Down Expand Up @@ -1075,9 +1038,12 @@ Accesses state from the linked chat buffer."
(model-short (if (string-empty-p model-name) "..."
(pi-coding-agent--shorten-model-name model-name)))
(thinking (or (plist-get state :thinking-level) ""))
(status-str (if-let ((spinner (pi-coding-agent--spinner-current)))
(concat " " spinner)
" "))) ; Same width as " ⠋" to prevent jumping
(activity-phase (or (and chat-buf
(buffer-local-value 'pi-coding-agent--activity-phase chat-buf))
"idle"))
(activity-phase-str
(propertize (format "%-8s" activity-phase)
'face 'pi-coding-agent-activity-phase)))
(concat
;; Model (clickable)
(propertize model-short
Expand All @@ -1092,8 +1058,8 @@ Accesses state from the linked chat buffer."
'mouse-face 'highlight
'help-echo "mouse-1: Cycle thinking level"
'local-map pi-coding-agent--header-thinking-map)))
;; Spinner/status (right after model/thinking)
status-str
;; Activity phase (fixed-width slot after model/thinking)
(concat " " activity-phase-str)
;; Stats (if available)
(pi-coding-agent--header-format-stats stats last-usage model-obj)
;; Extension status (if any)
Expand Down Expand Up @@ -1155,12 +1121,11 @@ Shows an error message if process is unavailable."

(defun pi-coding-agent--abort-send (chat-buf)
"Clean up after a failed send attempt in CHAT-BUF.
Stops spinner and resets status to idle."
Resets activity phase and status to idle."
(when (buffer-live-p chat-buf)
(with-current-buffer chat-buf
(pi-coding-agent--spinner-stop)
(setq pi-coding-agent--status 'idle)
(force-mode-line-update))))
(pi-coding-agent--set-activity-phase "idle"))))


(provide 'pi-coding-agent-ui)
Expand Down
Loading