Commit Graph

145 Commits

Author SHA1 Message Date
733ba7c1b8 fix: remove :input :interactive from stty size subprocess
:input :interactive causes uiop to block on /dev/tty in the parent.
stty size queries terminal via ioctl, not stdin — no input
redirection needed.
2026-05-14 14:30:17 -04:00
ce7af16b13 fix: restore ioctl block in simple.lisp (was lost in edit) 2026-05-14 14:25:23 -04:00
31f864471c fix: use :input :interactive for stty size subprocess
SBCL's stdin during --load is the load file, NOT the terminal.
When uiop:run-program creates a subprocess, it inherits this
stdin, so 'stty size' reads from the load file and fails.
:input :interactive opens /dev/tty for the child's stdin,
matching the behavior of 'stty size' from an interactive shell.
2026-05-14 14:24:55 -04:00
4b1ff3ed0f fix: move stty size to first priority in backend-size
stty size via subprocess is the most reliable method — it
returns the correct 59x83 from the user's terminal. Move it
before ioctl to ensure it's tried first.
2026-05-14 14:22:27 -04:00
fe301dc25b fix: run stty size via 'sh -c' with /dev/tty redirection
uiop:run-program may redirect the child's stdin, preventing stty
from querying the terminal. 'stty size < /dev/tty' explicitly
reads from the controlling terminal regardless of stdin setup.
2026-05-14 14:20:22 -04:00
4df3048a13 fix: add stty size subprocess fallback in both backends
stty size via uiop:run-program is the most reliable method —
it works from the shell on every Unix system and bypasses
alien/ioctl quirks. Placed between stdout ioctl and /dev/tty
ioctl in the fallback chain.
2026-05-14 14:15:30 -04:00
41e2b867be fix: use O_RDONLY (0) for /dev/tty open, guard invalid fd 2026-05-14 14:10:58 -04:00
a227a52c48 fix: use raw O_RDWR=2 constant (sb-unix:o-rdwr doesn't exist) 2026-05-14 13:57:03 -04:00
37f83db35e fix: replace stty/tput fallback with direct ioctl on /dev/tty
uiop:run-program can inherit different terminal state than the
interactive shell. Opening /dev/tty directly and calling ioctl
on that fd is equivalent to what the shell's stty does, and
works regardless of SBCL's fd inheritance quirks.
2026-05-14 13:56:16 -04:00
9b472e281f 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.
2026-05-14 13:46:13 -04:00
4fa7e98b80 fix: set both dimensions from stty, add tput fallback, export shell vars
- stty size returns 'rows cols'. Old code set only one dimension
  when both env vars were missing; new code sets both.
- Added tput cols/lines as final env-var fallback for systems
  where COLUMNS/LINES are not exported and stty is unavailable.
- Added 'export COLUMNS LINES' to the passepartout script so
  SBCL can read them from the environment.
2026-05-14 13:36:54 -04:00
03ffec75c8 fix: add (require :sb-posix) before SIGWINCH handler
sb-posix is a built-in SBCL contrib, available via require.
Without it, sb-posix:sigwinch causes a reader error and the
eval-when for the SIGWINCH handler never executes, making
*terminal-resized-p* always nil and resize detection broken.
2026-05-14 13:30:38 -04:00
5e9a974981 fix: fill missing env dimension from stty size
When only COLUMNS or only LINES is set, run 'stty size' to get the
other dimension. This handles tmux/screen where only one env var
is exported.
2026-05-14 13:14:22 -04:00
4b9482c09a fix: add env var fallback to backend-size in both backends
ioctl on stdout's fd can return 80x24 even when the terminal is
larger. Add COLUMNS/LINES from the shell as a fallback. Also adds
ioctl-based sizing to simple-backend (was hardcoded 80x24).
2026-05-14 13:12:49 -04:00
83a6e87720 fix: simplify backend-size with direct when guard on values
The try-ioctl function returns (values cols rows) only when both
are valid integers > 0. or propagates complete pairs. This avoids
the nil-in-h crash from partial ioctl results.
2026-05-14 13:11:16 -04:00
db07f8c3a7 fix: guard ioctl results with when to avoid partial values
ignore-errors + ioctl can return (values 80 nil) when the fd exists
but isn't a terminal. or propagates partial values, causing nil in
w or h. Wrap with multiple-value-bind + when to filter.
2026-05-14 13:07:55 -04:00
4a86ae3274 fix: ioctl on stdin fd (0) first, then stdout fd, then env vars
The user's terminal reports 186x60 via stty (which uses stdin fd)
and via COLUMNS/LINES, but ioctl on stdout's fd returns 80x24.
Priority: fd 0 → backend output fd → env vars → 80x24 fallback.
2026-05-14 13:07:05 -04:00
7813e27907 fix: revert to simple ioctl-first with env var fallback
The previous logic (check ioctl result, prefer env when 80x24)
added complexity and crashes. Simple or with env vars after ioctl
is safe: ioctl returns 80x24 on stdout fd mismatch, env vars
(COLUMNS/LINES from shell) provide the correct initial size.
2026-05-14 13:05:53 -04:00
abe4edaffc fix: fallback to stty size when LINES env var is missing
Some environments (tmux) export COLUMNS but not LINES. Use
'stty size' as a fallback for the missing dimension.
2026-05-14 13:04:57 -04:00
1ac6ca02ee fix: handle nil env vars in backend-size
parse-integer errors on nil input. Guard with when before parsing.
2026-05-14 13:03:42 -04:00
0e0151664e fix: prefer env vars over ioctl when ioctl returns 80x24
ioctl on stdout's fd can return the default 80x24 even when the
terminal is much larger (fd mismatch). The new logic:

1. Try ioctl — if it returns >80x24, trust it (correct at runtime).
2. If ioctl returned 80x24 (suspicious default), try COLUMNS/LINES
   from the shell environment instead.
3. If both fail, return whatever ioctl gave us (80x24).

This fixes initial sizing on terminals where ioctl disagrees with
the real TTY size, without breaking runtime SIGWINCH resize
(which always re-queries ioctl, and that is correct after resize).
2026-05-14 13:02:09 -04:00
5c8a253171 fix: add / env var fallback in backend-size
ioctl on stdout's fd can disagree with the real terminal size when
the process is started with stdout redirected or in some terminal
multiplexer configurations. / are set by every POSIX
shell at process start and reflect the actual terminal dimensions.

Priority: ioctl → env vars → 80x24 fallback.
This covers both initial sizing and dynamic SIGWINCH-driven resize.
2026-05-14 12:57:01 -04:00
7cdb556531 fix: remove %query-terminal-size completely
The CSI 18t query leaks into the threaded keyboard reader because
the response arrives on stdin after the reader thread starts. The
response bytes get queued as key events and inserted as text into
the TUI input buffer. Removing the query entirely — ioctl is
sufficient for terminal size detection on all modern terminals.
2026-05-14 11:22:12 -04:00
920545dafb fix: wrap CSI terminal query in with-timeout 0.3s
The blocking read-char in %query-terminal-size could hang if the
terminal doesn't respond to CSI 18 t. Wrapped in
sb-ext:with-timeout 0.3 to abort if no response.
2026-05-14 10:17:59 -04:00
5a3b882f93 fix: add blocking-read-based CSI 18t terminal size query fallback
%query-terminal-size uses blocking read-char on an fd 0 stream
to read the terminal's response to \033[18t. This works even when
unix-simple-poll on fd 0 returns NIL (unlike read-char-no-hang).
Added as fallback in both modern and simple backends.
2026-05-14 10:14:40 -04:00
21d9890374 fix: revert read-raw-byte poll check (unix-read must block)
The earlier fix that checked unix-simple-poll before unix-read
broke input: poll on fd 0 always returns NIL in this SBCL, so
read-raw-byte always returned nil. Reverted to original: call
unix-simple-poll (for timeout) then unix-read unconditionally.
unix-read blocks until data arrives, which is correct for a TUI.
2026-05-14 10:12:24 -04:00
b80bd77d84 fix: remove CSI 18t terminal query (read-char-no-hang on fd 0 never returns)
The %query-terminal-size function sent \033[18t and tried to read
the response via read-char-no-hang on fd 0, which always returns nil
in this SBCL environment. The response leaked into user input,
displaying garbled CSI sequences. Rely on ioctl only.
2026-05-14 09:32:25 -04:00
14b41831c3 fix: disable kitty keyboard, fix CSI parser crashes
- Disabled \033[?u kitty keyboard protocol in modern-backend
  (converts all keys to escape sequences, breaking Ctrl+letter dispatch)
- Fixed parse-csi-sequence: use multiple-value-bind instead of let*
  with destructuring-bind (lost secondary return value from read-param)
- Fixed parse-csi-params format string: pass char-code of terminator
  as distinct argument for ~d, keeping the character for ~C
- Added %query-terminal-size in classes.lisp: ANSI CSI 18t fallback
  for terminal size detection when ioctl fails or returns zero
2026-05-14 09:31:09 -04:00
e8b37f6268 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
2026-05-14 08:55:56 -04:00
1637c3352c fix: read-raw-byte checks poll result before unix-read
The original code called unix-simple-poll then unconditionally
called unix-read, ignoring the poll result. When poll returned
nil (no data), unix-read would block indefinitely. Fixed by
checking poll result: only read if poll says data is ready.
2026-05-14 08:53:16 -04:00
07cea571ef fix: add backend-clear method for raw 2D arrays
Same pattern as the draw-text array fix. Application code may call
backend-clear with a framebuffer array instead of a backend instance.
The array method clears all cells to default blank state.
2026-05-13 16:29:50 -04:00
3bc6df6fd0 fix: read-raw-byte alien type mismatch and timeout ms conversion
- Replace make-alien unsigned-char buffer with make-array + vector-sap
  to avoid SBCL alien type mismatch between signed-char and unsigned-char
- Convert timeout seconds to fixnum milliseconds for unix-simple-poll
  (was passing float 0.1, broke on fixnum-typed sb-unix:to-msec)
- Both fixes make read-raw-byte work on SBCL 2.5.2.debian
2026-05-13 16:15:09 -04:00
22886c1794 fix: add draw-text method for raw 2D arrays
Application code (passepartout TUI) calls draw-text with a framebuffer
(2D array) as the first argument, but draw-text only had methods for
framebuffer-backend CLOS instances. Added a method on array that sets
cells directly on the framebuffer array, matching make-framebuffer's
return type.
2026-05-13 16:06:05 -04:00
66e86734cb literate: add with-terminal, suspend-backend, resume-backend to org source
with-terminal macro was only in tangled .lisp (not .org). suspend-backend
and resume-backend generics + simple-backend methods + tests were also
in hand-edited .lisp only. All three added to org/backend-protocol.org
with proper prose, following the literate programming discipline.

Also added suspend/resume assertions to simple-backend-lifecycle test suite.
2026-05-13 13:14:24 -04:00
c30917056c Merge pull request 'v1.1.0: SGR Mouse Event Parsing' (#9) from feature/v0.11.0-slots into main
Reviewed-on: http://10.10.10.201:3001/amr/cl-tty/pulls/9
2026-05-12 18:43:20 -04:00
Hermes Agent
d4aba6ef06 docs: add v1.1.0 SGR mouse parsing to ROADMAP.org 2026-05-12 22:22:06 +00:00
Hermes Agent
b3b191529a feat: SGR mouse event parsing in read-event
- Add %read-digits to read multi-digit parameters from raw terminal bytes
- Add %parse-sgr-mouse to decode ESC[<Cb;Cx;CyM/m SGR mouse sequences
  into mouse-event structs with :press/:release type and :left/:middle/
  :right/:scroll-up/:scroll-down/:drag button classification
- Modified parse-csi-sequence to detect the < marker (0x3C) and
  delegate to %parse-sgr-mouse instead of treating it as key input
- Coordinates converted from 1-based (terminal protocol) to 0-based
  (framebuffer convention)
- All 12 test suites pass at 100% (461 checks, no regressions)
- Org source (text-input.org) updated as the source of truth
v1.1.0
2026-05-12 22:14:03 +00:00
07c29290d4 Merge pull request 'v1.0.0 — Stable release + TUI support' (#8) from feature/v0.11.0-slots into main
Reviewed-on: http://10.10.10.201:3001/amr/cl-tty/pulls/8
2026-05-12 16:34:48 -04:00
Hermes Agent
38ee561625 v1.0.0: TUI support — resize events, with-terminal macro v1.0.0 2026-05-12 20:32:37 +00:00
Hermes Agent
84e8482fec v1.0.0: TUI support — resize events, with-terminal macro
- read-event now checks *terminal-resized-p* and returns :resize on SIGWINCH
- Added with-terminal convenience macro (detect → init → body → shutdown)
- Exported *terminal-resized-p* from cl-tty.input package
- Exported with-terminal from cl-tty.backend package
- Updated text-input.org with resize event integration and refactored tests
- Tests: 461 checks, 100% pass (93 input suite, +2 new test cases)
2026-05-12 20:28:55 +00:00
Hermes Agent
3cbcfd2d75 v1.0.0 release
Bug fixes:
- Fix OSC8 format strings (backslash escape layering) in modern-backend.org
  - Test format string had single backslash instead of double, causing
    unclosed CL string that cascaded through 3 subsequent test forms
  - Implementation format string had leading escaped quote (not a string
    opener) and triple-backslash ending (also not a string terminator)
- Fix missing closing parens in border-char-rounded and border-char-double tests
- Fix ASDF input-tests pathname (file lives in tests/, not src/components/)

New features:
- Implement suspend-backend / resume-backend protocol methods
  - modern-backend: exit/enter alt screen, re-enable mouse/kitty/bracketed-paste
  - simple-backend: no-ops (no terminal state to preserve)

Infrastructure:
- Update test suite to cover suspend/resume (backend + modern-backend suites)
- 454 checks, 100% pass across 14 test suites
2026-05-12 20:00:27 +00:00
Hermes Agent
9c879e7a97 fix: validate slot mode on first defslot call
Add assert to reject invalid mode keywords on first registration
instead of silently storing them and only crashing later in
slot-render's ecase. Valid modes: :stack, :replace, :single-winner.
2026-05-12 19:33:18 +00:00
Hermes Agent
352f27e260 fix: osc8-link doubled backslashes in format string
The osc8-link implementation and its test both had doubled
backslashes (\\ -> \\) in their format strings, producing two
literal backslashes at runtime instead of the single backslash
needed for the OSC 8 string terminator (ST = ESC \).

Fix: change \\ to \\ in both the implementation and test format
strings. The tangled .lisp files now have correct escaped
backslashes (\) producing one backslash in the runtime string.

Additionally clean up a patch artifact that left a stray backslash
before the opening quote.
2026-05-12 19:26:00 +00:00
Hermes Agent
6cd045ff59 implement: slot modes (:stack, :replace, :single-winner)
Add :mode parameter to defslot with three behaviors:
- :stack (default) — accumulate all registrations, render in order
- :replace — each registration replaces previous entries
- :single-winner — first registration wins, rest ignored

Mode is set on first defslot call and frozen for subsequent calls
to prevent conflicting mode specifications from different plugins.

Store slot data as plist (:mode <keyword> :entries <list>) instead
of bare entries list.

Add 5 new tests covering mode-specific behavior. All 9 slot tests
pass. All 13 suites pass at 100%.
2026-05-12 19:17:24 +00:00
Hermes Agent
a9670a5cd7 literate: add org sources for orphan test files, update README
- Create org/integration-tests.org (15 blocks, per-test prose)
- Add Markdown tests section to org/markdown-renderer.org (11 test blocks)
- Delete deprecated src/components/input-tests.lisp stub
- Update README.org: tree diagram, literate programming section,
  development commands, remove stale test counts

All 13 test suites pass at 100%. Zero .lisp files without org origin.
2026-05-12 19:01:22 +00:00
Hermes Agent
29f99a576d literate: restructure all 19 org files with per-function blocks and prose
Every function, defclass, defstruct, defgeneric, defmethod, defmacro,
defvar, and defparameter in every org file now has its own #+BEGIN_SRC
block with literate prose above it explaining the design reasoning.

Block counts before → after:
  package.org:           1 → 7
  container-package.org: 1 → 1 (prose expanded)
  dirty.org:             4 → 6
  render.org:           10 → 25
  theme.org:             6 → 19
  box-renderable.org:    9 → 29
  scrollbox.org:         8 → 26
  tabbar.org:            5 → 10
  backend-protocol.org:  8 → 66
  modern-backend.org:   17 → 53
  detection.org:         4 → 6
  layout-engine.org:     9 → 36
  framebuffer.org:       8 → 37
  markdown-renderer.org:13 → 38
  dialog.org:           17 → 23 (merged dual structure)
  mouse.org:             4 → 25
  select.org:           12 → 30
  slot.org:              4 → 12
  text-input.org:       11 → 53

Total: ~153 blocks → ~502 blocks

Bugs fixed during restructuring:
- render.org: stray π character typo (backenπd → backend)
- modern-backend.org: sgr-attr missing closing paren + #+END_SRC
- detection.org: invalid #\Esc character reference
- select.org: extra closing paren in select-visible-options

All 13 test suites pass at 100%.
2026-05-12 18:55:07 +00:00
Hermes Agent
927f786716 remove: old scrollbox-tabbar.org (all prose distributed to per-module orgs)
The combined org file had no unique content — all prose and code were
already in scrollbox.org, tabbar.org, and container-package.org. The
old file's code blocks had the pre-bugfix render/draw-scrollbars
versions and all had :tangle no.

Also update README.org and ARCHITECTURE.org references from
scrollbox-tabbar.org to the individual org files.
2026-05-12 18:08:02 +00:00
Hermes Agent
668966380e prose: split scrollbox-tabbar.org prose into per-module org files
Distribute the literate prose from the old combined scrollbox-tabbar.org
into three individual module org files:

- scrollbox.org: ScrollBox class, render, scrollbars, bug fixes,
  plus the combined test suite (tangles scrollbox-tabbar-tests.lisp)
- tabbar.org: TabBar class, navigation, keyboard handler, render
- container-package.org: Package definition and exports

The old scrollbox-tabbar.org is retained as a documentation archive
with all code blocks set to :tangle no and a redirecting note.

Fixes the draw-scrollbars code block to use the post-bugfix version
(with layout-node origin offset ox/oy), matching the working code.
All 13 test suites pass at 100%.
2026-05-12 18:06:07 +00:00
Hermes Agent
a061d60898 split: scrollbox-tabbar.org into scrollbox.org, tabbar.org, container-package.org
- Create org/scrollbox.org (tangles scrollbox.lisp)
- Create org/tabbar.org (tangles tabbar.lisp)
- Create org/container-package.org (tangles container-package.lisp)
- Disable :tangle in old scrollbox-tabbar.org (kept for prose docs)
- Fix missing paren in render method (was depth=1 at EOF)
- All 483 tests pass, 14 suites, 100%
2026-05-12 18:00:06 +00:00
Hermes Agent
d5caaf296d fix: restore original text-input.lisp in org to fix handle-text-input
The tangled handle-text-input used (key-event-text event) for character
insertion, but the test suite creates key events with :code not :text.
Restored the original handle-text-input which uses
(code-char (key-event-code event)) — matching the test expectations.
2026-05-12 17:52:43 +00:00