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
9 changes: 9 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ Here's some common non-default preferences:
;; Make the input pane 25% of the Pi window pair; rebalanced on frame resize:
(setopt pi-coding-agent-input-window-height 0.25)

;; Show the input pane only when launching a session and while composing;
;; hide it after each send and reopen it with M-x pi-coding-agent-open-input:
(setopt pi-coding-agent-input-window-display 'on-demand)

;; Start sessions with only the chat window visible; open the input on
;; demand (M-x pi-coding-agent-open-input, or `i'/`a' under Evil) and
;; have it hide again after each send:
(setopt pi-coding-agent-input-window-display 'hidden)

;; Copy source Markdown from the chat buffer instead of only visible text:
(setopt pi-coding-agent-copy-raw-markdown t)

Expand Down
2 changes: 2 additions & 0 deletions pi-coding-agent-input.el
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,11 @@ The /compact command is handled locally; other slash commands sent to pi."
(pi-coding-agent--builtin-command-name text)))
(busy
(pi-coding-agent--queue-followup-text chat-buf text)
(pi-coding-agent--maybe-hide-input-window)
(message "Pi: Message queued (will send when Pi is ready)"))
(t
(pi-coding-agent--accept-input-text text)
(pi-coding-agent--maybe-hide-input-window)
(with-current-buffer chat-buf
(pi-coding-agent--prepare-and-send text))))))

Expand Down
125 changes: 104 additions & 21 deletions pi-coding-agent-ui.el
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,27 @@ total window height, e.g. 0.3 means 30% for input."
(float :tag "Fraction (0.0–1.0)"))
:group 'pi-coding-agent)

(defcustom pi-coding-agent-input-window-display 'always
"How the input window is displayed alongside the chat window.
When `always' (the default), the input window is shown whenever the
session is displayed.

When `on-demand', the input window is shown when a session is first
launched, hidden after each send, and reopened with
\[pi-coding-agent-open-input]. Redisplaying an existing session
\(e.g. with `pi-coding-agent-toggle') shows only the chat window.

When `hidden', a session launches with only the chat window visible.
In every other respect this is like `on-demand': the input is hidden
after each send, reopened with `pi-coding-agent-open-input', and
redisplaying an existing session shows only the chat window. This
suits a chat-centric workflow where you compose in the input only
when needed."
:type '(choice (const :tag "Always visible" always)
(const :tag "On demand (hide after send)" on-demand)
(const :tag "Hidden at launch (chat only; open on demand)" hidden))
:group 'pi-coding-agent)

(defcustom pi-coding-agent-activity-phase-functions nil
"Functions called after a session activity phase is applied.
Each function is called with five arguments:
Expand Down Expand Up @@ -1662,25 +1683,90 @@ currently selected window."
below))))

(defun pi-coding-agent--best-input-window (chat-buf input-buf)
"Return best visible window for INPUT-BUF in current frame.
"Return best visible window for INPUT-BUF in current frame, or nil.
Prefer the input window below the selected CHAT-BUF window, then the
selected input window, then the tallest input window."
(let* ((input-wins (get-buffer-window-list input-buf nil))
(selected (selected-window))
(selected-chat-win (and (eq (window-buffer selected) chat-buf)
selected)))
(or (pi-coding-agent--paired-input-window selected-chat-win input-buf)
(and (memq selected input-wins)
selected)
(pi-coding-agent--window-with-most-height input-wins))))
(when-let* ((input-wins (get-buffer-window-list input-buf nil)))
(let* ((selected (selected-window))
(selected-chat-win (and (eq (window-buffer selected) chat-buf)
selected)))
(or (pi-coding-agent--paired-input-window selected-chat-win input-buf)
(and (memq selected input-wins)
selected)
(pi-coding-agent--window-with-most-height input-wins)))))

(defun pi-coding-agent--focus-input-window (chat-buf input-buf)
"Select a visible INPUT-BUF window for the CHAT-BUF session."
(when-let* ((win (pi-coding-agent--best-input-window chat-buf input-buf)))
(select-window win)))

