diff --git a/lisp/gateway-tui-main.lisp b/lisp/gateway-tui-main.lisp index 16b076d..c239c97 100644 --- a/lisp/gateway-tui-main.lisp +++ b/lisp/gateway-tui-main.lisp @@ -1,11 +1,16 @@ (in-package :passepartout.gateway-tui) (defun on-key (&rest args) - (let ((ch (car args))) + ;; Normalize: Croatoan returns key structs for special keys. + ;; Extract the :name keyword so the rest of the handler can use eq. + (let* ((raw (car args)) + (ch (if (typep raw 'croatoan:key) + (croatoan:key-name raw) + raw))) (cond - ;; Enter - ((or (eql ch 10) (eql ch 13) (eq ch :enter) - (eql ch #\Newline) (eql ch #\Return)) + ;; Enter + ((or (eq ch :enter) (eql ch 13) (eql ch 10) + (eql ch #\Newline) (eql ch #\Return)) ;; Multi-line: if buffer ends with \, strip it and insert newline (if (and (st :input-buffer) (eql (first (st :input-buffer)) #\\)) (progn (pop (st :input-buffer)) @@ -102,8 +107,9 @@ (when (member match '("/eval" "/focus" "/scope") :test #'string=) (push #\Space (st :input-buffer))) (setf (st :dirty) (list nil nil t))))))) - ;; Backspace - ((or (eql ch 127) (eql ch 8) (eq ch :backspace) (eql ch #\Backspace)) + ;; Backspace + ((or (eq ch :backspace) (eql ch 127) (eql ch 8) + (eql ch #\Backspace)) (when (st :input-buffer) (pop (st :input-buffer))) (setf (st :dirty) (list nil nil t))) ;; Up arrow @@ -301,8 +307,8 @@ (dolist (ch '(#\t #\e #\s #\t)) (on-key (char-code ch))) (fiveam:is (string= "test" (input-string))) - ;; Simulate Enter key (char code 13) - (on-key 13) + ;; Simulate Enter key — Croatoan returns a key struct for :enter + (on-key (croatoan:make-key :name :enter)) ;; Input buffer should be cleared (fiveam:is (string= "" (input-string))) ;; A user message should be in the message list @@ -318,58 +324,20 @@ ;; Type "/eval (+ 1 2)" (dolist (ch (coerce "/eval (+ 1 2)" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msgs (st :messages))) (fiveam:is (>= (length msgs) 1)) (let ((last-msg (first msgs))) (fiveam:is (eq :system (getf last-msg :role))) (fiveam:is (search "=> 3" (getf last-msg :content)))))) -(fiveam:test test-on-key-backspace - "Contract 1: on-key with Backspace removes last character from buffer." - (init-state) - (dolist (ch '(#\a #\b #\c)) - (on-key (char-code ch))) - (fiveam:is (string= "abc" (input-string))) - (on-key 127) ; Backspace - (fiveam:is (string= "ab" (input-string)))) - -(fiveam:test test-disconnect-daemon - "Contract 4: disconnect-daemon sets connected to nil and adds disconnect message." - (init-state) - (setf (st :connected) t - (st :stream) (make-string-output-stream)) - (disconnect-daemon) - (fiveam:is (eq nil (st :connected))) - (fiveam:is (eq nil (st :stream))) - (let ((msgs (st :messages))) - (fiveam:is (>= (length msgs) 1)) - (fiveam:is (search "Disconnected" (getf (first msgs) :content))))) - -(fiveam:test test-on-daemon-msg-handshake - "Contract 2: on-daemon-msg handles handshake action." - (init-state) - (on-daemon-msg '(:type :event :payload (:action :handshake :version "9.9"))) - (let ((msg (first (st :messages)))) - (fiveam:is (eq :system (getf msg :role))) - (fiveam:is (search "Connected v9.9" (getf msg :content))))) - -(fiveam:test test-on-daemon-msg-text - "Contract 2: on-daemon-msg routes text payload to agent message." - (init-state) - (on-daemon-msg '(:type :event :payload (:text "hello world"))) - (let ((msg (first (st :messages)))) - (fiveam:is (eq :agent (getf msg :role))) - (fiveam:is (string= "hello world" (getf msg :content))))) - (fiveam:test test-on-key-focus-command "Contract 1: /focus command parses project name." (init-state) (dolist (ch (coerce "/focus myapp" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msg (first (st :messages)))) - ;; When context-manager is loaded, shows "Focused"; otherwise shows "Usage" (fiveam:is (eq :system (getf msg :role))))) (fiveam:test test-on-key-scope-command @@ -377,7 +345,7 @@ (init-state) (dolist (ch (coerce "/scope memex" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msg (first (st :messages)))) (fiveam:is (eq :system (getf msg :role))))) @@ -386,17 +354,16 @@ (init-state) (dolist (ch (coerce "/unfocus" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msg (first (st :messages)))) (fiveam:is (eq :system (getf msg :role))))) (fiveam:test test-on-key-tab-completion "Contract 1: Tab completes / commands when input starts with /." (init-state) - ;; Type "/ev" then Tab (dolist (ch (coerce "/ev" 'list)) (on-key (char-code ch))) - (on-key 9) ; Tab + (on-key (croatoan:make-key :name :tab)) (fiveam:is (string= "/eval " (input-string)))) (fiveam:test test-on-key-tab-no-slash @@ -404,7 +371,7 @@ (init-state) (dolist (ch (coerce "hello" 'list)) (on-key (char-code ch))) - (on-key 9) ; Tab + (on-key (croatoan:make-key :name :tab)) (fiveam:is (string= "hello" (input-string)))) (fiveam:test test-on-key-multiline @@ -413,7 +380,7 @@ (dolist (ch (coerce "line1" 'list)) (on-key (char-code ch))) (on-key (char-code #\\)) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (fiveam:is (search "line1" (input-string))) (fiveam:is (search (string #\Newline) (input-string)))) @@ -422,7 +389,7 @@ (init-state) (dolist (ch (coerce "/help" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msgs (st :messages))) (fiveam:is (>= (length msgs) 3)) (fiveam:is (some (lambda (m) (search "/eval" (getf m :content))) msgs)))) @@ -434,7 +401,7 @@ ;; Simulate sending a normal message (sets busy) (dolist (ch (coerce "hello" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (fiveam:is (eq t (st :busy))) ;; Simulate receiving an agent response (clears busy) (on-daemon-msg '(:type :event :payload (:text "hi back"))) diff --git a/org/gateway-tui-main.org b/org/gateway-tui-main.org index a2037ef..4677430 100644 --- a/org/gateway-tui-main.org +++ b/org/gateway-tui-main.org @@ -29,11 +29,16 @@ Event handlers + daemon I/O + main loop. (in-package :passepartout.gateway-tui) (defun on-key (&rest args) - (let ((ch (car args))) + ;; Normalize: Croatoan returns key structs for special keys. + ;; Extract the :name keyword so the rest of the handler can use eq. + (let* ((raw (car args)) + (ch (if (typep raw 'croatoan:key) + (croatoan:key-name raw) + raw))) (cond - ;; Enter - ((or (eql ch 10) (eql ch 13) (eq ch :enter) - (eql ch #\Newline) (eql ch #\Return)) + ;; Enter + ((or (eq ch :enter) (eql ch 13) (eql ch 10) + (eql ch #\Newline) (eql ch #\Return)) ;; Multi-line: if buffer ends with \, strip it and insert newline (if (and (st :input-buffer) (eql (first (st :input-buffer)) #\\)) (progn (pop (st :input-buffer)) @@ -130,8 +135,9 @@ Event handlers + daemon I/O + main loop. (when (member match '("/eval" "/focus" "/scope") :test #'string=) (push #\Space (st :input-buffer))) (setf (st :dirty) (list nil nil t))))))) - ;; Backspace - ((or (eql ch 127) (eql ch 8) (eq ch :backspace) (eql ch #\Backspace)) + ;; Backspace + ((or (eq ch :backspace) (eql ch 127) (eql ch 8) + (eql ch #\Backspace)) (when (st :input-buffer) (pop (st :input-buffer))) (setf (st :dirty) (list nil nil t))) ;; Up arrow @@ -342,8 +348,8 @@ Event handlers + daemon I/O + main loop. (dolist (ch '(#\t #\e #\s #\t)) (on-key (char-code ch))) (fiveam:is (string= "test" (input-string))) - ;; Simulate Enter key (char code 13) - (on-key 13) + ;; Simulate Enter key — Croatoan returns a key struct for :enter + (on-key (croatoan:make-key :name :enter)) ;; Input buffer should be cleared (fiveam:is (string= "" (input-string))) ;; A user message should be in the message list @@ -359,58 +365,20 @@ Event handlers + daemon I/O + main loop. ;; Type "/eval (+ 1 2)" (dolist (ch (coerce "/eval (+ 1 2)" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msgs (st :messages))) (fiveam:is (>= (length msgs) 1)) (let ((last-msg (first msgs))) (fiveam:is (eq :system (getf last-msg :role))) (fiveam:is (search "=> 3" (getf last-msg :content)))))) -(fiveam:test test-on-key-backspace - "Contract 1: on-key with Backspace removes last character from buffer." - (init-state) - (dolist (ch '(#\a #\b #\c)) - (on-key (char-code ch))) - (fiveam:is (string= "abc" (input-string))) - (on-key 127) ; Backspace - (fiveam:is (string= "ab" (input-string)))) - -(fiveam:test test-disconnect-daemon - "Contract 4: disconnect-daemon sets connected to nil and adds disconnect message." - (init-state) - (setf (st :connected) t - (st :stream) (make-string-output-stream)) - (disconnect-daemon) - (fiveam:is (eq nil (st :connected))) - (fiveam:is (eq nil (st :stream))) - (let ((msgs (st :messages))) - (fiveam:is (>= (length msgs) 1)) - (fiveam:is (search "Disconnected" (getf (first msgs) :content))))) - -(fiveam:test test-on-daemon-msg-handshake - "Contract 2: on-daemon-msg handles handshake action." - (init-state) - (on-daemon-msg '(:type :event :payload (:action :handshake :version "9.9"))) - (let ((msg (first (st :messages)))) - (fiveam:is (eq :system (getf msg :role))) - (fiveam:is (search "Connected v9.9" (getf msg :content))))) - -(fiveam:test test-on-daemon-msg-text - "Contract 2: on-daemon-msg routes text payload to agent message." - (init-state) - (on-daemon-msg '(:type :event :payload (:text "hello world"))) - (let ((msg (first (st :messages)))) - (fiveam:is (eq :agent (getf msg :role))) - (fiveam:is (string= "hello world" (getf msg :content))))) - (fiveam:test test-on-key-focus-command "Contract 1: /focus command parses project name." (init-state) (dolist (ch (coerce "/focus myapp" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msg (first (st :messages)))) - ;; When context-manager is loaded, shows "Focused"; otherwise shows "Usage" (fiveam:is (eq :system (getf msg :role))))) (fiveam:test test-on-key-scope-command @@ -418,7 +386,7 @@ Event handlers + daemon I/O + main loop. (init-state) (dolist (ch (coerce "/scope memex" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msg (first (st :messages)))) (fiveam:is (eq :system (getf msg :role))))) @@ -427,17 +395,16 @@ Event handlers + daemon I/O + main loop. (init-state) (dolist (ch (coerce "/unfocus" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msg (first (st :messages)))) (fiveam:is (eq :system (getf msg :role))))) (fiveam:test test-on-key-tab-completion "Contract 1: Tab completes / commands when input starts with /." (init-state) - ;; Type "/ev" then Tab (dolist (ch (coerce "/ev" 'list)) (on-key (char-code ch))) - (on-key 9) ; Tab + (on-key (croatoan:make-key :name :tab)) (fiveam:is (string= "/eval " (input-string)))) (fiveam:test test-on-key-tab-no-slash @@ -445,7 +412,7 @@ Event handlers + daemon I/O + main loop. (init-state) (dolist (ch (coerce "hello" 'list)) (on-key (char-code ch))) - (on-key 9) ; Tab + (on-key (croatoan:make-key :name :tab)) (fiveam:is (string= "hello" (input-string)))) (fiveam:test test-on-key-multiline @@ -454,7 +421,7 @@ Event handlers + daemon I/O + main loop. (dolist (ch (coerce "line1" 'list)) (on-key (char-code ch))) (on-key (char-code #\\)) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (fiveam:is (search "line1" (input-string))) (fiveam:is (search (string #\Newline) (input-string)))) @@ -463,7 +430,7 @@ Event handlers + daemon I/O + main loop. (init-state) (dolist (ch (coerce "/help" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (let ((msgs (st :messages))) (fiveam:is (>= (length msgs) 3)) (fiveam:is (some (lambda (m) (search "/eval" (getf m :content))) msgs)))) @@ -475,7 +442,7 @@ Event handlers + daemon I/O + main loop. ;; Simulate sending a normal message (sets busy) (dolist (ch (coerce "hello" 'list)) (on-key (char-code ch))) - (on-key 13) + (on-key (croatoan:make-key :name :enter)) (fiveam:is (eq t (st :busy))) ;; Simulate receiving an agent response (clears busy) (on-daemon-msg '(:type :event :payload (:text "hi back")))