diff --git a/lisp/channel-tui-main.lisp b/lisp/channel-tui-main.lisp index 7fbf315..3760b00 100644 --- a/lisp/channel-tui-main.lisp +++ b/lisp/channel-tui-main.lisp @@ -863,11 +863,12 @@ (add-msg :system "* Swank unavailable *")))) (cl-tty.input:with-raw-terminal (cl-tty.backend:with-terminal (be w h) - ;; Log backend info and terminal dimensions - (let ((backend-type (if (typep be 'cl-tty.backend:modern-backend) - "modern" "simple"))) - (add-msg :system (format nil "* ~a backend ~dx~d *" backend-type w h))) - ;; Initial render + (let ((tty (sb-sys:make-fd-stream 0 :input t :buffering :none))) + ;; Log backend info and terminal dimensions + (let ((backend-type (if (typep be 'cl-tty.backend:modern-backend) + "modern" "simple"))) + (add-msg :system (format nil "* ~a backend ~dx~d *" backend-type w h))) + ;; Initial render (cl-tty.backend:backend-clear be) (view-status be w h) (view-chat be w h) @@ -884,26 +885,66 @@ (st :busy) nil) (add-msg :system "* Connection lost — type /reconnect to retry *")))) ;; Read key input via cl-tty read-event (10ms timeout) - (multiple-value-bind (type data) - (cl-tty.input:read-event be :timeout 0.01) - (when (eq type :resize) - (multiple-value-setq (w h) (cl-tty.backend:backend-size be)) - (setf (st :dirty) (list t t t))) - (when data - (let* ((ke data) - (ch (if (cl-tty.input:key-event-p ke) - (let ((k (cl-tty.input:key-event-key ke))) - (if (cl-tty.input:key-event-ctrl ke) - (intern (format nil "CTRL-~a" k) :keyword) - k)) - ke))) - (case ch - (:CTRL-Q (setf (st :running) nil)) - (:CTRL-P (command-palette-show-commands)) - (:CTRL-B (setf (st :sidebar-visible) (not (st :sidebar-visible))) - (setf (st :dirty) (list t t nil))) - (:CTRL-L (setf (st :dirty) (list t t t))) - (t (on-key ch)))))) + ;; Read key input via blocking read-char with 0.1s timeout + ;; (sb-unix:unix-simple-poll returns NIL on fd 0 in this SBCL, + ;; so read-char-no-hang and read-event never fire. Raw blocking + ;; read-char with sb-ext:with-timeout is the reliable fallback.) + (handler-case + (sb-ext:with-timeout 0.1 + (let* ((raw-ch (read-char tty nil 'eof)) + (code (when (characterp raw-ch) (char-code raw-ch)))) + (when code + (let ((ch (cond + ((= code 13) :enter) + ((= code 10) :enter) + ((= code 27) :escape) + ((= code 9) :tab) + ((or (= code 127) (= code 8)) :backspace) + ((and (>= code 1) (<= code 26)) + (intern (string-upcase + (format nil "CTRL-~a" + (code-char (+ #x60 code)))) + :keyword)) + (t raw-ch)))) + (case ch + (:CTRL-Q (setf (st :running) nil)) + (:CTRL-P (command-palette-show-commands)) + (:CTRL-B (setf (st :sidebar-visible) (not (st :sidebar-visible))) + (setf (st :dirty) (list t t nil))) + (:CTRL-L (setf (st :dirty) (list t t t))) + (t (if (st :dialog-stack) + (let* ((dlg (car (st :dialog-stack))) + (sel (cl-tty.dialog:dialog-content dlg))) + (cond + ((eql ch :escape) + (pop (st :dialog-stack)) + (setf (st :minibuffer-active) nil) + (setf (st :command-palette-active) nil) + (setf (st :dirty) (list t t nil))) + ((member ch '(:up :down)) + (if (eql ch :up) + (cl-tty.select:select-prev sel) + (cl-tty.select:select-next sel))) + ((member ch '(:enter 13 10)) + (let* ((filtered (cl-tty.select:select-filtered-options sel)) + (idx (cl-tty.select:select-selected-index sel)) + (item (when (< idx (length filtered)) + (third (nth idx filtered))))) + (when item + (let ((cb (cl-tty.select:select-on-select sel))) + (when cb (funcall cb item)))))) + ((and (characterp ch) (graphic-char-p ch)) + (setf (cl-tty.select:select-filter sel) + (concatenate 'string + (or (cl-tty.select:select-filter sel) "") + (string ch)))) + ((member ch '(:backspace 127 8)) + (let ((f (cl-tty.select:select-filter sel))) + (when (> (length f) 0) + (setf (cl-tty.select:select-filter sel) + (subseq f 0 (1- f)))))))) + (on-key ch)))))))) + (sb-ext:timeout ())) ;; Re-query terminal size once after daemon handshake (when (and (st :connected) (st :daemon-version) (not (st :size-queried))) (multiple-value-setq (w h) (cl-tty.backend:backend-size be)) @@ -951,8 +992,9 @@ (t (theme-color :agent-fg))) nil :bold sel-p) (incf y-off))))))) - (sleep 0.1)))) - (disconnect-daemon))) + (sleep 0.1))) + (close tty))) + (disconnect-daemon))) (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t))