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:
Hermes Agent
2026-05-12 18:55:07 +00:00
parent 927f786716
commit 29f99a576d
42 changed files with 4730 additions and 1745 deletions

View File

@@ -9,7 +9,7 @@ escape sequences. It supports truecolor 24-bit color, OSC 8 hyperlinks,
DECICM synchronized updates, SGR mouse tracking, kitty keyboard protocol,
and Unicode box-drawing characters (single, double, rounded).
All rendering functions produce CSI/OSC escape sequences directly no
All rendering functions produce CSI/OSC escape sequences directly --- no
ncurses, no terminfo, no FFI. Color resolution handles named colors
(~:red~, ~:blue~, etc.), hex strings (~"#FFD700"~), and semantic theme
roles (~:accent~, ~:error~) via the ~*theme-colors*~ hash table.
@@ -18,166 +18,281 @@ roles (~:accent~, ~:error~) via the ~*theme-colors*~ hash table.
** Color and attribute helpers
- ~(hex-to-rgb hex)~ → (values r g b) parse "#RRGGBB" or "#RGB"
- ~(sgr-fg color)~ escape string foreground color escape
- ~(sgr-bg color)~ escape string background color escape
- ~(sgr-attr attr)~ escape string attribute escape (bold, italic, etc.)
- ~(hex-to-rgb hex)~ (r g b) --- parse "#RRGGBB" or "#RGB"
- ~(sgr-fg color)~ escape string --- foreground color escape
- ~(sgr-bg color)~ escape string --- background color escape
- ~(sgr-attr attr)~ escape string --- attribute escape (bold, italic, etc.)
** Cursor helpers
- ~(cursor-move-escape x y)~ escape string CSI cursor position
- ~(cursor-style-escape shape blink)~ escape string DECSTR cursor shape
- ~(cursor-move-escape x y)~ escape string --- CSI cursor position
- ~(cursor-style-escape shape blink)~ escape string --- DECSTR cursor shape
** Sync and link helpers
- ~(decicm-begin)~ escape string enable synchronized updates
- ~(decicm-end)~ escape string disable synchronized updates
- ~(osc8-link url text)~ escape string OSC 8 hyperlink wrapper
- ~(decicm-begin)~ escape string --- enable synchronized updates
- ~(decicm-end)~ escape string --- disable synchronized updates
- ~(osc8-link url text)~ escape string --- OSC 8 hyperlink wrapper
** Border helpers
- ~(border-char style pos)~ string Unicode box-drawing character
- ~(border-char style pos)~ string --- Unicode box-drawing character
** Modern backend class
- ~(make-modern-backend &key output-stream)~ modern-backend
- ~(make-modern-backend &key output-stream)~ modern-backend
- Implements all ~backend~ protocol methods with escape sequences
* Tests
The test suite lives in =modern-tests.lisp= and uses FiveAM. Each test
covers one logical behavior.
** Package and setup
The test package uses =cl-tty.backend= to access internal symbols for
white-box testing of escape generation.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(defpackage :cl-tty-modern-backend-test
(:use :cl :fiveam :cl-tty.backend)
(:export #:run-tests))
(in-package :cl-tty-modern-backend-test)
#+END_SRC
** Suite definition
A single suite groups all modern backend tests.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(def-suite modern-backend-suite :description "Modern backend tests")
(in-suite modern-backend-suite)
#+END_SRC
** Test runner
The =run-tests= entry point is called by the CI test harness.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(defun run-tests ()
(let ((result (run 'modern-backend-suite)))
(fiveam:explain! result)
(uiop:quit 0)))
#+END_SRC
;; ── Constructor ────────────────────────────────────────────────
** Constructor test
Verifies that =make-modern-backend= returns an instance of the correct
class. This is the most basic smoke test for the backend factory.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test make-modern-backend-creates
"make-modern-backend returns a modern-backend instance"
(let ((b (make-modern-backend)))
(is (typep b 'cl-tty.backend::modern-backend))))
#+END_SRC
;; ── Escape Generation ──────────────────────────────────────────
** SGR truecolor foreground escape
Ensures a 6-digit hex string produces the correct 24-bit foreground
escape sequence with red, green, and blue components in the right order.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test sgr-truecolor-foreground
"SGR truecolor foreground escape is correct"
(is (equal (cl-tty.backend::sgr-fg "#FFD700")
(format nil "~C[38;2;255;215;0m" #\Esc))))
#+END_SRC
** SGR truecolor background escape
Same as foreground but uses the =48= background prefix instead of =38=.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test sgr-truecolor-background
"SGR truecolor background escape is correct"
(is (equal (cl-tty.backend::sgr-bg "#1a1b26")
(format nil "~C[48;2;26;27;38m" #\Esc))))
#+END_SRC
** SGR named color resolution
Verifies that keyword symbols like =:red= and =:blue= resolve to the
standard 8-color SGR codes (=31= foreground, =44= background).
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test sgr-named-colors
"SGR named colors resolve to 8-color codes"
(is (equal (cl-tty.backend::sgr-fg :red)
(format nil "~C[31m" #\Esc)))
(is (equal (cl-tty.backend::sgr-bg :blue)
(format nil "~C[44m" #\Esc))))
#+END_SRC
** SGR attribute escapes
Each attribute keyword (=:bold=, =:italic=, =:underline=, =:reset=)
should map to the correct SGR number.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test sgr-bold-italic
"SGR attribute escapes are correct"
(is (equal (cl-tty.backend::sgr-attr :bold) (format nil "~C[1m" #\Esc)))
(is (equal (cl-tty.backend::sgr-attr :italic) (format nil "~C[3m" #\Esc)))
(is (equal (cl-tty.backend::sgr-attr :underline) (format nil "~C[4m" #\Esc)))
(is (equal (cl-tty.backend::sgr-attr :reset) (format nil "~C[0m" #\Esc))))
#+END_SRC
;; ── Cursor ─────────────────────────────────────────────────────
** Cursor move escape
Verifies that =cursor-move-escape= produces a CSI =H= sequence with
1-indexed row and column.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test cursor-move-escape
"cursor-move generates correct CSI escape"
(let ((b (make-modern-backend)))
(is (equal (cl-tty.backend::cursor-move-escape 5 10)
(format nil "~C[11;6H" #\Esc)))))
#+END_SRC
** Cursor style block
Verifies the DECSTR escape for a block cursor without blinking (code 2).
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test cursor-style-block
"cursor-style :block generate correct escape"
(let ((b (make-modern-backend)))
(is (equal (cl-tty.backend::cursor-style-escape :block nil)
(format nil "~C[2 q" #\Esc)))))
#+END_SRC
** Cursor style bar
Verifies the DECSTR escape for a bar cursor without blinking (code 6).
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test cursor-style-bar
"cursor-style :bar generate correct escape"
(let ((b (make-modern-backend)))
(is (equal (cl-tty.backend::cursor-style-escape :bar nil)
(format nil "~C[6 q" #\Esc)))))
#+END_SRC
** Cursor style underline with blink
Verifies that =:underline= with =blink=t= produces code 5 (underline
blinking), which is base 4 + blink offset 1.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test cursor-style-underline-blink
"cursor-style :underline with blink"
(let ((b (make-modern-backend)))
(is (equal (cl-tty.backend::cursor-style-escape :underline t)
(format nil "~C[5 q" #\Esc)))))
#+END_SRC
;; ── Synchronization ────────────────────────────────────────────
** DECICM synchronized update escapes
Confirms that =decicm-begin= and =decicm-end= produce =?2026h= and
=?2026l= respectively.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test decicm-escapes
"DECICM synchronized update escapes"
(is (equal (cl-tty.backend::decicm-begin) (format nil "~C[?2026h" #\Esc)))
(is (equal (cl-tty.backend::decicm-end) (format nil "~C[?2026l" #\Esc))))
#+END_SRC
;; ── OSC 8 Hyperlinks ──────────────────────────────────────────
** OSC 8 hyperlink escape
Verifies the full OSC 8 wrapping: opening sequence with URL, the text,
and the closing sequence. The FORMAT string uses ~~ for literal tilde
and ~\\ for literal backslash.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test osc8-escape
"OSC 8 hyperlink escape wraps text"
(is (equal (cl-tty.backend::osc8-link "http://example.com" "click here")
(format nil "~C]8;;http://example.com~C\\\\click here~C]8;;~C\\\\"
(format nil "~C]8;;http://example.com~C\\click here~C]8;;~C\"
#\Esc #\Esc #\Esc #\Esc))))
#+END_SRC
;; ── Hex Parsing ────────────────────────────────────────────────
** Hex color parsing (gold)
Verifies that ="#FFD700"= parses to (255, 215, 0).
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test hex-color-parsing
"hex-to-rgb parses valid hex colors"
(multiple-value-bind (r g b) (cl-tty.backend::hex-to-rgb "#FFD700")
(is (= r 255))
(is (= g 215))
(is (= b 0))))
#+END_SRC
** Hex color parsing (black)
Verifies all-zero parsing.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test hex-color-black
"hex-to-rgb parses black"
(multiple-value-bind (r g b) (cl-tty.backend::hex-to-rgb "#000000")
(is (= r 0))
(is (= g 0))
(is (= b 0))))
#+END_SRC
** Hex color parsing (3-digit short form)
Verifies that ="#F00"= expands to ="#FF0000"= = (255, 0, 0).
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test hex-color-short-form
"hex-to-rgb parses 3-digit hex"
(multiple-value-bind (r g b) (cl-tty.backend::hex-to-rgb "#F00")
(is (= r 255))
(is (= g 0))
(is (= b 0))))
#+END_SRC
;; ── Border Characters ──────────────────────────────────────────
** Border characters --- rounded style
Confirms that =:rounded= style maps to the Unicode box-drawing
characters for the four corners and edges.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test border-char-rounded
"modern-border-char returns Unicode box-drawing for rounded style"
(is (equal (cl-tty.backend::border-char :rounded :top-left) "╭"))
(is (equal (cl-tty.backend::border-char :rounded :horizontal) "─"))
(is (equal (cl-tty.backend::border-char :rounded :vertical) "│"))
(is (equal (cl-tty.backend::border-char :rounded :bottom-right) "╯")))
(is (equal (cl-tty.backend::border-char :rounded :bottom-right) "╯"))
#+END_SRC
** Border characters --- double style
Confirms that =:double= style maps to double-line box-drawing characters.
#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp
(test border-char-double
"modern-border-char returns double-line chars"
(is (equal (cl-tty.backend::border-char :double :top-left) "╔"))
(is (equal (cl-tty.backend::border-char :double :horizontal) "═"))
(is (equal (cl-tty.backend::border-char :double :vertical) "║")))
(is (equal (cl-tty.backend::border-char :double :vertical) "║"))
#+END_SRC
* Implementation
** Color and attribute helpers
*** hex-to-rgb
~hex-to-rgb~ parses hex color strings into (r g b) triplets. Handles
both 6-digit (fully specified) and 3-digit (shorthand) formats.
both 6-digit (fully specified) and 3-digit (shorthand) formats. The
3-digit form expands each hexit by duplicating it (=#F00= => =#FF0000=).
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(in-package :cl-tty.backend)
@@ -197,21 +312,37 @@ both 6-digit (fully specified) and 3-digit (shorthand) formats.
(parse-integer (subseq clean 4 6) :radix 16 :junk-allowed t)))))
#+END_SRC
Named color mapping and theme color store:
*** *named-colors*
Maps keyword color names to 8-color SGR index values. Used as the
primary lookup in =sgr-fg= and =sgr-bg= before falling back to the
theme colors hash table.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defparameter *named-colors*
'((:black . 0) (:red . 1) (:green . 2) (:yellow . 3)
(:blue . 4) (:magenta . 5) (:cyan . 6) (:white . 7)))
#+END_SRC
*** *theme-colors*
Hash table mapping semantic theme role keywords to hex color strings.
Populated by the theme system's =load-preset=. When a keyword is not in
=*named-colors*=, =sgr-fg= and =sgr-bg= consult this table as a
fallback, enabling user themes to define custom color roles.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defvar *theme-colors* (make-hash-table :test 'eq)
"Hash table mapping theme keywords to hex color strings.
Populated by the theme system's load-preset. Checked by sgr-fg/sgr-bg
as a fallback when a keyword is not in *named-colors*.")
#+END_SRC
~sgr-fg~ and ~sgr-bg~ produce the actual escape sequences. The
resolution chain is: hex → named color → theme semantic role → empty.
*** sgr-fg
~sgr-fg~ produces the SGR foreground escape sequence. Resolution chain:
hex string => named color => semantic theme role => empty string if
unresolved. Truecolor uses =38;2;R;G;B=, named colors use =3n=.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun sgr-fg (color)
@@ -232,6 +363,11 @@ resolution chain is: hex → named color → theme semantic role → empty.
(t ""))))
#+END_SRC
*** sgr-bg
~sgr-bg~ produces the SGR background escape. Same resolution chain as
=sgr-fg= but uses =48;2;R;G;B= for truecolor and =4n= for named colors.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun sgr-bg (color)
"Return SGR background escape for COLOR."
@@ -251,13 +387,23 @@ resolution chain is: hex → named color → theme semantic role → empty.
(t ""))))
#+END_SRC
Attribute codes map keywords to SGR numbers:
*** *sgr-attr-codes*
Maps attribute keywords to SGR parameter numbers. Covers bold, dim,
italic, underline, blink, reverse video, and reset.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defparameter *sgr-attr-codes*
'((:bold . 1) (:dim . 2) (:italic . 3) (:underline . 4)
(:blink . 5) (:reverse . 7) (:reset . 0)))
#+END_SRC
*** sgr-attr
~sgr-attr~ looks up the keyword in =*sgr-attr-codes*= and produces the
matching SGR escape.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun sgr-attr (attr)
"Return SGR attribute escape for ATTR keyword."
(let ((code (cdr (assoc attr *sgr-attr-codes*))))
@@ -268,11 +414,24 @@ Attribute codes map keywords to SGR numbers:
** Cursor escapes
*** cursor-move-escape
Produces a CSI =H= (CUP) sequence to position the cursor. Coordinates
are 1-indexed: =cursor-move-escape 0 0= moves to row 1, column 1.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun cursor-move-escape (x y)
"Return CSI escape to move cursor to (x, y), 1-indexed."
(format nil "~C[~d;~dH" #\Esc (1+ y) (1+ x)))
#+END_SRC
*** cursor-style-escape
Produces a DECSTR sequence (=CSI Ps q=) to set the cursor shape.
Base codes: block=2, underline=4, bar=6. When =blink= is true the code
is incremented by 1 (e.g. blinking block = code 3).
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun cursor-style-escape (shape blink)
"Return DECSTR escape for cursor shape."
(let* ((base (case shape
@@ -284,23 +443,50 @@ Attribute codes map keywords to SGR numbers:
** Sync and link escapes
*** decicm-begin
Enables DEC private mode 2026 (synchronized updates). All output
between =begin= and =end= is buffered by the terminal and rendered
atomically.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun decicm-begin ()
"Return escape to enable synchronized updates."
(format nil "~C[?2026h" #\Esc))
#+END_SRC
*** decicm-end
Disables DEC private mode 2026, flushing the buffered frame to the
display.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun decicm-end ()
"Return escape to disable synchronized updates."
(format nil "~C[?2026l" #\Esc))
#+END_SRC
*** osc8-link
Wraps text in an OSC 8 hyperlink. The opening sequence carries the URL,
the closing sequence (=ESC]8;;ESC\)=) terminates the link. This
allows clickable text in terminals that support the protocol.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun osc8-link (url text)
"Wrap TEXT in an OSC 8 hyperlink to URL."
(format nil "~C]8;;~A~C\\\\~A~C]8;;~C\\\\"
(format nil "~C]8;;~A~C\\~A~C]8;;~C\\"
#\Esc url #\Esc text #\Esc #\Esc))
#+END_SRC
** Border characters
*** *border-chars*
Lookup alist mapping =(style position)= pairs to Unicode box-drawing
characters. Covers single, double, and rounded styles with all four
corners plus horizontal and vertical connectors.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defparameter *border-chars*
'(((:single :top-left) . "┌") ((:single :top-right) . "┐")
@@ -312,7 +498,16 @@ Attribute codes map keywords to SGR numbers:
((:rounded :top-left) . "╭") ((:rounded :top-right) . "╮")
((:rounded :bottom-left) . "╰") ((:rounded :bottom-right) . "╯")
((:rounded :horizontal) . "─") ((:rounded :vertical) . "│")))
#+END_SRC
*** border-char
Looks up a border character by style and position. Falls back to
horizontal/vertical lines (=U+2500=, =U+2502=) if the style is unknown
for edge positions, or =+= for corners --- ensuring the UI never shows
a blank gap.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun border-char (style pos)
"Return the Unicode box-drawing character for STYLE at POS."
(let ((char (cdr (assoc (list style pos) *border-chars* :test #'equal))))
@@ -323,13 +518,28 @@ Attribute codes map keywords to SGR numbers:
** Modern backend class
*** modern-backend (class)
Subclasses the abstract =backend= class. =output-stream= is where escape
sequences are written; =in-sync-p= tracks whether we are inside a
DECICM synchronized update block.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defclass modern-backend (backend)
((output-stream :initform *standard-output*
:initarg :output-stream
:accessor backend-output-stream)
(in-sync-p :initform nil :accessor in-sync-p)))
#+END_SRC
*** make-modern-backend
Factory function that creates a =modern-backend= instance. Accepts an
optional =output-stream=; defaults to =*standard-output*=. The
=color-palette= argument is ignored in favor of the dynamic
=*theme-colors*= hash table.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defun make-modern-backend (&key color-palette output-stream)
(declare (ignore color-palette))
(make-instance 'modern-backend :output-stream (or output-stream *standard-output*)))
@@ -337,9 +547,12 @@ Attribute codes map keywords to SGR numbers:
** Lifecycle
~initialize-backend~ enters the alt screen, enables mouse tracking,
bracketed paste, and kitty keyboard protocol. ~shutdown-backend~
restores everything.
*** initialize-backend
Enters the alternate screen buffer, enables mouse tracking (basic +
drag + SGR), bracketed paste mode, and the Kitty keyboard protocol.
Hides the cursor and flushes the stream. Returns the backend instance
for chaining.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod initialize-backend ((b modern-backend))
@@ -352,7 +565,15 @@ restores everything.
(cursor-hide b)
(finish-output (backend-output-stream b))
b)
#+END_SRC
*** shutdown-backend
Restores the terminal: shows the cursor, disables the Kitty keyboard
protocol, bracketed paste, SGR mouse, drag, basic mouse, and finally
leaves the alternate screen. Returns =nil= (via =(values)=).
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod shutdown-backend ((b modern-backend))
(cursor-show b)
(backend-write b (format nil "~C[?u" #\Esc))
@@ -367,8 +588,11 @@ restores everything.
** Backend-size via ioctl
Uses TIOCGWINSZ to query actual terminal dimensions. The alien-sap
wrapper ensures compatibility across SBCL versions.
*** backend-size
Uses TIOCGWINSZ (=0x5413= = 21523) to query actual terminal dimensions
from the kernel via =ioctl=. The =alien-sap= wrapper ensures
compatibility across SBCL versions. Returns (values cols rows).
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod backend-size ((b modern-backend))
@@ -386,13 +610,27 @@ wrapper ensures compatibility across SBCL versions.
** Capability query and write
*** backend-write
Writes a string to the backend's output stream, flushing after each
write to ensure the terminal receives the escape sequence immediately.
Returns the string length for protocol compatibility.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod backend-write ((b modern-backend) string)
(let ((stream (backend-output-stream b)))
(write-string string stream)
(finish-output stream)
(length string)))
#+END_SRC
*** capable-p
Advertises which features this backend supports. =modern-backend=
supports truecolor, OSC 8 hyperlinks, DECICM sync, SGR mouse,
bracketed paste, cursor style control, and the Kitty keyboard protocol.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod capable-p ((b modern-backend) feature)
(member feature '(:truecolor :osc8 :sync :mouse
:bracketed-paste :cursor-style
@@ -401,9 +639,12 @@ wrapper ensures compatibility across SBCL versions.
** Drawing
~draw-text~ combines cursor positioning, SGR colors, attributes, the
text itself, and a reset into a single string. This minimizes ioctl
calls — one write per draw operation.
*** draw-text
Combines cursor positioning, SGR colors, optional attributes, the text
itself, and a reset into a single concatenated string. Minimizes output
calls --- one =backend-write= per draw operation --- by packing everything
into one buffer.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod draw-text ((b modern-backend) x y string fg bg
@@ -421,9 +662,12 @@ calls — one write per draw operation.
(backend-write b (apply #'concatenate 'string parts))))
#+END_SRC
~draw-border~ builds the full border as three string parts (top with
optional title, mid with sides, bottom) and writes them with minimal
output calls.
*** draw-border
Builds the full border as three distinct string parts (top with optional
title, repeated mid sections, bottom) and writes them with minimal
output calls. The title can be left-aligned or centered within the top
border line. Uses the border character lookup for the chosen style.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod draw-border ((b modern-backend) x y width height
@@ -480,6 +724,13 @@ output calls.
(backend-write b bot)))
#+END_SRC
*** draw-rect
Fills a rectangular area with a background color. For each row, moves
the cursor and writes a filled line. This is simpler than =draw-border=
because it has no border characters --- just spaces with a background
color.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod draw-rect ((b modern-backend) x y width height &key bg)
(let* ((bg-esc (sgr-bg bg))
@@ -491,7 +742,16 @@ output calls.
(loop :for row :from 0 :below height :do
(backend-write b (cursor-move-escape x (+ y row)))
(backend-write b line))))
#+END_SRC
*** draw-link
Draws a hyperlinked text at position (x, y). Combines cursor
positioning, optional fg/bg colors, the OSC 8 link wrapper around the
text, and a reset. This lets the user click the text to open the URL
in terminals that support OSC 8.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod draw-link ((b modern-backend) x y string url
&key fg bg)
(let ((parts (list (cursor-move-escape x y)
@@ -499,7 +759,15 @@ output calls.
(osc8-link url string)
(sgr-attr :reset))))
(backend-write b (apply #'concatenate 'string parts))))
#+END_SRC
*** draw-ellipsis
Draws a three-dot ellipsis at the given position. The =width= parameter
is ignored since dots have a fixed visual length; delegates to
=draw-text= for uniform rendering.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod draw-ellipsis ((b modern-backend) x y width
&key fg bg)
(declare (ignore width))
@@ -509,33 +777,87 @@ output calls.
** Cursor and input methods
*** cursor-move
Delegates to =cursor-move-escape= and writes the resulting CSI sequence
to the output stream.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod cursor-move ((b modern-backend) x y)
(backend-write b (cursor-move-escape x y)))
#+END_SRC
*** cursor-hide
Sends the DECTCEM private mode =?25l= to hide the cursor.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod cursor-hide ((b modern-backend))
(backend-write b (format nil "~C[?25l" #\Esc)))
#+END_SRC
*** cursor-show
Sends =?25h= to restore the cursor visibility.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod cursor-show ((b modern-backend))
(backend-write b (format nil "~C[?25h" #\Esc)))
#+END_SRC
*** cursor-style
Sets the cursor shape (block/underline/bar, optionally blinking) by
delegating to =cursor-style-escape=.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod cursor-style ((b modern-backend) shape &key blink)
(backend-write b (cursor-style-escape shape blink)))
#+END_SRC
*** enable-mouse
Enables basic mouse tracking, button-event tracking (drag), and SGR
extended mouse mode. These three modes together give full mouse
support while staying compatible with modern terminal emulators.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod enable-mouse ((b modern-backend))
(backend-write b (format nil "~C[?1000h" #\Esc))
(backend-write b (format nil "~C[?1002h" #\Esc))
(backend-write b (format nil "~C[?1006h" #\Esc))
(finish-output (backend-output-stream b)))
#+END_SRC
*** enable-bracketed-paste
Enables bracketed paste mode, where the terminal wraps pasted text in
=ESC[200~= and =ESC[201~= delimiters. This allows the application to
distinguish user input from pasted content.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod enable-bracketed-paste ((b modern-backend))
(backend-write b (format nil "~C[?2004h" #\Esc))
(finish-output (backend-output-stream b)))
#+END_SRC
*** begin-sync
Begins a synchronized update frame using DECICM. Sets the =in-sync-p=
slot so other methods can check whether we are inside a sync block.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod begin-sync ((b modern-backend))
(setf (in-sync-p b) t)
(backend-write b (decicm-begin)))
#+END_SRC
*** end-sync
Ends the synchronized update frame and flushes the output, causing the
terminal to render the buffered changes atomically.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod end-sync ((b modern-backend))
(setf (in-sync-p b) nil)
(backend-write b (decicm-end))