From de1864bd94768e861292086cefb71122c56447a8 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Fri, 15 May 2026 08:51:13 -0400 Subject: [PATCH] fix: backend-size returns both cols and rows via multi-value-bind (or discards secondary values) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OR pattern inside backend-size used (or (multiple-value-bind ...) ...), but multiple-value-bind only returns the primary value of its body. When the env-var shortcut was removed, both calls to backend-size (the cols nth-value 0 and rows nth-value 1) returned the same primary value, making rows always nil. Restructure with nested multiple-value-bind/values chains so both return values propagate correctly through all fallback stages. Also remove MY_TERM_COLS/ROWS env-var pre-check — it returned stale startup dimensions after terminal resize. --- org/backend-protocol.org | 109 ++++++++++++++++++--------------------- org/modern-backend.org | 81 +++++++++++++---------------- src/backend/modern.lisp | 69 ++++++++++++------------- src/backend/simple.lisp | 97 +++++++++++++++++----------------- 4 files changed, 165 insertions(+), 191 deletions(-) diff --git a/org/backend-protocol.org b/org/backend-protocol.org index d17271f..c9f4eb4 100644 --- a/org/backend-protocol.org +++ b/org/backend-protocol.org @@ -820,68 +820,61 @@ No-op — simple backend has no terminal state to restore. Queries actual terminal dimensions through a fallback chain, with a hard-coded 80x24 at the end: -1. **Env var pre-check** — ~MY_TERM_COLS~ / ~MY_TERM_ROWS~, set by the - calling script before ~exec sbcl~. Checked with ~return-from~ so that - /both/ values are preserved (Common Lisp's ~or~ discards secondary - values). -2. **ioctl on fd 0 (stdin)** — the parent's real terminal fd. -3. **ioctl on stdout** — fast and correct after SIGWINCH at runtime. -4. **ioctl on ~/dev/tty~** — fallback when stdin/stdout are pipes. -5. **~(values 80 24)~** — last resort. +1. **ioctl on fd 0 (stdin)** — the parent's real terminal fd. +2. **ioctl on stdout** — fast and correct after SIGWINCH at runtime. +3. **ioctl on ~/dev/tty~** — fallback when stdin/stdout are pipes. +4. **~(values 80 24)~** — last resort. #+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp (defmethod backend-size ((b simple-backend)) - ;; MY_TERM_COLS/MY_TERM_ROWS — set by the calling script. - ;; Check with return-from to preserve both values. - (let ((cstr (sb-ext:posix-getenv "MY_TERM_COLS")) - (rstr (sb-ext:posix-getenv "MY_TERM_ROWS"))) - (when (and cstr rstr) - (let ((cols (parse-integer cstr :junk-allowed t)) - (rows (parse-integer rstr :junk-allowed t))) - (when (and cols rows (> cols 0) (> rows 0)) - (return-from backend-size (values cols rows)))))) - (or ;; ioctl on fd 0 (stdin) — the parent's own terminal. - (multiple-value-bind (cols rows) - (ignore-errors - (let ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (unwind-protect - (let ((ok (sb-unix:unix-ioctl 0 21523 - (sb-alien:alien-sap winsize)))) - (when ok - (let ((c (sb-alien:deref winsize 1)) - (r (sb-alien:deref winsize 0))) - (when (and c r (> c 0) (> r 0)) - (values c r))))) - (sb-alien:free-alien winsize)))) - (when (and cols rows (> cols 0) (> rows 0)) - (values cols rows))) - ;; ioctl on stdout fd — fast, correct after SIGWINCH at runtime. - (multiple-value-bind (cols rows) - (ignore-errors - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (unwind-protect - (let ((ok (sb-unix:unix-ioctl - (sb-sys:fd-stream-fd (backend-output-stream b)) - 21523 (sb-alien:alien-sap winsize)))) - (when ok - (values (sb-alien:deref winsize 1) - (sb-alien:deref winsize 0)))) - (sb-alien:free-alien winsize)))) - (when (and cols rows (> cols 0) (> rows 0)) - (values cols rows))) + ;; Try ioctl on fd 0 (stdin), then stdout, then /dev/tty, then 80x24. + ;; Use multiple-value-bind/values to preserve both cols and rows + ;; (or discards secondary values). + (multiple-value-bind (cols rows) (ignore-errors - (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) - (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) - (unwind-protect - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (let ((ok (sb-unix:unix-ioctl tty-fd 21523 - (sb-alien:alien-sap winsize)))) - (when ok - (values (sb-alien:deref winsize 1) - (sb-alien:deref winsize 0)))) - (sb-alien:free-alien winsize)) - (sb-unix:unix-close tty-fd))))) - (values 80 24))) + (let ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (let ((ok (sb-unix:unix-ioctl 0 21523 + (sb-alien:alien-sap winsize)))) + (when ok + (let ((c (sb-alien:deref winsize 1)) + (r (sb-alien:deref winsize 0))) + (when (and c r (> c 0) (> r 0)) + (values c r))))) + (sb-alien:free-alien winsize)))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + ;; ioctl on stdout fd + (multiple-value-bind (cols rows) + (ignore-errors + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (let ((ok (sb-unix:unix-ioctl + (sb-sys:fd-stream-fd (backend-output-stream b)) + 21523 (sb-alien:alien-sap winsize)))) + (when ok + (values (sb-alien:deref winsize 1) + (sb-alien:deref winsize 0)))) + (sb-alien:free-alien winsize)))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + ;; Direct ioctl on /dev/tty + (multiple-value-bind (cols rows) + (ignore-errors + (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) + (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) + (unwind-protect + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (let ((ok (sb-unix:unix-ioctl tty-fd 21523 + (sb-alien:alien-sap winsize)))) + (when ok + (values (sb-alien:deref winsize 1) + (sb-alien:deref winsize 0)))) + (sb-alien:free-alien winsize)) + (sb-unix:unix-close tty-fd))))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + (values 80 24)))))))) #+END_SRC *** Backend Write (Simple) diff --git a/org/modern-backend.org b/org/modern-backend.org index 334fb19..01c54bc 100644 --- a/org/modern-backend.org +++ b/org/modern-backend.org @@ -642,59 +642,48 @@ is responsible for redrawing the full screen after resume. (values)) #+END_SRC -** Backend-size via ioctl and env vars +** Backend-size via ioctl *** backend-size -Uses a fallback chain to determine terminal dimensions: - -1. **Env var pre-check** — ~MY_TERM_COLS~ / ~MY_TERM_ROWS~, set by - the calling script before ~exec sbcl~. Uses ~return-from~ to preserve - both values (~or~ discards secondary values). -2. **ioctl on stdout** — fast, correct after SIGWINCH at runtime. -3. **ioctl on ~/dev/tty~** — fallback when stdout is not a terminal. -4. **~(values 80 24)~** — last resort. +Uses ioctl (TIOCGWINSZ = 21523) to query actual terminal dimensions +from the kernel, with a ~/dev/tty~ fallback and 80x24 last resort. #+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod backend-size ((b modern-backend)) - ;; MY_TERM_COLS/MY_TERM_ROWS — set by the calling script. - ;; Check FIRST with return-from so both values (cols and rows) - ;; are preserved (or discards secondaries). - (let ((cstr (sb-ext:posix-getenv "MY_TERM_COLS")) - (rstr (sb-ext:posix-getenv "MY_TERM_ROWS"))) - (when (and cstr rstr) - (let ((cols (parse-integer cstr :junk-allowed t)) - (rows (parse-integer rstr :junk-allowed t))) - (when (and cols rows (> cols 0) (> rows 0)) - (return-from backend-size (values cols rows)))))) - (or - (multiple-value-bind (cols rows) - (ignore-errors - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (unwind-protect - (let ((ok (sb-unix:unix-ioctl - (sb-sys:fd-stream-fd (backend-output-stream b)) - 21523 (sb-alien:alien-sap winsize)))) - (when ok - (values (sb-alien:deref winsize 1) ;; cols - (sb-alien:deref winsize 0)))) ;; rows - (sb-alien:free-alien winsize)))) - (when (and cols rows (> cols 0) (> rows 0)) - (values cols rows))) - ;; Direct ioctl on /dev/tty. + ;; Try ioctl on stdout, fall back to /dev/tty, then 80x24. + ;; Each arm uses multiple-value-bind/values to preserve both cols and rows + ;; (or discards secondary values, so we avoid it for multi-value returns). + (multiple-value-bind (cols rows) (ignore-errors - (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) - (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) - (unwind-protect - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (let ((ok (sb-unix:unix-ioctl tty-fd 21523 - (sb-alien:alien-sap winsize)))) - (when ok - (let ((cols (sb-alien:deref winsize 1)) - (rows (sb-alien:deref winsize 0))) - (values cols rows))))) - (sb-unix:unix-close tty-fd))))) - (values 80 24))) + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (let ((ok (sb-unix:unix-ioctl + (sb-sys:fd-stream-fd (backend-output-stream b)) + 21523 (sb-alien:alien-sap winsize)))) + (when ok + (values (sb-alien:deref winsize 1) ;; cols + (sb-alien:deref winsize 0)))) ;; rows + (sb-alien:free-alien winsize)))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + ;; Direct ioctl on /dev/tty. + (multiple-value-bind (cols rows) + (ignore-errors + (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) + (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) + (unwind-protect + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (let ((ok (sb-unix:unix-ioctl tty-fd 21523 + (sb-alien:alien-sap winsize)))) + (when ok + (let ((cols (sb-alien:deref winsize 1)) + (rows (sb-alien:deref winsize 0))) + (values cols rows))))) + (sb-unix:unix-close tty-fd))))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + (values 80 24)))))) #+END_SRC ** Capability query and write diff --git a/src/backend/modern.lisp b/src/backend/modern.lisp index f10e360..8666491 100644 --- a/src/backend/modern.lisp +++ b/src/backend/modern.lisp @@ -162,44 +162,39 @@ as a fallback when a keyword is not in *named-colors*.") (values)) (defmethod backend-size ((b modern-backend)) - ;; MY_TERM_COLS/MY_TERM_ROWS — set by the calling script. - ;; Check FIRST with return-from so both values (cols and rows) - ;; are preserved (or discards secondaries). - (let ((cstr (sb-ext:posix-getenv "MY_TERM_COLS")) - (rstr (sb-ext:posix-getenv "MY_TERM_ROWS"))) - (when (and cstr rstr) - (let ((cols (parse-integer cstr :junk-allowed t)) - (rows (parse-integer rstr :junk-allowed t))) - (when (and cols rows (> cols 0) (> rows 0)) - (return-from backend-size (values cols rows)))))) - (or - (multiple-value-bind (cols rows) - (ignore-errors - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (unwind-protect - (let ((ok (sb-unix:unix-ioctl - (sb-sys:fd-stream-fd (backend-output-stream b)) - 21523 (sb-alien:alien-sap winsize)))) - (when ok - (values (sb-alien:deref winsize 1) ;; cols - (sb-alien:deref winsize 0)))) ;; rows - (sb-alien:free-alien winsize)))) - (when (and cols rows (> cols 0) (> rows 0)) - (values cols rows))) - ;; Direct ioctl on /dev/tty. + ;; Try ioctl on stdout, fall back to /dev/tty, then 80x24. + ;; Each arm uses multiple-value-bind/values to preserve both cols and rows + ;; (or discards secondary values, so we avoid it for multi-value returns). + (multiple-value-bind (cols rows) (ignore-errors - (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) - (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) - (unwind-protect - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (let ((ok (sb-unix:unix-ioctl tty-fd 21523 - (sb-alien:alien-sap winsize)))) - (when ok - (let ((cols (sb-alien:deref winsize 1)) - (rows (sb-alien:deref winsize 0))) - (values cols rows))))) - (sb-unix:unix-close tty-fd))))) - (values 80 24))) + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (let ((ok (sb-unix:unix-ioctl + (sb-sys:fd-stream-fd (backend-output-stream b)) + 21523 (sb-alien:alien-sap winsize)))) + (when ok + (values (sb-alien:deref winsize 1) ;; cols + (sb-alien:deref winsize 0)))) ;; rows + (sb-alien:free-alien winsize)))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + ;; Direct ioctl on /dev/tty. + (multiple-value-bind (cols rows) + (ignore-errors + (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) + (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) + (unwind-protect + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (let ((ok (sb-unix:unix-ioctl tty-fd 21523 + (sb-alien:alien-sap winsize)))) + (when ok + (let ((cols (sb-alien:deref winsize 1)) + (rows (sb-alien:deref winsize 0))) + (values cols rows))))) + (sb-unix:unix-close tty-fd))))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + (values 80 24)))))) (defmethod backend-write ((b modern-backend) string) (let ((stream (backend-output-stream b))) diff --git a/src/backend/simple.lisp b/src/backend/simple.lisp index 936761a..8923771 100644 --- a/src/backend/simple.lisp +++ b/src/backend/simple.lisp @@ -22,57 +22,54 @@ (values)) (defmethod backend-size ((b simple-backend)) - ;; MY_TERM_COLS/MY_TERM_ROWS — set by the calling script. - ;; Check with return-from to preserve both values. - (let ((cstr (sb-ext:posix-getenv "MY_TERM_COLS")) - (rstr (sb-ext:posix-getenv "MY_TERM_ROWS"))) - (when (and cstr rstr) - (let ((cols (parse-integer cstr :junk-allowed t)) - (rows (parse-integer rstr :junk-allowed t))) - (when (and cols rows (> cols 0) (> rows 0)) - (return-from backend-size (values cols rows)))))) - (or ;; ioctl on fd 0 (stdin) — the parent's own terminal. - (multiple-value-bind (cols rows) - (ignore-errors - (let ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (unwind-protect - (let ((ok (sb-unix:unix-ioctl 0 21523 - (sb-alien:alien-sap winsize)))) - (when ok - (let ((c (sb-alien:deref winsize 1)) - (r (sb-alien:deref winsize 0))) - (when (and c r (> c 0) (> r 0)) - (values c r))))) - (sb-alien:free-alien winsize)))) - (when (and cols rows (> cols 0) (> rows 0)) - (values cols rows))) - ;; ioctl on stdout fd — fast, correct after SIGWINCH at runtime. - (multiple-value-bind (cols rows) - (ignore-errors - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (unwind-protect - (let ((ok (sb-unix:unix-ioctl - (sb-sys:fd-stream-fd (backend-output-stream b)) - 21523 (sb-alien:alien-sap winsize)))) - (when ok - (values (sb-alien:deref winsize 1) - (sb-alien:deref winsize 0)))) - (sb-alien:free-alien winsize)))) - (when (and cols rows (> cols 0) (> rows 0)) - (values cols rows))) + ;; Try ioctl on fd 0 (stdin), then stdout, then /dev/tty, then 80x24. + ;; Use multiple-value-bind/values to preserve both cols and rows + ;; (or discards secondary values). + (multiple-value-bind (cols rows) (ignore-errors - (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) - (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) - (unwind-protect - (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) - (let ((ok (sb-unix:unix-ioctl tty-fd 21523 - (sb-alien:alien-sap winsize)))) - (when ok - (values (sb-alien:deref winsize 1) - (sb-alien:deref winsize 0)))) - (sb-alien:free-alien winsize)) - (sb-unix:unix-close tty-fd))))) - (values 80 24))) + (let ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (let ((ok (sb-unix:unix-ioctl 0 21523 + (sb-alien:alien-sap winsize)))) + (when ok + (let ((c (sb-alien:deref winsize 1)) + (r (sb-alien:deref winsize 0))) + (when (and c r (> c 0) (> r 0)) + (values c r))))) + (sb-alien:free-alien winsize)))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + ;; ioctl on stdout fd + (multiple-value-bind (cols rows) + (ignore-errors + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (let ((ok (sb-unix:unix-ioctl + (sb-sys:fd-stream-fd (backend-output-stream b)) + 21523 (sb-alien:alien-sap winsize)))) + (when ok + (values (sb-alien:deref winsize 1) + (sb-alien:deref winsize 0)))) + (sb-alien:free-alien winsize)))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + ;; Direct ioctl on /dev/tty + (multiple-value-bind (cols rows) + (ignore-errors + (let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0))) + (when (and tty-fd (numberp tty-fd) (> tty-fd 0)) + (unwind-protect + (let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (let ((ok (sb-unix:unix-ioctl tty-fd 21523 + (sb-alien:alien-sap winsize)))) + (when ok + (values (sb-alien:deref winsize 1) + (sb-alien:deref winsize 0)))) + (sb-alien:free-alien winsize)) + (sb-unix:unix-close tty-fd))))) + (if (and cols rows (> cols 0) (> rows 0)) + (values cols rows) + (values 80 24)))))))) (defmethod backend-write ((b simple-backend) string) (let ((stream (backend-output-stream b)))