(defun pi-coding-agent--display-buffers (chat-buf input-buf)
(defun pi-coding-agent--split-input-below-chat (chat-buf input-buf)
"Show INPUT-BUF in a new window below the best visible CHAT-BUF window.
The new window is soft-dedicated so `display-buffer' never targets it.
Return the new input window, or nil when no chat window can be split."
(when-let* ((chat-win
(or (and (eq (window-buffer (selected-window)) chat-buf)
(pi-coding-agent--window-can-split-for-input-p
(selected-window))
(selected-window))
(cl-find-if #'pi-coding-agent--window-can-split-for-input-p
(pi-coding-agent--windows-by-height
(get-buffer-window-list chat-buf nil))))))
(let ((input-win
(split-window
chat-win (- (pi-coding-agent--input-height-for-window chat-win))
'below)))
(set-window-buffer input-win input-buf)
;; Soft-dedicate the input window so `display-buffer' never
;; targets it (magit, help, compilation, etc.). The 'side
;; value still allows `switch-to-buffer' and `C-x o'.
(set-window-dedicated-p input-win 'side)
input-win)))

(defun pi-coding-agent-open-input ()
"Open the input window below the chat window and select it.
If an input window is already visible, select it instead. If no chat
window is visible either, restore the full session layout."
(interactive)
(let* ((chat-buf (pi-coding-agent--get-chat-buffer))
(input-buf (pi-coding-agent--get-input-buffer)))
(unless (and (buffer-live-p chat-buf) (buffer-live-p input-buf))
(user-error "No pi session for this buffer"))
(cond
((pi-coding-agent--focus-input-window chat-buf input-buf))
((when-let* ((input-win
(pi-coding-agent--split-input-below-chat chat-buf input-buf)))
(select-window input-win)))
(t (pi-coding-agent--display-buffers chat-buf input-buf)))))

(defun pi-coding-agent--input-window-on-demand-p ()
"Return non-nil when the input window is shown on demand.
True for the `on-demand' and `hidden' values of
`pi-coding-agent-input-window-display', which both hide the input
after each send and reopen it with `pi-coding-agent-open-input'."
(memq pi-coding-agent-input-window-display '(on-demand hidden)))

(defun pi-coding-agent--maybe-hide-input-window ()
"Hide the input window when it is shown on demand.
Intended to run after `pi-coding-agent-send' accepts input. When the
selected window is deleted, select a chat window instead."
(when (pi-coding-agent--input-window-on-demand-p)
(let* ((input-buf (pi-coding-agent--get-input-buffer))
(input-wins (and (buffer-live-p input-buf)
(get-buffer-window-list input-buf nil)))
(selected (selected-window)))
(dolist (win input-wins)
(when (window-parent win)
(delete-window win)))
(when (and (memq selected input-wins)
(not (window-live-p selected)))
(when-let* ((chat-buf (pi-coding-agent--get-chat-buffer))
(chat-win (get-buffer-window chat-buf)))
(select-window chat-win))))))

(defun pi-coding-agent--display-buffers (chat-buf input-buf &optional chat-only)
"Ensure CHAT-BUF and INPUT-BUF are visible.
When CHAT-ONLY is non-nil, show only the chat window.
Uses a split window with chat above and input below. Falls back to a
larger window when the selected one cannot be split."
(let* ((chat-wins (get-buffer-window-list chat-buf nil))
Expand All @@ -1694,20 +1780,17 @@ larger window when the selected one cannot be split."
(when (and input-wins (not chat-wins))
(pi-coding-agent--delete-extra-input-windows input-wins target))
(with-selected-window target
(unless (pi-coding-agent--window-can-split-for-input-p target)
(delete-other-windows target))
(unless (pi-coding-agent--window-can-split-for-input-p target)
(user-error "Window too small for chat + input layout"))
(unless chat-only
(unless (pi-coding-agent--window-can-split-for-input-p target)
(delete-other-windows target))
(unless (pi-coding-agent--window-can-split-for-input-p target)
(user-error "Window too small for chat + input layout")))
(switch-to-buffer chat-buf)
(with-current-buffer chat-buf
(goto-char (point-max)))
(let ((input-height (pi-coding-agent--input-height-for-window target)))
(setq input-win (split-window nil (- input-height) 'below))
(set-window-buffer input-win input-buf)
;; Soft-dedicate the input window so `display-buffer' never
;; targets it (magit, help, compilation, etc.). The 'side
;; value still allows `switch-to-buffer' and `C-x o'.
(set-window-dedicated-p input-win 'side)))
(unless chat-only
(setq input-win
(pi-coding-agent--split-input-below-chat chat-buf input-buf))))
(when (window-live-p input-win)
(select-window input-win))))

Expand Down
14 changes: 10 additions & 4 deletions pi-coding-agent.el
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,15 @@ Returns the chat buffer."
chat-buf))

