Skip to content
Closed
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
29 changes: 25 additions & 4 deletions pi-coding-agent-menu.el
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,19 @@ Uses commands from pi's `get_commands' RPC."

;;;; Transient Menu

(defun pi-coding-agent--transient-setup (name &rest args)
"Call `transient-setup' with NAME and ARGS for pi menus on Emacs 29.
Some newer Transient builds can run their Emacs 30 text-conversion
suspension path on Emacs 29, where `text-conversion-style' and
`overriding-text-conversion-style' do not exist. Bind those symbols only
while setting up pi's Transient menus; Emacs 29 has no text-conversion
state to preserve, so nil is the correct compatibility value."
(if (< emacs-major-version 30)
(cl-progv '(text-conversion-style overriding-text-conversion-style)
'(nil nil)
(apply #'transient-setup name args))
(apply #'transient-setup name args)))

(transient-define-prefix pi-coding-agent-menu ()
"Pi coding agent menu."
[:description #'pi-coding-agent--menu-description
Expand All @@ -1068,7 +1081,9 @@ Uses commands from pi's `get_commands' RPC."
(pi-coding-agent-menu-default-thinking-display)]
["Info"
("i" "stats" pi-coding-agent-session-stats)
("y" "copy last" pi-coding-agent-copy-last-message)]])
("y" "copy last" pi-coding-agent-copy-last-message)]]
(interactive)
(pi-coding-agent--transient-setup 'pi-coding-agent-menu))

(defun pi-coding-agent-refresh-commands ()
"Refresh commands from pi via RPC."
Expand Down Expand Up @@ -1215,7 +1230,9 @@ Press letter to run, Shift+letter to edit source file."
:setup-children
(lambda (_)
(pi-coding-agent--make-edit-columns
'pi-coding-agent-templates-menu "prompt"))])
'pi-coding-agent-templates-menu "prompt"))]
(interactive)
(pi-coding-agent--transient-setup 'pi-coding-agent-templates-menu))

(transient-define-prefix pi-coding-agent-extensions-menu ()
"All extension commands.
Expand All @@ -1230,7 +1247,9 @@ Press letter to run, Shift+letter to edit source file."
:setup-children
(lambda (_)
(pi-coding-agent--make-edit-columns
'pi-coding-agent-extensions-menu "extension"))])
'pi-coding-agent-extensions-menu "extension"))]
(interactive)
(pi-coding-agent--transient-setup 'pi-coding-agent-extensions-menu))

(transient-define-prefix pi-coding-agent-skills-menu ()
"All available skills.
Expand All @@ -1245,7 +1264,9 @@ Press letter to run, Shift+letter to edit source file."
:setup-children
(lambda (_)
(pi-coding-agent--make-edit-columns
'pi-coding-agent-skills-menu "skill"))])
'pi-coding-agent-skills-menu "skill"))]
(interactive)
(pi-coding-agent--transient-setup 'pi-coding-agent-skills-menu))

;;;; Main Menu Command Sections

Expand Down
139 changes: 92 additions & 47 deletions test/pi-coding-agent-menu-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,52 @@ Pi v0.51.3+ renamed SlashCommandSource from \"template\" to \"prompt\"."
(should (transient-get-suffix 'pi-coding-agent-menu '(3))))
(ignore-errors (transient-remove-suffix 'pi-coding-agent-menu '(3))))))

(defmacro pi-coding-agent-test--with-transient-suffixes (menu &rest body)
"Initialize MENU suffixes without displaying a Transient, then run BODY.
These tests assert menu structure only. Avoid `transient-setup' here so
batch unit tests do not exercise terminal/input setup; the real command
path is covered separately."
(declare (indent 1) (debug (form body)))
`(let (transient--prefix
transient--layout
transient--suffixes
transient--refreshp
(transient--editp nil))
(transient--init-objects ,menu nil nil)
,@body))

(ert-deftest pi-coding-agent-test-menu-command-binds-text-conversion-vars-on-emacs-29 ()
"Pi Transient commands tolerate affected Transient builds on Emacs 29.
Some newer Transient builds can run an Emacs 30-only text-conversion path
while setting up a prefix. On Emacs 29 these symbols are normally
unbound; invoking the real pi menu commands should bind them only for the
`transient-setup' call."
(skip-unless (< emacs-major-version 30))
(skip-unless (not (boundp 'text-conversion-style)))
(skip-unless (not (boundp 'overriding-text-conversion-style)))
(let ((called nil))
(cl-letf (((symbol-function 'transient-setup)
(lambda (name &rest _args)
(push name called)
(unless (boundp 'text-conversion-style)
(signal 'void-variable '(text-conversion-style)))
(unless (boundp 'overriding-text-conversion-style)
(signal 'void-variable '(overriding-text-conversion-style)))
(should-not (symbol-value 'text-conversion-style))
(should-not (symbol-value 'overriding-text-conversion-style)))))
(dolist (menu '(pi-coding-agent-menu
pi-coding-agent-templates-menu
pi-coding-agent-extensions-menu
pi-coding-agent-skills-menu))
(funcall menu)))
(should (equal (nreverse called)
'(pi-coding-agent-menu
pi-coding-agent-templates-menu
pi-coding-agent-extensions-menu
pi-coding-agent-skills-menu)))
(should-not (boundp 'text-conversion-style))
(should-not (boundp 'overriding-text-conversion-style))))

(defun pi-coding-agent-test--suffix-key-bound-p (key)
"Return non-nil if KEY is bound in current transient suffixes."
(cl-find-if (lambda (obj) (equal (oref obj key) key))
Expand All @@ -874,14 +920,14 @@ Pi v0.51.3+ renamed SlashCommandSource from \"template\" to \"prompt\"."
(dolist (menu '(pi-coding-agent-templates-menu
pi-coding-agent-extensions-menu
pi-coding-agent-skills-menu))
(transient-setup menu))))
(pi-coding-agent-test--with-transient-suffixes menu))))

(ert-deftest pi-coding-agent-test-templates-menu-shows-run-keys ()
"Templates submenu binds letter keys to commands."
(let ((pi-coding-agent--commands
'((:name "test-tmpl" :description "A template" :source "prompt"))))
(transient-setup 'pi-coding-agent-templates-menu)
(should (pi-coding-agent-test--suffix-key-bound-p "a"))))
(pi-coding-agent-test--with-transient-suffixes 'pi-coding-agent-templates-menu
(should (pi-coding-agent-test--suffix-key-bound-p "a")))))

(ert-deftest pi-coding-agent-test-templates-menu-shows-edit-keys ()
"Templates submenu binds uppercase letter keys to edit file paths."
Expand All @@ -890,15 +936,15 @@ Pi v0.51.3+ renamed SlashCommandSource from \"template\" to \"prompt\"."
:source "prompt" :path "/tmp/uncle-bob.md" :location "user")
(:name "fix-tests" :description "Fix tests"
:source "prompt" :path "/tmp/fix-tests.md" :location "project"))))
(transient-setup 'pi-coding-agent-templates-menu)
(should (pi-coding-agent-test--suffix-key-bound-p "a"))
(should (pi-coding-agent-test--suffix-key-bound-p "A"))))
(pi-coding-agent-test--with-transient-suffixes 'pi-coding-agent-templates-menu
(should (pi-coding-agent-test--suffix-key-bound-p "a"))
(should (pi-coding-agent-test--suffix-key-bound-p "A")))))

(ert-deftest pi-coding-agent-test-stats-uses-i-key-not-S ()
"Stats is bound to `i' so it doesn't conflict with Skills `S' key."
(transient-setup 'pi-coding-agent-menu)
(should (pi-coding-agent-test--suffix-key-bound-p "i"))
(should-not (pi-coding-agent-test--suffix-key-bound-p "S")))
(pi-coding-agent-test--with-transient-suffixes 'pi-coding-agent-menu
(should (pi-coding-agent-test--suffix-key-bound-p "i"))
(should-not (pi-coding-agent-test--suffix-key-bound-p "S"))))

(ert-deftest pi-coding-agent-test-submenu-handles-more-than-9-commands ()
"Submenu with 13 skills uses letter keys without crashing."
Expand All @@ -908,11 +954,10 @@ Pi v0.51.3+ renamed SlashCommandSource from \"template\" to \"prompt\"."
:description (format "Skill number %d" i)
:source "skill"
:location "user"))))
;; Should not signal an error
(transient-setup 'pi-coding-agent-skills-menu)
;; First and last should be bound
(should (pi-coding-agent-test--suffix-key-bound-p "a"))
(should (pi-coding-agent-test--suffix-key-bound-p "m"))))
(pi-coding-agent-test--with-transient-suffixes 'pi-coding-agent-skills-menu
;; First and last should be bound.
(should (pi-coding-agent-test--suffix-key-bound-p "a"))
(should (pi-coding-agent-test--suffix-key-bound-p "m")))))

(ert-deftest pi-coding-agent-test-submenu-run-and-edit-keys-correspond ()
"Run key `a' and edit key `A' refer to the same command."
Expand All @@ -921,12 +966,12 @@ Pi v0.51.3+ renamed SlashCommandSource from \"template\" to \"prompt\"."
:location "user" :path "/tmp/alpha.md")
(:name "beta" :description "Second" :source "skill"
:location "user" :path "/tmp/beta.md"))))
(transient-setup 'pi-coding-agent-skills-menu)
;; Run keys a, b and edit keys A, B should all be bound
(should (pi-coding-agent-test--suffix-key-bound-p "a"))
(should (pi-coding-agent-test--suffix-key-bound-p "b"))
(should (pi-coding-agent-test--suffix-key-bound-p "A"))
(should (pi-coding-agent-test--suffix-key-bound-p "B"))))
(pi-coding-agent-test--with-transient-suffixes 'pi-coding-agent-skills-menu
;; Run keys a, b and edit keys A, B should all be bound.
(should (pi-coding-agent-test--suffix-key-bound-p "a"))
(should (pi-coding-agent-test--suffix-key-bound-p "b"))
(should (pi-coding-agent-test--suffix-key-bound-p "A"))
(should (pi-coding-agent-test--suffix-key-bound-p "B")))))

;;; Manual Compaction

Expand Down Expand Up @@ -2087,33 +2132,33 @@ The tree is built iteratively to avoid recursion in test setup."
(unwind-protect
(progn
(pi-coding-agent--rebuild-commands-menu)
(transient-setup 'pi-coding-agent-menu)
(let ((thinking-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "t"))
transient--suffixes))
(chat-display-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "h"))
transient--suffixes))
(default-display-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "H"))
transient--suffixes))
(templates-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "T"))
transient--suffixes)))
(should thinking-suffix)
(should (eq (oref thinking-suffix command)
'pi-coding-agent-select-thinking))
(should chat-display-suffix)
(should (equal "This chat"
(transient-format-description chat-display-suffix)))
(should default-display-suffix)
(should (equal "New chat default"
(transient-format-description default-display-suffix)))
(should templates-suffix)))
(pi-coding-agent-test--with-transient-suffixes 'pi-coding-agent-menu
(let ((thinking-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "t"))
transient--suffixes))
(chat-display-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "h"))
transient--suffixes))
(default-display-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "H"))
transient--suffixes))
(templates-suffix
(cl-find-if (lambda (obj)
(equal (oref obj key) "T"))
transient--suffixes)))
(should thinking-suffix)
(should (eq (oref thinking-suffix command)
'pi-coding-agent-select-thinking))
(should chat-display-suffix)
(should (equal "This chat"
(transient-format-description chat-display-suffix)))
(should default-display-suffix)
(should (equal "New chat default"
(transient-format-description default-display-suffix)))
(should templates-suffix))))
(ignore-errors (transient-remove-suffix 'pi-coding-agent-menu '(3))))))

;;; sourceInfo normalization
Expand Down
Loading