fix: backspace — normalize Croatoan key structs to keywords in on-key
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
Croatoan returns key structs (make-key :name :backspace) for special keys. The on-key handler was comparing these structs to keywords like :backspace with eq, which always failed. Keys like Enter (returned as 13) worked, but Backspace/Tab/arrows didn't. Actually, the user couldn't delete typed characters. Fix: normalize at the top of on-key — if the input is a key struct, extract the :name keyword. This allows the existing keyword-based cond dispatches to work for all keys. Updated all tests to use (make-key :name :enter/backspace/tab) instead of raw integer codes, matching what Croatoan actually sends. TUI: 43/43 pass.
This commit is contained in:
@@ -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")))
|
||||
|
||||
Reference in New Issue
Block a user