fix: add CSI positioning and ioctl sizing to simple-backend
- backend-size now uses TIOCGWINSZ ioctl (like modern-backend) - draw-text adds \033[row;colH CSI cursor positioning - draw-rect fills background with space characters at position - draw-border uses CSI positioning instead of raw newlines+spaces - Added cursor-hide/cursor-show, cursor-move, initialize/shutdown - Detection: broader DA1 check (any ANSI response, not just kitty) - Detection: added TERM-based fallback for modern terminal detection
This commit is contained in:
@@ -31,13 +31,26 @@ TIMEOUT seconds. Returns the response string, or nil if no response."
|
||||
response)))
|
||||
|
||||
(defun detect-backend-by-da1 ()
|
||||
"Send DA1 (ESC[c) query and check for kitty terminal response code.
|
||||
Returns T if terminal reports kitty compatibility codes."
|
||||
"Send DA1 (ESC[c) query and check for any terminal response.
|
||||
Returns T if the terminal responds to DA1 (indicating an ANSI-compatible terminal)."
|
||||
(let ((response (query-terminal (format nil "~C[c" (code-char 27)))))
|
||||
(when response
|
||||
;; DA1 response format: ESC [ ? digits ; digits c
|
||||
;; Kitty reports code 62 in the response
|
||||
(search "?62" response))))
|
||||
;; Any DA1 response (ESC [ ? digits ... c) means the terminal
|
||||
;; understands ANSI escape sequences — good enough for modern-backend
|
||||
(> (length response) 0))))
|
||||
|
||||
(defun detect-backend-by-term ()
|
||||
"Check TERM environment variable for modern terminal types.
|
||||
Returns :modern if TERM contains xterm, tmux, screen, kitty,
|
||||
alacritty, foot, wezterm, or ghostty."
|
||||
(let ((term (sb-ext:posix-getenv "TERM")))
|
||||
(when term
|
||||
;; Known non-modern terms
|
||||
(unless (or (string-equal term "dumb")
|
||||
(string-equal term "dump")
|
||||
(string-equal term "emacs")
|
||||
(search "52" term)) ; VT52 — no ANSI
|
||||
:modern))))
|
||||
|
||||
(defun detect-backend ()
|
||||
"Auto-detect the appropriate backend for the current terminal.
|
||||
@@ -47,6 +60,7 @@ Result is cached in *detected-backend* for subsequent calls."
|
||||
(setf *detected-backend*
|
||||
(if (and (detect-backend-by-tty)
|
||||
(or (eql (detect-backend-by-env) :modern)
|
||||
(detect-backend-by-da1)))
|
||||
(detect-backend-by-da1)
|
||||
(eql (detect-backend-by-term) :modern)))
|
||||
(make-modern-backend)
|
||||
(make-simple-backend)))))
|
||||
|
||||
@@ -10,20 +10,36 @@
|
||||
:output-stream (or output-stream *standard-output*)))
|
||||
|
||||
(defmethod initialize-backend ((b simple-backend))
|
||||
(backend-write b (format nil "~C[2J~C[H" #\Esc #\Esc)) ; clear + home
|
||||
(backend-write b (format nil "~C[?25l" #\Esc)) ; hide cursor
|
||||
b)
|
||||
|
||||
(defmethod shutdown-backend ((b simple-backend))
|
||||
(backend-write b (format nil "~C[?25h" #\Esc)) ; show cursor
|
||||
(values))
|
||||
|
||||
(defmethod suspend-backend ((b simple-backend))
|
||||
(backend-write b (format nil "~C[?25h" #\Esc))
|
||||
(values))
|
||||
|
||||
(defmethod resume-backend ((b simple-backend))
|
||||
(backend-write b (format nil "~C[?25l" #\Esc))
|
||||
(values))
|
||||
|
||||
(defmethod backend-size ((b simple-backend))
|
||||
;; Try ioctl, fall back to 80x24
|
||||
(values 80 24))
|
||||
(or (ignore-errors
|
||||
(let* ((+tiocgwinsz+ 21523)
|
||||
(winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(sb-unix:unix-ioctl (sb-sys:fd-stream-fd
|
||||
(backend-output-stream b))
|
||||
+tiocgwinsz+
|
||||
(sb-alien:alien-sap winsize))
|
||||
(values (sb-alien:deref winsize 1)
|
||||
(sb-alien:deref winsize 0)))
|
||||
(sb-alien:free-alien winsize))))
|
||||
(values 80 24)))
|
||||
|
||||
(defmethod backend-write ((b simple-backend) string)
|
||||
(let ((stream (backend-output-stream b)))
|
||||
@@ -31,16 +47,29 @@
|
||||
(finish-output stream)
|
||||
(length string)))
|
||||
|
||||
(defmethod capable-p ((b simple-backend) feature)
|
||||
(declare (ignore feature))
|
||||
nil)
|
||||
|
||||
(defun %cursor-move (x y)
|
||||
(format nil "~C[~d;~dH" #\Esc (1+ y) (1+ x)))
|
||||
|
||||
(defmethod cursor-hide ((b simple-backend))
|
||||
(backend-write b (format nil "~C[?25l" #\Esc)))
|
||||
|
||||
(defmethod cursor-show ((b simple-backend))
|
||||
(backend-write b (format nil "~C[?25h" #\Esc)))
|
||||
|
||||
(defmethod cursor-move ((b simple-backend) x y)
|
||||
(backend-write b (%cursor-move x y)))
|
||||
|
||||
(defmethod draw-text ((b simple-backend) x y string fg bg
|
||||
&key bold italic underline reverse dim blink
|
||||
&allow-other-keys)
|
||||
(declare (ignore x y fg bg bold italic underline reverse dim blink))
|
||||
(backend-write b string))
|
||||
(declare (ignore fg bg bold italic underline reverse dim blink))
|
||||
(backend-write b (concatenate 'string (%cursor-move x y) string)))
|
||||
|
||||
(defun %simple-border-char (pos)
|
||||
"Return ASCII border character at POS.
|
||||
POS is :top-left, :top-right, :bottom-left, :bottom-right,
|
||||
:horizontal, or :vertical."
|
||||
(case pos
|
||||
((:top-left :top-right :bottom-left :bottom-right) #\+)
|
||||
(:horizontal #\-)
|
||||
@@ -55,10 +84,8 @@ POS is :top-left, :top-right, :bottom-left, :bottom-right,
|
||||
(tr (%simple-border-char :top-right))
|
||||
(bl (%simple-border-char :bottom-left))
|
||||
(br (%simple-border-char :bottom-right)))
|
||||
;; Position cursor with newlines and spaces (no escape sequences)
|
||||
(dotimes (row y) (backend-write b (string #\Newline)))
|
||||
;; Top edge with optional title
|
||||
(backend-write b (make-string x :initial-element #\space))
|
||||
;; Top edge
|
||||
(backend-write b (%cursor-move x y))
|
||||
(backend-write b (string tl))
|
||||
(if (and title (plusp (length title)))
|
||||
(let* ((align (or title-align :left))
|
||||
@@ -83,23 +110,23 @@ POS is :top-left, :top-right, :bottom-left, :bottom-right,
|
||||
(backend-write b (string tr))
|
||||
;; Sides
|
||||
(loop for i from 1 below (1- height)
|
||||
do (backend-write b (string #\Newline))
|
||||
(backend-write b (make-string x :initial-element #\space))
|
||||
do (backend-write b (%cursor-move x (+ y i)))
|
||||
(backend-write b (string v))
|
||||
(backend-write b (make-string (- width 2) :initial-element #\space))
|
||||
(backend-write b (string v)))
|
||||
;; Bottom edge
|
||||
(backend-write b (string #\Newline))
|
||||
(backend-write b (make-string x :initial-element #\space))
|
||||
(backend-write b (%cursor-move x (+ y height -1)))
|
||||
(backend-write b (string bl))
|
||||
(backend-write b (make-string (- width 2) :initial-element h))
|
||||
(backend-write b (string br))))
|
||||
|
||||
(defmethod draw-rect ((b simple-backend) x y width height
|
||||
&key bg)
|
||||
(declare (ignore x y width height bg))
|
||||
;; On simple backend, background fill is a no-op
|
||||
(values))
|
||||
(declare (ignore bg))
|
||||
(let ((line (make-string width :initial-element #\space)))
|
||||
(loop for row from 0 below height
|
||||
do (backend-write b (%cursor-move x (+ y row)))
|
||||
(backend-write b line))))
|
||||
|
||||
(defmethod draw-link ((b simple-backend) x y string url
|
||||
&key fg bg)
|
||||
@@ -109,7 +136,4 @@ POS is :top-left, :top-right, :bottom-left, :bottom-right,
|
||||
(defmethod draw-ellipsis ((b simple-backend) x y width
|
||||
&key fg bg)
|
||||
(declare (ignore width fg bg))
|
||||
;; Position using newlines+spaces (simple-backend pattern)
|
||||
(dotimes (row y) (backend-write b (string #\Newline)))
|
||||
(backend-write b (make-string x :initial-element #\Space))
|
||||
(backend-write b "..."))
|
||||
(draw-text b x y "..." nil nil))
|
||||
|
||||
Reference in New Issue
Block a user