(defun pi-coding-agent--show-session-buffers (chat-buf input-buf)
"Show CHAT-BUF and INPUT-BUF, focusing input when both are visible."
"Show CHAT-BUF and INPUT-BUF, focusing input when both are visible.
When `pi-coding-agent-input-window-display' is `hidden', a freshly
displayed session starts with only the chat window visible."
(if (and (get-buffer-window-list chat-buf nil)
(get-buffer-window-list input-buf nil))
(pi-coding-agent--focus-input-window chat-buf input-buf)
(pi-coding-agent--display-buffers chat-buf input-buf)))
(pi-coding-agent--display-buffers
chat-buf input-buf
(eq pi-coding-agent-input-window-display 'hidden))))

(defun pi-coding-agent--dired-regular-file-at-point ()
"Return Dired's regular file at point, or nil."
Expand Down Expand Up @@ -290,9 +294,11 @@ If no session exists, signal an error."
(and input-buf (get-buffer-window-list input-buf nil)))
(with-current-buffer chat-buf
(pi-coding-agent--hide-session-windows)))
;; Session hidden: show it
;; Session hidden: show it (chat only when input is shown on demand)
(t
(pi-coding-agent--display-buffers chat-buf input-buf)))))
(pi-coding-agent--display-buffers
chat-buf input-buf
(pi-coding-agent--input-window-on-demand-p))))))

(provide 'pi-coding-agent)
;;; pi-coding-agent.el ends here
14 changes: 7 additions & 7 deletions test/pi-coding-agent-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ still mock the RPC boundary, so the process is never used for I/O."
(push (list 'setup-session dir session) calls)
chat-buf))
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (chat input)
(lambda (chat input &optional _chat-only)
(push (list 'display chat input) calls)))
((symbol-function 'pi-coding-agent--session-transition-ready-p)
(lambda (chat action)
Expand Down Expand Up @@ -184,7 +184,7 @@ still mock the RPC boundary, so the process is never used for I/O."
(push (list 'setup-session dir session) calls)
chat-buf))
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (chat input)
(lambda (chat input &optional _chat-only)
(push (list 'display chat input) calls)))
((symbol-function 'pi-coding-agent--session-transition-ready-p)
(lambda (chat action)
Expand Down Expand Up @@ -226,7 +226,7 @@ still mock the RPC boundary, so the process is never used for I/O."
((symbol-function 'pi-coding-agent--setup-session)
(lambda (_dir &optional _session) chat-buf))
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (_chat _input) (setq displayed t)))
(lambda (_chat _input &optional _chat-only) (setq displayed t)))
((symbol-function 'pi-coding-agent--session-transition-ready-p)
(lambda (_chat _action) nil))
((symbol-function 'pi-coding-agent--resume-selected-session)
Expand Down Expand Up @@ -1127,7 +1127,7 @@ must decide whether this is a no-op."
(cl-letf (((symbol-function 'project-current) (lambda (&rest _) nil))
((symbol-function 'pi-coding-agent--start-process) (lambda (_) nil))
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (chat _input) (setq displayed chat))))
(lambda (chat _input &rest _) (setq displayed chat))))
(unwind-protect
(progn
;; Create named session first.
Expand Down Expand Up @@ -1303,7 +1303,7 @@ must decide whether this is a no-op."
(lambda (&rest _)
(ert-fail "toggle checked process dependencies")))
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (_chat _input)
(lambda (_chat _input &rest _)
(setq displayed t))))
(with-temp-buffer
(setq default-directory root)
Expand All @@ -1323,7 +1323,7 @@ must decide whether this is a no-op."
((symbol-function 'pi-coding-agent--start-process) (lambda (_) nil))
((symbol-function 'pi-coding-agent--check-dependencies) #'ignore)
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (chat _input)
(lambda (chat _input &rest _)
(setq displayed-name (buffer-name chat)))))
(unwind-protect
(progn
Expand Down Expand Up @@ -1478,7 +1478,7 @@ must decide whether this is a no-op."
((symbol-function 'pi-coding-agent--start-process) (lambda (_) nil))
((symbol-function 'pi-coding-agent--check-dependencies) #'ignore)
((symbol-function 'pi-coding-agent--display-buffers)
(lambda (chat-buf input-buf)
(lambda (chat-buf input-buf &rest _)
(setq displayed-chat chat-buf
displayed-input input-buf))))
(setq chat (pi-coding-agent--setup-session root nil)
Expand Down
Loading
Loading