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%.
This commit is contained in:
@@ -95,6 +95,18 @@ class. Application code never calls terminal escape sequences directly.
|
||||
|
||||
* Tests
|
||||
|
||||
The test suite is organized around the backend protocol contract.
|
||||
Each rendering primitive and lifecycle operation has a dedicated
|
||||
test case. Tests use a capturing backend (a simple-backend wired to
|
||||
a string output stream) so assertions check actual output strings
|
||||
rather than terminal behavior.
|
||||
|
||||
** Test Package and Suite
|
||||
|
||||
FiveAM requires a test package with :use of :fiveam and the system
|
||||
under test. The suite name ~backend-suite~ is referenced by the
|
||||
multi-suite runner in ~run-all-tests.lisp~.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(defpackage :cl-tty-backend-test
|
||||
(:use :cl :fiveam :cl-tty.backend)
|
||||
@@ -103,23 +115,45 @@ class. Application code never calls terminal escape sequences directly.
|
||||
|
||||
(def-suite backend-suite :description "Backend protocol tests")
|
||||
(in-suite backend-suite)
|
||||
#+END_SRC
|
||||
|
||||
;; ── Helpers ─────────────────────────────────────────────────────
|
||||
** Capturing Backend Helper
|
||||
|
||||
Tests need to inspect what the backend actually writes. This helper
|
||||
creates a simple-backend pointed at a string output stream and
|
||||
returns both the backend and the stream. The test can then call
|
||||
~get-output-stream-string~ after the operation.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(defun make-capturing-backend ()
|
||||
"Create a simple-backend that writes to a string stream."
|
||||
(let* ((s (make-string-output-stream))
|
||||
(b (make-simple-backend :output-stream s)))
|
||||
(values b s)))
|
||||
#+END_SRC
|
||||
|
||||
;; ── Simple Backend ──────────────────────────────────────────────
|
||||
** Test Runner Entry Point
|
||||
|
||||
The ~run-tests~ function is an alternative entry point for
|
||||
interactive use or for downstream scripts that want to run only the
|
||||
backend suite. It prints results with FiveAM's explainer.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(defun run-tests ()
|
||||
"Run all backend tests."
|
||||
(let ((result (run 'backend-suite)))
|
||||
(fiveam:explain! result)
|
||||
(uiop:quit 0)))
|
||||
#+END_SRC
|
||||
|
||||
** Simple Backend Lifecycle
|
||||
|
||||
Verifies that a simple-backend can be constructed, initialized, and
|
||||
shut down without errors. Also confirms that the capability query
|
||||
returns nil for truecolor — the defining characteristic of the
|
||||
simple backend.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test simple-backend-lifecycle
|
||||
"simple-backend can be created and shut down"
|
||||
(let ((b (make-simple-backend)))
|
||||
@@ -127,7 +161,16 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(initialize-backend b)
|
||||
(is-false (capable-p b :truecolor) "simple backend has no truecolor")
|
||||
(shutdown-backend b)))
|
||||
#+END_SRC
|
||||
|
||||
** Simple Backend Draw Text
|
||||
|
||||
The simple backend ignores style attributes (bold, italic, color)
|
||||
and position. It merely appends the text string to the output stream.
|
||||
This test confirms that passing style keywords does not change the
|
||||
output — the captured stream should contain exactly the string "hello".
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test simple-backend-draw-text
|
||||
"simple-backend renders text at position, ignoring style"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -136,7 +179,16 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(shutdown-backend b)
|
||||
(is (string= (get-output-stream-string s) "hello")
|
||||
"draw-text should output the string ignoring style")))
|
||||
#+END_SRC
|
||||
|
||||
** Simple Backend Draw Border
|
||||
|
||||
Border rendering on the simple backend uses ASCII characters:
|
||||
~+~ for corners, ~-~ for horizontal edges, ~|~ for vertical edges.
|
||||
This test checks that the top edge contains "+---+" and a middle
|
||||
row shows "| |" with pipe-separated empty space.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test simple-backend-draw-border
|
||||
"simple-backend draws ASCII border with +-| characters"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -144,9 +196,18 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(draw-border b 0 0 5 3 :style :single)
|
||||
(shutdown-backend b)
|
||||
(let ((out (get-output-stream-string s)))
|
||||
(is (search "+---+" out) "top edge should have +---+")
|
||||
(is (search "+---+" out) "top edge should have +---+\"")
|
||||
(is (search "| |" out) "middle row should have pipe sides"))))
|
||||
#+END_SRC
|
||||
|
||||
** Simple Backend Draw Rounded Border
|
||||
|
||||
The simple backend does not support rounded corners — every style
|
||||
falls back to the same ASCII characters. This test verifies that
|
||||
requesting ~:rounded~ produces the same output as ~:single~,
|
||||
confirming the graceful fallback.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test simple-backend-draw-rounded
|
||||
"simple-backend falls back to straight edges for rounded style"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -154,9 +215,17 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(draw-border b 0 0 5 3 :style :rounded)
|
||||
(shutdown-backend b)
|
||||
(let ((out (get-output-stream-string s)))
|
||||
;; Rounded falls back to ASCII — identical output to single
|
||||
;; Rounded falls back to ASCII -- identical output to single
|
||||
(is (search "+---+" out) "rounded style produces same dashes as single"))))
|
||||
#+END_SRC
|
||||
|
||||
** Simple Backend Draw Link
|
||||
|
||||
Hyperlinks are meaningless on a simple terminal output. The simple
|
||||
backend's ~draw-link~ should output only the visible text and
|
||||
completely ignore the URL parameter.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test simple-backend-draw-link
|
||||
"simple-backend renders link as plain text"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -165,7 +234,15 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(shutdown-backend b)
|
||||
(is (string= (get-output-stream-string s) "click me")
|
||||
"simple-backend ignores URL, outputs text only")))
|
||||
#+END_SRC
|
||||
|
||||
** Simple Backend Draw Ellipsis
|
||||
|
||||
Truncation markers are rendered as three literal dots on the simple
|
||||
backend. This test checks that ~draw-ellipsis~ outputs exactly "..."
|
||||
at the specified position.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test simple-backend-draw-ellipsis
|
||||
"simple-backend renders ... for ellipsis"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -174,9 +251,16 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(shutdown-backend b)
|
||||
(is (string= (get-output-stream-string s) "...")
|
||||
"ellipsis should output 3 dots")))
|
||||
#+END_SRC
|
||||
|
||||
;; ── Backend Capabilities ───────────────────────────────────────
|
||||
** Capability Query: Known Features
|
||||
|
||||
All known terminal features should report ~nil~ on the simple
|
||||
backend. This comprehensive check iterates every feature keyword
|
||||
to ensure the simple backend makes no false claims about its
|
||||
capabilities.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test capable-p-known-features
|
||||
"capable-p returns nil for all features on simple-backend"
|
||||
(let ((b (make-simple-backend)))
|
||||
@@ -186,9 +270,16 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(is-false (capable-p b f)
|
||||
(format nil "~s should not be supported on simple-backend" f)))
|
||||
(shutdown-backend b)))
|
||||
#+END_SRC
|
||||
|
||||
;; ── Backend Size ───────────────────────────────────────────────
|
||||
** Backend Size Returns Integers
|
||||
|
||||
The ~backend-size~ function must return two integer values
|
||||
representing columns and lines. This test verifies the return types
|
||||
and a minimum size threshold (10 columns, 3 lines) for any
|
||||
terminal-like environment.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test backend-size-returns-integers
|
||||
"backend-size returns two integer values"
|
||||
(let ((b (make-simple-backend)))
|
||||
@@ -199,9 +290,17 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(is (>= cols 10))
|
||||
(is (>= lines 3)))
|
||||
(shutdown-backend b)))
|
||||
#+END_SRC
|
||||
|
||||
;; ── Backend Protocol: Defaults and No-ops ──────────────────────
|
||||
** Default Methods Are No-Ops
|
||||
|
||||
All cursor operations and sync operations on the default backend
|
||||
should return ~nil~ (or ~(values)~) without signaling errors. This
|
||||
test calls ~cursor-hide~, ~cursor-show~, ~cursor-style~,
|
||||
~begin-sync~, and ~end-sync~ and confirms none of them produce
|
||||
multiple return values.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test default-methods-are-no-ops
|
||||
"Default backend methods don't error"
|
||||
(let ((b (make-simple-backend)))
|
||||
@@ -212,7 +311,16 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(is (null (multiple-value-list (begin-sync b))))
|
||||
(is (null (multiple-value-list (end-sync b))))
|
||||
(shutdown-backend b)))
|
||||
#+END_SRC
|
||||
|
||||
** Sync Is No-Op on Simple
|
||||
|
||||
Synchronized updates (DECICM) have no meaning on a simple terminal
|
||||
output. This test verifies that wrapping a draw-text call between
|
||||
~begin-sync~ and ~end-sync~ produces exactly the same output as
|
||||
draw-text alone — no extra escape sequences are emitted.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test sync-is-noop-on-simple
|
||||
"begin-sync and end-sync produce no output on simple-backend"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -223,9 +331,16 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(shutdown-backend b)
|
||||
(is (string= (get-output-stream-string s) "in sync")
|
||||
"no sync escape sequences should appear")))
|
||||
#+END_SRC
|
||||
|
||||
;; ── Draw-rect ──────────────────────────────────────────────────
|
||||
** Draw Rect Is No-Op on Simple
|
||||
|
||||
Background fill operations require escape sequences to change cell
|
||||
colors. Since the simple backend emits no escape sequences,
|
||||
~draw-rect~ should produce zero output regardless of the fill
|
||||
color requested.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test draw-rect-fills-area-correctly
|
||||
"draw-rect with background writes nothing to output (simple-backend no-op)"
|
||||
(multiple-value-bind (b s) (make-capturing-backend)
|
||||
@@ -234,14 +349,29 @@ class. Application code never calls terminal escape sequences directly.
|
||||
(shutdown-backend b)
|
||||
(is (string= (get-output-stream-string s) "")
|
||||
"draw-rect is a no-op on simple-backend")))
|
||||
#+END_SRC
|
||||
|
||||
;; ── Detection ──────────────────────────────────────────────────
|
||||
** Backend Detection Returns Instance
|
||||
|
||||
The ~detect-backend~ function must return a backend instance
|
||||
suitable for the current environment. This test verifies that the
|
||||
returned value is of type ~backend~ (satisfying the protocol).
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test detection-returns-backend-instance
|
||||
"detect-backend returns a valid backend instance"
|
||||
(let ((be (cl-tty.backend:detect-backend)))
|
||||
(is (typep be 'cl-tty.backend:backend))))
|
||||
#+END_SRC
|
||||
|
||||
** Backend Detection Caches Result
|
||||
|
||||
~detect-backend~ caches its result in ~*detected-backend*~ so that
|
||||
subsequent calls are cheap. This test clears the cache, calls
|
||||
detect-backend, and verifies that the special variable is no longer
|
||||
nil.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/tests.lisp
|
||||
(test detection-caches-result
|
||||
"detect-backend caches the result in *detected-backend*"
|
||||
(let ((*detected-backend* nil))
|
||||
@@ -251,10 +381,17 @@ class. Application code never calls terminal escape sequences directly.
|
||||
|
||||
* Implementation
|
||||
|
||||
This section defines the base backend protocol and the simple
|
||||
backend implementation. Each function, generic function, and method
|
||||
is documented individually with its design rationale.
|
||||
|
||||
** Package
|
||||
|
||||
The ~cl-tty.backend~ package exports all the generic function names
|
||||
and backend class names. It uses only ~:cl~ — no external dependencies.
|
||||
The package also exports internal symbols (~sgr-fg~, ~hex-to-24bit~,
|
||||
etc.) for testing. These let the test suite verify escape sequence
|
||||
construction without actually rendering to a terminal.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/package.lisp
|
||||
(defpackage :cl-tty.backend
|
||||
@@ -292,10 +429,6 @@ and backend class names. It uses only ~:cl~ — no external dependencies.
|
||||
(in-package :cl-tty.backend)
|
||||
#+END_SRC
|
||||
|
||||
The package also exports internal symbols (~sgr-fg~, ~hex-to-24bit~,
|
||||
etc.) for testing. These let the test suite verify escape sequence
|
||||
construction without actually rendering to a terminal.
|
||||
|
||||
** Backend Base Class
|
||||
|
||||
The ~backend~ class itself is empty — it's a base for method dispatch.
|
||||
@@ -303,84 +436,248 @@ Every generic function on ~backend~ has a default method so that new
|
||||
backend implementations only need to override the functions they
|
||||
actually support.
|
||||
|
||||
*** Backend Class Definition
|
||||
|
||||
An empty base class. There are no slots because backends manage
|
||||
their own state (e.g., output streams) directly.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(in-package :cl-tty.backend)
|
||||
|
||||
(defclass backend () ())
|
||||
#+END_SRC
|
||||
|
||||
*** Initialize Backend
|
||||
|
||||
Sets up terminal raw mode and enables features. The default method
|
||||
returns the backend instance unchanged — subclasses that need setup
|
||||
override this.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric initialize-backend (backend)
|
||||
(:method ((b backend)) b))
|
||||
#+END_SRC
|
||||
|
||||
*** Shutdown Backend
|
||||
|
||||
Restores terminal to cooked mode, resets colors, shows cursor.
|
||||
Must be called on exit. The default method is a no-op returning
|
||||
multiple values; subclasses with terminal state override this.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric shutdown-backend (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Backend Size
|
||||
|
||||
Returns terminal dimensions as two values: columns and lines.
|
||||
The default of 80x24 is a safe fallback that works everywhere.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric backend-size (backend)
|
||||
(:method ((b backend))
|
||||
(values 80 24)))
|
||||
#+END_SRC
|
||||
|
||||
*** Backend Write
|
||||
|
||||
Writes a raw string to the terminal output. Has no default method
|
||||
because every backend must provide its own output mechanism — there
|
||||
is no reasonable universal behavior.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric backend-write (backend string))
|
||||
#+END_SRC
|
||||
|
||||
*** Backend Clear
|
||||
|
||||
Clears the entire screen and resets the cursor to (0,0). The default
|
||||
method sends the ANSI escape sequence ~ESC[2J~ (clear entire screen)
|
||||
followed by ~ESC[H~ (cursor home).
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric backend-clear (backend)
|
||||
(:method ((b backend))
|
||||
(backend-write b (format nil "~C[2J~C[H" #\Esc #\Esc))))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Text
|
||||
|
||||
Renders text at position (x, y) with foreground and background
|
||||
colors and style attributes. The ~&allow-other-keys~ is important:
|
||||
it lets individual backend methods accept keyword arguments they
|
||||
don't use without signaling an error. The simple backend ignores
|
||||
styles; the modern backend processes them.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric draw-text (backend x y string fg bg &key
|
||||
bold italic underline reverse dim blink
|
||||
&allow-other-keys))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Border
|
||||
|
||||
Draws a border rectangle with optional title. Style is one of
|
||||
~:single~, ~:double~, or ~:rounded~. The default method has no
|
||||
implementation — each backend provides its own border rendering.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric draw-border (backend x y width height
|
||||
&key style fg bg title title-align))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Rectangle
|
||||
|
||||
Fills a rectangular area with a background color. On the simple
|
||||
backend this is a no-op; on the modern backend it writes space
|
||||
characters with the appropriate SGR background color.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric draw-rect (backend x y width height &key bg))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Link
|
||||
|
||||
Renders a clickable hyperlink using OSC 8 escape sequences. The
|
||||
default is a protocol declaration only — modern-backend implements
|
||||
the actual escape sequences, simple-backend falls back to plain text.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric draw-link (backend x y string url &key fg bg))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Ellipsis
|
||||
|
||||
Renders a "..." truncation marker at position (x, y). This is used
|
||||
when text exceeds the available width. Each backend positions the
|
||||
marker according to its own coordinate system.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric draw-ellipsis (backend x y width &key fg bg))
|
||||
#+END_SRC
|
||||
|
||||
*** Cursor Move
|
||||
|
||||
Moves the cursor to absolute position (x, y). The default method
|
||||
is a no-op — backends that support cursor positioning override this.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric cursor-move (backend x y)
|
||||
(:method ((b backend) x y) (declare (ignore x y)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Cursor Hide
|
||||
|
||||
Hides the terminal cursor. The default method is a no-op so that
|
||||
backends that lack cursor control still work safely.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric cursor-hide (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Cursor Show
|
||||
|
||||
Shows the terminal cursor after a hide. Always paired with
|
||||
~cursor-hide~. Default is a no-op.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric cursor-show (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Cursor Style
|
||||
|
||||
Sets the cursor shape and blink behavior. Shape is ~:block~,
|
||||
~:bar~, or ~:underline~. Default is a no-op for backends that
|
||||
don't support cursor styling.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric cursor-style (backend shape &key blink)
|
||||
(:method ((b backend) shape &key blink) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Begin Sync
|
||||
|
||||
Starts a synchronized update (DECICM). All subsequent output is
|
||||
buffered by the terminal until ~end-sync~. Default is a no-op.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric begin-sync (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** End Sync
|
||||
|
||||
Flushes the synchronized update buffer so the entire frame appears
|
||||
at once. Always paired with ~begin-sync~. Default is a no-op.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric end-sync (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Read Event
|
||||
|
||||
Reads the next input event from the terminal. Blocks until an event
|
||||
arrives or the timeout expires. Returns (values keyword event-data).
|
||||
The default method returns ~(values nil nil)~ — no events available.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric read-event (backend &key timeout)
|
||||
(:method ((b backend) &key timeout) (values nil nil)))
|
||||
#+END_SRC
|
||||
|
||||
*** Enable Mouse
|
||||
|
||||
Enables SGR mouse tracking so mouse click and motion events are
|
||||
reported as input. Default is a no-op on backends that don't
|
||||
support mouse input.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric enable-mouse (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Enable Bracketed Paste
|
||||
|
||||
Enables bracketed paste mode so the application can distinguish
|
||||
pasted text from typed input. Default is a no-op.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric enable-bracketed-paste (backend)
|
||||
(:method ((b backend)) (values)))
|
||||
#+END_SRC
|
||||
|
||||
*** Capable-P Feature Query
|
||||
|
||||
Queries whether the backend supports a named feature. Feature
|
||||
keywords include ~:truecolor~, ~:osc8~, ~:sync~, ~:mouse~,
|
||||
~:bracketed-paste~, ~:kitty-keyboard~, ~:sixel~, and
|
||||
~:cursor-style~. The default method returns ~nil~ for all features.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/classes.lisp
|
||||
(defgeneric capable-p (backend feature)
|
||||
(:method ((b backend) feature)
|
||||
(declare (ignore feature))
|
||||
nil))
|
||||
#+END_SRC
|
||||
|
||||
The ~&allow-other-keys~ on ~draw-text~ is important: it lets
|
||||
individual backend methods accept keyword arguments they don't use
|
||||
without signaling an error. The simple backend ignores styles; the
|
||||
modern backend processes them.
|
||||
|
||||
** Simple Backend
|
||||
|
||||
~simple-backend~ inherits from ~backend~ and implements every
|
||||
operation in pure ASCII. No escape sequences, no color, no modern
|
||||
features. Works in any terminal, pipe, or serial connection.
|
||||
|
||||
*** Simple Backend Class
|
||||
|
||||
The ~simple-backend~ class has a single slot: ~output-stream~.
|
||||
This defaults to ~*standard-output*~ but can be overridden via
|
||||
the ~:output-stream~ initarg — the key extensibility point. Tests
|
||||
use ~make-string-output-stream~ to capture output, while production
|
||||
uses ~*standard-output*~.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(in-package :cl-tty.backend)
|
||||
|
||||
@@ -388,44 +685,89 @@ features. Works in any terminal, pipe, or serial connection.
|
||||
((output-stream :initform *standard-output*
|
||||
:initarg :output-stream
|
||||
:accessor backend-output-stream)))
|
||||
#+END_SRC
|
||||
|
||||
*** Make Simple Backend
|
||||
|
||||
Constructor function that creates a ~simple-backend~ instance. Uses
|
||||
~make-instance~ with the provided output stream or falls back to
|
||||
~*standard-output*~. This function is exported rather than exposing
|
||||
~make-instance~ directly to provide a stable API surface.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defun make-simple-backend (&key output-stream)
|
||||
(make-instance 'simple-backend
|
||||
:output-stream (or output-stream *standard-output*)))
|
||||
#+END_SRC
|
||||
|
||||
The ~output-stream~ initarg is the key extensibility point: tests use
|
||||
~make-string-output-stream~ to capture output, while production uses
|
||||
~*standard-output*~.
|
||||
*** Initialize Backend (Simple)
|
||||
|
||||
Simple backend initialization is a no-op — there is no terminal
|
||||
state to configure. Returns the backend instance to satisfy the
|
||||
protocol contract.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod initialize-backend ((b simple-backend))
|
||||
b)
|
||||
#+END_SRC
|
||||
|
||||
*** Shutdown Backend (Simple)
|
||||
|
||||
Simple backend shutdown is a no-op — there is no terminal state to
|
||||
restore. Returns multiple values to satisfy the protocol contract.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod shutdown-backend ((b simple-backend))
|
||||
(values))
|
||||
#+END_SRC
|
||||
|
||||
*** Backend Size (Simple)
|
||||
|
||||
Returns hard-coded 80x24 dimensions. A real implementation would use
|
||||
ioctl or TIOCGWINSZ, but the simple backend avoids OS-specific calls
|
||||
for maximum portability.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod backend-size ((b simple-backend))
|
||||
;; Try ioctl, fall back to 80x24
|
||||
(values 80 24))
|
||||
#+END_SRC
|
||||
|
||||
*** Backend Write (Simple)
|
||||
|
||||
Writes a string to the backend's output stream, forces the stream to
|
||||
flush, and returns the length of the string. Uses ~finish-output~ to
|
||||
ensure the data is actually sent, which matters for pipe and network
|
||||
output.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod backend-write ((b simple-backend) string)
|
||||
(let ((stream (backend-output-stream b)))
|
||||
(write-string string stream)
|
||||
(finish-output stream)
|
||||
(length string)))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Text (Simple)
|
||||
|
||||
The simple backend's ~draw-text~ ignores position, color, and style
|
||||
completely. It appends only the string content to the output stream.
|
||||
This means simple backends are always a "scroll and dump" mode —
|
||||
no cursor positioning, no escape sequences.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod draw-text ((b simple-backend) x y string fg bg
|
||||
&key bold italic underline reverse dim blink)
|
||||
(declare (ignore x y fg bg bold italic underline reverse dim blink))
|
||||
(backend-write b string))
|
||||
#+END_SRC
|
||||
|
||||
~draw-text~ on simple-backend ignores position and style completely.
|
||||
It just appends the string to the output stream. This means simple
|
||||
backends are always a "scroll and dump" mode — no cursor positioning.
|
||||
*** Simple Border Character Helper
|
||||
|
||||
** Border drawing
|
||||
Returns the ASCII character for a given border position. All four
|
||||
corners use ~#\+~, horizontal edges use ~#\-~, and vertical edges
|
||||
use ~#\|~. No style distinction — single, double, and rounded are
|
||||
identical in ASCII output.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defun %simple-border-char (pos)
|
||||
@@ -438,8 +780,13 @@ POS is :top-left, :top-right, :bottom-left, :bottom-right,
|
||||
(:vertical #\|)))
|
||||
#+END_SRC
|
||||
|
||||
All four corners use ~#\+~, edges use ~#\-~ and ~#\|~. No style
|
||||
distinction — single, double, and rounded are identical in ASCII.
|
||||
*** Draw Border (Simple)
|
||||
|
||||
Draws a border using only newlines and spaces for positioning —
|
||||
no escape sequences. This makes it compatible with pipe output.
|
||||
The title rendering supports ~:left~ and ~:center~ alignment,
|
||||
placing the title inside the top border line with horizontal
|
||||
dashes filling the remaining space.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod draw-border ((b simple-backend) x y width height
|
||||
@@ -492,12 +839,10 @@ distinction — single, double, and rounded are identical in ASCII.
|
||||
(backend-write b (string br))))
|
||||
#+END_SRC
|
||||
|
||||
~draw-border~ on the simple backend uses newlines and spaces for
|
||||
positioning instead of ~cursor-move~ escape sequences. This makes it
|
||||
compatible with pipe output. The title rendering supports ~:left~ and
|
||||
~:center~ alignment, placing the title inside the top border line.
|
||||
*** Draw Rect (Simple)
|
||||
|
||||
** Remaining primitives
|
||||
Background fill is impossible without escape sequences. This method
|
||||
is a no-op — it discards all arguments and returns ~(values)~.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod draw-rect ((b simple-backend) x y width height
|
||||
@@ -505,12 +850,28 @@ compatible with pipe output. The title rendering supports ~:left~ and
|
||||
(declare (ignore x y width height bg))
|
||||
;; On simple backend, background fill is a no-op
|
||||
(values))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Link (Simple)
|
||||
|
||||
Hyperlinks fall back to plain text on the simple backend. The URL
|
||||
parameter is discarded entirely; the visible text is rendered via
|
||||
~draw-text~ with no styling.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod draw-link ((b simple-backend) x y string url
|
||||
&key fg bg)
|
||||
(declare (ignore url fg bg))
|
||||
(draw-text b x y string nil nil))
|
||||
#+END_SRC
|
||||
|
||||
*** Draw Ellipsis (Simple)
|
||||
|
||||
Renders "..." using the simple backend's positioning pattern:
|
||||
newlines to reach the target row, spaces to reach the target column,
|
||||
then the literal three dots. No escape sequences are used.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
|
||||
(defmethod draw-ellipsis ((b simple-backend) x y width
|
||||
&key fg bg)
|
||||
(declare (ignore width fg bg))
|
||||
@@ -519,7 +880,3 @@ compatible with pipe output. The title rendering supports ~:left~ and
|
||||
(backend-write b (make-string x :initial-element #\Space))
|
||||
(backend-write b "..."))
|
||||
#+END_SRC
|
||||
|
||||
~draw-rect~ is a no-op on simple-backend (no background fill possible
|
||||
without escape sequences). ~draw-link~ falls back to plain text.
|
||||
~draw-ellipsis~ positions and writes "...".
|
||||
|
||||
Reference in New Issue
Block a user