fix: remove env var fallback for COLUMNS/LINES (SBCL strips them)

SBCL unconditionally strips COLUMNS and LINES from the environment,
so posix-getenv always returns nil for those names. stty size is
the reliable cross-platform fallback for terminal dimensions.
This commit is contained in:
2026-05-14 13:46:13 -04:00
parent 4fa7e98b80
commit 9b472e281f
2 changed files with 48 additions and 60 deletions

View File

@@ -173,39 +173,32 @@ as a fallback when a keyword is not in *named-colors*.")
(values (sb-alien:deref winsize 1) ;; cols (values (sb-alien:deref winsize 1) ;; cols
(sb-alien:deref winsize 0))) ;; rows (sb-alien:deref winsize 0))) ;; rows
(sb-alien:free-alien winsize)))) (sb-alien:free-alien winsize))))
;; $COLUMNS/$LINES fallback — some systems return 80x24 from ;; stty size — reliable across all shells and terminals.
;; ioctl on stdout's fd even when the terminal is larger. ;; SBCL strips COLUMNS/LINES from the environment, so env var
;; fallbacks won't work for those names.
(ignore-errors (ignore-errors
(let* ((cstr (sb-ext:posix-getenv "COLUMNS"))
(rstr (sb-ext:posix-getenv "LINES"))
(cols (when cstr (parse-integer cstr :junk-allowed t)))
(rows (when rstr (parse-integer rstr :junk-allowed t))))
;; If env vars are missing, use stty size (rows cols)
(unless (and cols rows)
(let* ((out (uiop:run-program '("stty" "size") (let* ((out (uiop:run-program '("stty" "size")
:output :string :output :string
:ignore-error-status t)) :ignore-error-status t))
(parts (and out (uiop:split-string (parts (and out (uiop:split-string
(string-trim '(#\newline #\space) out))))) (string-trim '(#\newline #\space) out)))))
(when (and parts (= (length parts) 2)) (when (and parts (= (length parts) 2))
(let ((stty-rows (parse-integer (first parts) :junk-allowed t)) (let ((rows (parse-integer (first parts) :junk-allowed t))
(stty-cols (parse-integer (second parts) :junk-allowed t))) (cols (parse-integer (second parts) :junk-allowed t)))
(when (and stty-rows stty-cols (> stty-rows 0) (> stty-cols 0)) (when (and rows cols (> rows 0) (> cols 0))
(unless cols (setf cols stty-cols)) (values cols rows))))))
(unless rows (setf rows stty-rows))))))) ;; tput — final shell fallback for esoteric terminals
;; If still missing, try tput as final shell fallback (ignore-errors
(unless (and cols rows) (let* ((out (uiop:run-program '("sh" "-c" "tput cols 2>/dev/null; tput lines 2>/dev/null")
(let ((out (uiop:run-program '("sh" "-c" "tput cols 2>/dev/null; tput lines 2>/dev/null")
:output :string :output :string
:ignore-error-status t))) :ignore-error-status t))
(when out (parts (and out (uiop:split-string
(let* ((parts (uiop:split-string (string-trim '(#\newline #\space) out))) (string-trim '(#\newline #\space) out)))))
(a (when parts (parse-integer (first parts) :junk-allowed t))) (when (and parts (= (length parts) 2))
(b (when (cdr parts) (parse-integer (second parts) :junk-allowed t)))) (let ((cols (parse-integer (first parts) :junk-allowed t))
(when a (setf cols a)) (rows (parse-integer (second parts) :junk-allowed t)))
(when b (setf rows b)))))) (when (and rows cols (> rows 0) (> cols 0))
(when (and cols rows (> cols 0) (> rows 0)) (values cols rows))))))
(values cols rows))))
(values 80 24))) (values 80 24)))
(defmethod backend-write ((b modern-backend) string) (defmethod backend-write ((b modern-backend) string)

View File

@@ -34,35 +34,30 @@
(values (sb-alien:deref winsize 1) (values (sb-alien:deref winsize 1)
(sb-alien:deref winsize 0))) (sb-alien:deref winsize 0)))
(sb-alien:free-alien winsize)))) (sb-alien:free-alien winsize))))
;; stty size — reliable across all shells and terminals.
(ignore-errors (ignore-errors
(let* ((cstr (sb-ext:posix-getenv "COLUMNS"))
(rstr (sb-ext:posix-getenv "LINES"))
(cols (when cstr (parse-integer cstr :junk-allowed t)))
(rows (when rstr (parse-integer rstr :junk-allowed t))))
(unless (and cols rows)
(let* ((out (uiop:run-program '("stty" "size") (let* ((out (uiop:run-program '("stty" "size")
:output :string :output :string
:ignore-error-status t)) :ignore-error-status t))
(parts (and out (uiop:split-string (parts (and out (uiop:split-string
(string-trim '(#\newline #\space) out))))) (string-trim '(#\newline #\space) out)))))
(when (and parts (= (length parts) 2)) (when (and parts (= (length parts) 2))
(let ((stty-rows (parse-integer (first parts) :junk-allowed t)) (let ((rows (parse-integer (first parts) :junk-allowed t))
(stty-cols (parse-integer (second parts) :junk-allowed t))) (cols (parse-integer (second parts) :junk-allowed t)))
(when (and stty-rows stty-cols (> stty-rows 0) (> stty-cols 0)) (when (and rows cols (> rows 0) (> cols 0))
(unless cols (setf cols stty-cols)) (values cols rows))))))
(unless rows (setf rows stty-rows))))))) ;; tput — final shell fallback
(unless (and cols rows) (ignore-errors
(let ((out (uiop:run-program '("sh" "-c" "tput cols 2>/dev/null; tput lines 2>/dev/null") (let* ((out (uiop:run-program '("sh" "-c" "tput cols 2>/dev/null; tput lines 2>/dev/null")
:output :string :output :string
:ignore-error-status t))) :ignore-error-status t))
(when out (parts (and out (uiop:split-string
(let* ((parts (uiop:split-string (string-trim '(#\newline #\space) out))) (string-trim '(#\newline #\space) out)))))
(a (when parts (parse-integer (first parts) :junk-allowed t))) (when (and parts (= (length parts) 2))
(b (when (cdr parts) (parse-integer (second parts) :junk-allowed t)))) (let ((cols (parse-integer (first parts) :junk-allowed t))
(when a (setf cols a)) (rows (parse-integer (second parts) :junk-allowed t)))
(when b (setf rows b)))))) (when (and rows cols (> rows 0) (> cols 0))
(when (and cols rows (> cols 0) (> rows 0)) (values cols rows))))))
(values cols rows))))
(values 80 24))) (values 80 24)))
(defmethod backend-write ((b simple-backend) string) (defmethod backend-write ((b simple-backend) string)