Skip to content
Merged
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
61 changes: 14 additions & 47 deletions pi-coding-agent.el
Original file line number Diff line number Diff line change
Expand Up @@ -167,31 +167,11 @@ When phscroll is not available, tables wrap like other content."

;;;; Faces

(defface pi-coding-agent-separator
'((t :inherit font-lock-comment-face))
"Face for section separators in pi chat."
:group 'pi-coding-agent)

(defface pi-coding-agent-user-label
'((t :inherit bold :foreground "dodger blue"))
"Face for the You label in pi chat."
:group 'pi-coding-agent)

(defface pi-coding-agent-timestamp
'((t :inherit shadow))
"Face for timestamps in message headers."
:group 'pi-coding-agent)

(defface pi-coding-agent-assistant-label
'((t :inherit bold :foreground "sea green"))
"Face for the Assistant label in pi chat."
:group 'pi-coding-agent)

(defface pi-coding-agent-compaction-label
'((t :inherit bold :foreground "medium purple"))
"Face for the Compaction label in pi chat."
:group 'pi-coding-agent)

(defface pi-coding-agent-tool-name
'((t :inherit font-lock-function-name-face :weight bold))
"Face for tool names (BASH, READ, etc.) in pi chat."
Expand All @@ -213,29 +193,17 @@ When phscroll is not available, tables wrap like other content."
:group 'pi-coding-agent)

(defface pi-coding-agent-tool-block-pending
'((((class color) (min-colors 88) (background dark))
:background "#2a2a35" :extend t)
(((class color) (min-colors 88) (background light))
:background "#f0f0f5" :extend t)
(t :inherit secondary-selection :extend t))
'((t :inherit secondary-selection :extend t))
"Face for tool blocks during execution."
:group 'pi-coding-agent)

(defface pi-coding-agent-tool-block-success
'((((class color) (min-colors 88) (background dark))
:background "#2a352a" :extend t)
(((class color) (min-colors 88) (background light))
:background "#f0f5f0" :extend t)
(t :inherit secondary-selection :extend t))
'((t :inherit diff-added :extend t))
"Face for tool blocks after successful completion."
:group 'pi-coding-agent)

(defface pi-coding-agent-tool-block-error
'((((class color) (min-colors 88) (background dark))
:background "#352a2a" :extend t)
(((class color) (min-colors 88) (background light))
:background "#f5f0f0" :extend t)
(t :inherit secondary-selection :extend t))
'((t :inherit diff-removed :extend t))
"Face for tool blocks after failed completion."
:group 'pi-coding-agent)

Expand Down Expand Up @@ -854,10 +822,11 @@ Windows where user scrolled up (point earlier) stay in place."
(goto-char (point-max))
(insert text)))))

(defun pi-coding-agent--make-separator (label face &optional timestamp)
"Create a setext-style H1 heading separator with LABEL styled with FACE.
(defun pi-coding-agent--make-separator (label &optional timestamp)
"Create a setext-style H1 heading separator with LABEL.
If TIMESTAMP (Emacs time value) is provided, append it after \" · \".
Returns a markdown setext heading: label line followed by === underline.
Fontification is handled by `gfm-mode' (inherits `markdown-header-face-1').

Using setext headings enables outline/imenu navigation and keeps our
turn markers as H1 while LLM ATX headings are leveled down to H2+."
Expand All @@ -869,16 +838,15 @@ turn markers as H1 while LLM ATX headings are leveled down to H2+."
;; Underline must be at least 3 chars, and at least as long as header
(underline-len (max 3 (length header-line)))
(underline (make-string underline-len ?=)))
(concat (propertize header-line 'face face) "\n"
(propertize underline 'face 'pi-coding-agent-separator))))
(concat header-line "\n" underline)))

