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
13 changes: 9 additions & 4 deletions pi-coding-agent.el
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,12 @@ on each delta - fontification happens at message end instead."
(when (and delta pi-coding-agent--streaming-marker)
(let* ((inhibit-read-only t)
(inhibit-modification-hooks t)
;; Strip leading newlines from first content after header
(delta (if (and pi-coding-agent--message-start-marker
(= (marker-position pi-coding-agent--message-start-marker)
(marker-position pi-coding-agent--streaming-marker)))
(string-trim-left delta "\n+")
delta))
(transformed (pi-coding-agent--transform-delta delta)))
(pi-coding-agent--with-scroll-preservation
(save-excursion
Expand Down Expand Up @@ -1008,7 +1014,7 @@ CONTENT is ignored - we use what was already streamed."
(set-marker pi-coding-agent--streaming-marker (point)))))))

(defun pi-coding-agent--display-agent-end ()
"Display end of agent turn and process follow-up queue.
"Finalize agent turn: normalize whitespace, handle abort, process queue.
Note: status is set to `idle' by the event handler."
;; Reset local message tracker.
;; Ensures clean state for next turn (e.g., if abort occurred before echo arrived).
Expand All @@ -1030,14 +1036,13 @@ Note: status is set to `idle' by the event handler."
(insert "\n\n" (propertize "[Aborted]" 'face 'error) "\n")))
(setq pi-coding-agent--aborted nil)
(setq pi-coding-agent--followup-queue nil))
;; Add spacing for next turn, avoiding excess blank lines
;; Use scroll preservation so following windows stay at end
;; Normalize trailing newlines - ensure content ends with single newline.
(pi-coding-agent--with-scroll-preservation
(save-excursion
(goto-char (point-max))
(skip-chars-backward "\n")
(delete-region (point) (point-max))
(insert "\n\n"))))
(insert "\n"))))
(pi-coding-agent--spinner-stop)
(pi-coding-agent--fontify-timer-stop)
(pi-coding-agent--refresh-header)
Expand Down
54 changes: 51 additions & 3 deletions test/pi-coding-agent-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -773,13 +773,13 @@ as the top-level structure."
(pi-coding-agent--display-thinking-delta " about this.")
(should (string-match-p "Let me think... about this." (buffer-string)))))

(ert-deftest pi-coding-agent-test-display-agent-end-adds-newlines ()
"agent_end event adds trailing newlines."
(ert-deftest pi-coding-agent-test-display-agent-end-adds-newline ()
"agent_end normalizes trailing whitespace to single newline."
(with-temp-buffer
(pi-coding-agent-chat-mode)
(pi-coding-agent--append-to-chat "Some response")
(pi-coding-agent--display-agent-end)
(should (string-suffix-p "\n\n" (buffer-string)))))
(should (string-suffix-p "response\n" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-no-blank-line-after-user-header ()
"User header has no blank line after setext underline.
Expand All @@ -800,6 +800,26 @@ The hidden === provides visual spacing when `markdown-hide-markup' is t."
;; Pattern: setext heading (Assistant + underline), NO blank line, content
(should (string-match-p "Assistant\n=+\nHi" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-delta-leading-newlines-stripped ()
"Leading newlines from first text delta are stripped.
Models often send \\n\\n before first content, which would create
extra blank lines after the setext header."
(with-temp-buffer
(pi-coding-agent-chat-mode)
(pi-coding-agent--display-agent-start)
(pi-coding-agent--display-message-delta "\n\nHi")
(should (string-match-p "Assistant\n=+\nHi" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-thinking-leading-newlines-stripped ()
"Leading newlines before thinking block are stripped.
Models may send \\n\\n before thinking content too."
(with-temp-buffer
(pi-coding-agent-chat-mode)
(pi-coding-agent--display-agent-start)
(pi-coding-agent--display-thinking-start)
;; Thinking start should appear directly after header, no blank line
(should (string-match-p "Assistant\n=+\n>" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-blank-line-before-tool ()
"Tool block is preceded by blank line when after text."
(with-temp-buffer
Expand All @@ -823,6 +843,34 @@ The hidden === provides visual spacing when `markdown-hide-markup' is t."
;; Should end with closing fence and blank line
(should (string-match-p "```\n\n" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-single-blank-line-between-turns ()
"Only one blank line between agent response and next section header.
agent_end + next section's leading newline must not create triple newlines."
(with-temp-buffer
(pi-coding-agent-chat-mode)
;; Turn 1: user + assistant
(pi-coding-agent--display-user-message "Hi")
(pi-coding-agent--display-agent-start)
(pi-coding-agent--display-message-delta "Hello!")
(pi-coding-agent--render-complete-message)
(pi-coding-agent--display-agent-end)
;; Turn 2: user message
(setq pi-coding-agent--assistant-header-shown nil)
(pi-coding-agent--display-user-message "Bye")
;; Should never have triple newlines (which would be two blank lines)
(should-not (string-match-p "\n\n\n" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-single-blank-line-before-compaction ()
"Only one blank line between agent response and compaction header."
(with-temp-buffer
(pi-coding-agent-chat-mode)
(pi-coding-agent--display-agent-start)
(pi-coding-agent--display-message-delta "Some response.")
(pi-coding-agent--render-complete-message)
(pi-coding-agent--display-agent-end)
(pi-coding-agent--display-compaction-result 50000 "Summary.")
(should-not (string-match-p "\n\n\n" (buffer-string)))))

(ert-deftest pi-coding-agent-test-spacing-no-double-blank-between-tools ()
"Consecutive tools have single blank line between them."
(with-temp-buffer
Expand Down