v0.8.0: TUI simplification — process-key-event, with-frame, inline reader

Replace queue-based key dispatch with process-key-event (inline in reader,
zero latency between keypress and render).
Add with-frame to cl-tty.backend (error-safe begin-sync/end-sync wrapper).
Use with-frame in redraw instead of manual begin-sync/end-sync.
Add initial render before main loop (UI appears before first read-event).
Remove position-cursor (replaced by inline block cursor in view-input).
Remove input-string/input-insert-char/input-delete-char wrappers.
Remove :input-buffer/:cursor-pos from state (managed by text-input widget).
passepartout script: set *debugger-hook* nil and failure-behaviour :warn
before quickload to survive compile warnings; remove cache-clear line.
This commit is contained in:
2026-05-18 20:55:22 -04:00
parent f783b45ac7
commit b61191bec2
4 changed files with 91 additions and 86 deletions

View File

@@ -758,6 +758,55 @@ supplied (e.g. \"/\"), pre-fill the select filter with it."
** Connection
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-main.lisp
;; Process a key-event: route through dialog, keymap, navigation, or text-input.
(defun process-key-event (event)
(let* ((k (cl-tty.input:key-event-key event)))
(cond
((st :dialog-stack)
(let* ((dlg (car (st :dialog-stack)))
(sel (cl-tty.dialog:dialog-content dlg)))
(cond
((eq k :escape)
(pop (st :dialog-stack))
(setf (st :dirty) (list t t nil)))
((member k '(:up :down))
(if (eq k :up)
(cl-tty.dialog:select-prev sel)
(cl-tty.dialog:select-next sel))
(setf (st :dirty) (list t t nil)))
((eq k :enter)
(let* ((filtered (cl-tty.dialog:select-filtered-options sel))
(idx (cl-tty.dialog:select-selected-index sel))
(item (when (< idx (length filtered))
(third (nth idx filtered)))))
(when item
(let ((cb (cl-tty.dialog:select-on-select sel)))
(when cb (funcall cb item))))
(pop (st :dialog-stack))
(setf (st :dirty) (list t t nil))))
((let ((ch (code-char (cl-tty.input:key-event-code event))))
(and ch (graphic-char-p ch))
(setf (cl-tty.dialog:select-filter sel)
(concatenate 'string
(or (cl-tty.dialog:select-filter sel) "")
(string ch)))))
((eq k :backspace)
(let* ((f (cl-tty.dialog:select-filter sel))
(len (length (or f ""))))
(when (> len 0)
(setf (cl-tty.dialog:select-filter sel)
(subseq f 0 (1- len)))))))))
((cl-tty.input:dispatch-key-event event)
(setf (st :dirty) (list t t nil)))
((member k '(:enter :tab :escape :up :down))
(on-key k))
(t (handler-case
(progn
(cl-tty.input:handle-text-input (st :text-input) event)
(setf (st :dirty) (list nil nil t)))
(error (c)
(add-msg :system (format nil "* Input error: ~a *" c))))))))
(defun connect-daemon (&optional (host "127.0.0.1") (start-port 9105) (end-port 9115))
"Try to connect to daemon once across START-PORT to END-PORT.
Returns T on success, nil on failure. Does NOT wait or retry."
@@ -919,8 +968,11 @@ Returns T on success, nil on failure. Does NOT wait or retry."
(loop while (and (st :running) (not (st :connected)))
do (connect-daemon)
(unless (st :connected) (sleep 5))))
:name "daemon-auto-connect"))
(loop while (st :running) do
:name "daemon-auto-connect"))
;; Initial render before first read-event (which may block)
(unless (st :dialog-stack)
(redraw be w h))
(loop while (st :running) do
(dolist (ev (drain-queue))
(cond
((eq (getf ev :type) :daemon)
@@ -982,25 +1034,8 @@ Returns T on success, nil on failure. Does NOT wait or retry."
(let ((new-size resize-data))
(setq w (car new-size) h (cdr new-size))
(setf (st :dirty) (list t t t))))
((cl-tty.input:key-event-p ev)
(let* ((k (cl-tty.input:key-event-key ev))
(ctrl (cl-tty.input:key-event-ctrl ev))
(code (cl-tty.input:key-event-code ev))
(ch (cond
;; Ctrl+letter → :CTRL-X keyword (compatible with case dispatch)
(ctrl (let ((c (char (symbol-name k) 0)))
(intern (string-upcase (format nil "CTRL-~a" c)) :keyword)))
;; PageUp/PageDown → :ppage/:npage
((eq k :page-up) :ppage)
((eq k :page-down) :npage)
;; Single-char keyword → printable character
((and (keywordp k) (= (length (symbol-name k)) 1))
(code-char code))
;; Everything else → pass keyword through
(t k))))
(queue-event
(list :type :key
:payload (list :code (or code 0) :ch ch)))))))
((cl-tty.input:key-event-p ev)
(process-key-event ev))))
(error (c)
(add-msg :system (format nil "* Reader error: ~a *" c))))
;; Guard w and h before render (resize or other code may have set them to nil)
@@ -1062,12 +1097,9 @@ Returns T on success, nil on failure. Does NOT wait or retry."
(cl-tty.backend:draw-text be 0 (- h 3)
(format nil "> ~a" (or filter ""))
(theme-color :input-prompt) bg-p))
(cl-tty.backend:end-sync be))
(sleep 0.1)
;; Show terminal cursor at input position every frame
(unless (st :dialog-stack)
(passepartout.channel-tui:position-cursor be w h))))
(progn (disconnect-daemon)))))
(cl-tty.backend:end-sync be))
(sleep 0.1)))
(progn (disconnect-daemon)))))
#+END_SRC
* Test Suite