(defun pi-coding-agent--display-user-message (text &optional timestamp)
"Display user message TEXT in the chat buffer.
If TIMESTAMP (Emacs time value) is provided, display it in the header.
Note: No blank line after setext underline - the hidden === provides
visual spacing when `markdown-hide-markup' is enabled."
(pi-coding-agent--append-to-chat
(concat "\n" (pi-coding-agent--make-separator "You" 'pi-coding-agent-user-label timestamp) "\n"
(concat "\n" (pi-coding-agent--make-separator "You" timestamp) "\n"
text "\n")))

(defun pi-coding-agent--display-agent-start ()
Expand All @@ -891,7 +859,7 @@ visual spacing when `markdown-hide-markup' is enabled."
;; Only show header if not already shown for this prompt
(unless pi-coding-agent--assistant-header-shown
(pi-coding-agent--append-to-chat
(concat "\n" (pi-coding-agent--make-separator "Assistant" 'pi-coding-agent-assistant-label) "\n"))
(concat "\n" (pi-coding-agent--make-separator "Assistant") "\n"))
(setq pi-coding-agent--assistant-header-shown t))
;; Create markers at current end position
;; message-start-marker: where content begins (for later replacement)
Expand Down Expand Up @@ -1381,7 +1349,7 @@ Updates buffer-local state and renders display updates."
;; Assistant message - show header if needed, reset markers
(unless pi-coding-agent--assistant-header-shown
(pi-coding-agent--append-to-chat
(concat "\n" (pi-coding-agent--make-separator "Assistant" 'pi-coding-agent-assistant-label) "\n"))
(concat "\n" (pi-coding-agent--make-separator "Assistant") "\n"))
(setq pi-coding-agent--assistant-header-shown t))
(setq pi-coding-agent--message-start-marker (copy-marker (point-max) nil))
(setq pi-coding-agent--streaming-marker (copy-marker (point-max) t))))))
Expand Down Expand Up @@ -2288,7 +2256,7 @@ TOKENS-BEFORE is the token count before compaction.
SUMMARY is the compaction summary text (markdown).
TIMESTAMP is optional time when compaction occurred."
(pi-coding-agent--append-to-chat
(concat "\n" (pi-coding-agent--make-separator "Compaction" 'pi-coding-agent-compaction-label timestamp) "\n"
(concat "\n" (pi-coding-agent--make-separator "Compaction" timestamp) "\n"
(propertize (format "Compacted from %s tokens\n\n"
(pi-coding-agent--format-number (or tokens-before 0)))
'face 'pi-coding-agent-tool-name)
Expand Down Expand Up @@ -2332,8 +2300,7 @@ Displays warnings for missing dependencies."
(defun pi-coding-agent--format-startup-header ()
"Format the startup header string with styled separator."
(let* ((pi-coding-agent-cli-version (pi-coding-agent--get-pi-coding-agent-version))
(separator (pi-coding-agent--make-separator (format "Pi %s" pi-coding-agent-cli-version)
'pi-coding-agent-assistant-label)))
(separator (pi-coding-agent--make-separator (format "Pi %s" pi-coding-agent-cli-version))))
(concat
separator "\n"
"C-c C-c send prompt\n"
Expand Down Expand Up @@ -2932,15 +2899,15 @@ Each text block is rendered independently for proper formatting."
(timestamp (pi-coding-agent--ms-to-time (plist-get message :timestamp))))
(when (and text (not (string-empty-p text)))
(pi-coding-agent--append-to-chat
(concat "\n" (pi-coding-agent--make-separator "You" 'pi-coding-agent-user-label timestamp) "\n"
(concat "\n" (pi-coding-agent--make-separator "You" timestamp) "\n"
text "\n"))))
(setq prev-role "user"))
("assistant"
;; Show header only on transition from user
(when (not (equal prev-role "assistant"))
(flush-tools)
(pi-coding-agent--append-to-chat
(concat "\n" (pi-coding-agent--make-separator "Assistant" 'pi-coding-agent-assistant-label) "\n")))
(concat "\n" (pi-coding-agent--make-separator "Assistant") "\n")))
;; Render text immediately (isolated)
(let ((text (pi-coding-agent--extract-message-text message))
(tool-count (pi-coding-agent--count-tool-calls message)))
Expand Down
6 changes: 3 additions & 3 deletions test/pi-coding-agent-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -1270,19 +1270,19 @@ since we don't display them locally. Let pi's message_start handle it."

(ert-deftest pi-coding-agent-test-separator-without-timestamp ()
"Separator without timestamp is setext H1 heading."
(let ((sep (pi-coding-agent--make-separator "You" 'pi-coding-agent-user-label)))
(let ((sep (pi-coding-agent--make-separator "You")))
;; Setext format: label on one line, === underline on next
(should (string-match-p "^You\n=+$" sep))))

(ert-deftest pi-coding-agent-test-separator-with-timestamp ()
"Separator with timestamp shows label · time as setext H1."
(let ((sep (pi-coding-agent--make-separator "You" 'pi-coding-agent-user-label (current-time))))
(let ((sep (pi-coding-agent--make-separator "You" (current-time))))
;; Format: "You · HH:MM" followed by newline and ===
(should (string-match-p "^You · [0-2][0-9]:[0-5][0-9]\n=+$" sep))))

(ert-deftest pi-coding-agent-test-separator-is-valid-setext-heading ()
"Separator produces valid markdown setext H1 syntax."
(let ((sep (pi-coding-agent--make-separator "Assistant" 'pi-coding-agent-assistant-label)))
(let ((sep (pi-coding-agent--make-separator "Assistant")))
;; Must have at least 3 = characters for valid setext
(should (string-match-p "\n===+" sep))
;; Underline should match or exceed label length
Expand Down