restructure: move backend/ and layout/ into src/; convert README to org syntax; fix demo package conflict and alien-sap ioctl; update ROADMAP with v0.15.0; remove stale files
- Move backend/ and layout/ directories into src/ - Update all path references in ASD, scripts, docs - Convert README.org from Markdown syntax to proper Org-mode - Fix demo.lisp use-package conflict (both backend and input export #:read-event) - Fix modern-backend TIOCGWINSZ ioctl alien type (alien-sap wrapper) - Add v0.15.0 section to ROADMAP, update line count to 5760 - Add known gaps (suspend/resume-backend, slot modes) to v1.0.0 checklist - Remove docs/plans/, debug-layout.lisp, system-index.txt, ci-watchdog.sh - Move tangle.py to Hermes skill (org-babel-tangle) - Add .gitignore for fasl files
This commit is contained in:
207
README.org
207
README.org
@@ -1,17 +1,17 @@
|
||||
# cl-tty — Terminal UI Framework for Common Lisp
|
||||
#+TITLE: cl-tty — Terminal UI Framework for Common Lisp
|
||||
|
||||
Pure CL terminal UI framework. No ncurses, no FFI, no external dependencies.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(ql:quickload :cl-tty)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
## Quick start
|
||||
* Quick start
|
||||
|
||||
The simplest possible cl-tty program — detect the terminal, draw some text,
|
||||
read a key, and shut down:
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(sb-posix:with-raw-terminal
|
||||
(let* ((be (cl-tty.backend:detect-backend))
|
||||
(w 80) (h 24))
|
||||
@@ -24,30 +24,30 @@ read a key, and shut down:
|
||||
;; Read one key (blocks)
|
||||
(cl-tty.input:read-event be))
|
||||
(cl-tty.backend:shutdown-backend be))))
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
Or run the full interactive demo:
|
||||
|
||||
```bash
|
||||
#+BEGIN_SRC bash
|
||||
sbcl --script demo.lisp
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
## Architecture
|
||||
* Architecture
|
||||
|
||||
Two backends, one protocol:
|
||||
|
||||
- **modern-backend** — truecolor 24-bit, OSC 8 hyperlinks, DECICM sync,
|
||||
- *modern-backend* — truecolor 24-bit, OSC 8 hyperlinks, DECICM sync,
|
||||
SGR mouse, kitty keyboard, bold/italic/underline, box-drawing chars
|
||||
- **simple-backend** — ASCII art, no color, universal compatibility (pipe-safe)
|
||||
- *simple-backend* — ASCII art, no color, universal compatibility (pipe-safe)
|
||||
|
||||
Everything is pure escape sequences (no curses, no terminfo, no FFI).
|
||||
|
||||
### Backend protocol
|
||||
** Backend protocol
|
||||
|
||||
Every drawing operation is a CLOS generic function dispatched on the backend
|
||||
class. Programs never call terminal codes directly:
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
;; Lifecycle
|
||||
(initialize-backend backend)
|
||||
(shutdown-backend backend)
|
||||
@@ -67,11 +67,11 @@ class. Programs never call terminal codes directly:
|
||||
(cursor-hide backend)
|
||||
(cursor-show backend)
|
||||
(cursor-style backend shape &key blink) ;; :bar :block :underline
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Event loop pattern
|
||||
** Event loop pattern
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(let ((be (detect-backend)))
|
||||
(initialize-backend be)
|
||||
(loop with running = t
|
||||
@@ -89,48 +89,48 @@ class. Programs never call terminal codes directly:
|
||||
))
|
||||
(when (eq event :eof) (setf running nil))))
|
||||
(shutdown-backend be))
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Layout system
|
||||
** Layout system
|
||||
|
||||
Pure CL flexbox layout engine. No C dependencies, no Yoga FFI.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
;; Macros build layout-trees:
|
||||
(vbox (:gap 1 :padding 1)
|
||||
(header "Title")
|
||||
(hbox (:grow 1)
|
||||
(sidebar (:width 30) ...)
|
||||
(content ...)))
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
Layout properties: `:direction` (`:row` / `:column`), `:grow`, `:shrink`,
|
||||
`:basis`, `:gap`, `:padding`, `:margin`, `:width`, `:height`, `:wrap`.
|
||||
Layout properties: ~:direction~ (~:row~ / ~:column~), ~:grow~, ~:shrink~,
|
||||
~:basis~, ~:gap~, ~:padding~, ~:margin~, ~:width~, ~:height~, ~:wrap~.
|
||||
|
||||
See `layout/layout.lisp` or `org/layout-engine.org` for the full API.
|
||||
See ~src/layout/layout.lisp~ or ~org/layout-engine.org~ for the full API.
|
||||
|
||||
### Rendering pipeline
|
||||
** Rendering pipeline
|
||||
|
||||
Component trees render through a coordinated pipeline:
|
||||
|
||||
1. **Layout pass** — `compute-layout` traverses dirty branches, solves flex constraints
|
||||
2. **Render dispatch** — `render` generic dispatches per component type
|
||||
3. **Framebuffer** — (optional) `make-framebuffer-backend` captures to a cell array,
|
||||
`diff-framebuffers` computes minimal changes, `flush-framebuffer` writes only
|
||||
1. *Layout pass* — ~compute-layout~ traverses dirty branches, solves flex constraints
|
||||
2. *Render dispatch* — ~render~ generic dispatches per component type
|
||||
3. *Framebuffer* — (optional) ~make-framebuffer-backend~ captures to a cell array,
|
||||
~diff-framebuffers~ computes minimal changes, ~flush-framebuffer~ writes only
|
||||
changed cells
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
;; Full pipeline with framebuffer
|
||||
(let* ((fb-be (make-framebuffer-backend :width 80 :height 24))
|
||||
(fb (fb-framebuffer fb-be)))
|
||||
(render my-component fb-be)
|
||||
(flush-framebuffer prev-fb fb real-backend))
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
## Components
|
||||
* Components
|
||||
|
||||
| Component | What it does | Status |
|
||||
|-------------|------------------------------------------------------|--------|
|
||||
|-------------+------------------------------------------------------+--------|
|
||||
| Box | Bordered container with background, title | stable |
|
||||
| Text | Styled text with word-wrap, spans | stable |
|
||||
| ScrollBox | Scrollable viewport with scrollbars | stable |
|
||||
@@ -146,7 +146,7 @@ Component trees render through a coordinated pipeline:
|
||||
|
||||
Each component follows a consistent pattern:
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
;; 1. Create — factory function returns instance
|
||||
(let ((input (make-text-input :placeholder "Type here..."))
|
||||
(box (make-box :border-style :single :title "My Box")))
|
||||
@@ -160,135 +160,135 @@ Each component follows a consistent pattern:
|
||||
|
||||
;; 3. Render — dispatches through the component protocol
|
||||
(render my-component backend))
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Box
|
||||
*** Box
|
||||
|
||||
Bordered container. Draws borders using Unicode box-drawing characters
|
||||
(modern) or ASCII `+`/`-`/`|` (simple). Supports background fill, titled
|
||||
borders. See `org/box-renderable.org`.
|
||||
(modern) or ASCII ~+~/~-~/~|~ (simple). Supports background fill, titled
|
||||
borders. See ~org/box-renderable.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-box &key (border-style :single) title (title-align :left) fg bg width height)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Text
|
||||
*** Text
|
||||
|
||||
Styled text with inline spans and word wrapping. Spans support per-run
|
||||
attributes (bold, italic, underline, fg, bg). See `org/box-renderable.org`.
|
||||
attributes (bold, italic, underline, fg, bg). See ~org/box-renderable.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-text content &key fg bg wrap-mode width height spans)
|
||||
;; Span example:
|
||||
(span "hello" :bold t :fg :bright-yellow)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### TextInput
|
||||
*** TextInput
|
||||
|
||||
Single-line text editor with emacs-style keybindings. Supports placeholder,
|
||||
max-length, on-submit callback. See `org/text-input.org`.
|
||||
max-length, on-submit callback. See ~org/text-input.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-text-input &key value cursor placeholder max-length on-submit)
|
||||
;; Widget logic (input-level, no backend needed):
|
||||
(handle-text-input input (make-key-event :key :a :code (char-code #\a)))
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### TextArea
|
||||
*** TextArea
|
||||
|
||||
Multi-line text editor. Supports undo/redo (Ctrl+Z/Y), cursor movement,
|
||||
line joining on backspace. See `org/text-input.org`.
|
||||
line joining on backspace. See ~org/text-input.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-textarea &key value on-submit)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### ScrollBox
|
||||
*** ScrollBox
|
||||
|
||||
Scrollable viewport with a list of children. Only renders children
|
||||
intersecting the visible area (viewport culling). Scrollbars drawn
|
||||
at the right/bottom edges. See `org/scrollbox-tabbar.org`.
|
||||
at the right/bottom edges. See ~org/scrollbox-tabbar.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-scroll-box &key children scroll-y scroll-x sticky-scroll-p)
|
||||
(scroll-by sb dy dx)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### TabBar
|
||||
*** TabBar
|
||||
|
||||
Horizontal tab navigation. Renders tab labels, highlights active tab.
|
||||
Left/right arrows cycle through tabs. See `org/scrollbox-tabbar.org`.
|
||||
Left/right arrows cycle through tabs. See ~org/scrollbox-tabbar.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-tab-bar &key tabs active)
|
||||
(tab-bar-add tb id title)
|
||||
(tab-bar-next tb) / (tab-bar-prev tb)
|
||||
(tab-bar-handle-key tb event)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Select
|
||||
*** Select
|
||||
|
||||
Dropdown/filter widget. Options can have categories (rendered as
|
||||
non-selectable headers). Fuzzy fallback: matching > 30% character
|
||||
overlap. Arrow keys navigate, Enter selects. See `org/select.org`.
|
||||
overlap. Arrow keys navigate, Enter selects. See ~org/select.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(make-select &key options filter on-select)
|
||||
;; Options format: (:title "Name" :category "Group") or (:title "Name")
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Markdown
|
||||
*** Markdown
|
||||
|
||||
Parsed markdown AST with rendering. Supports headings, paragraphs,
|
||||
bold, italic, inline code, links, code blocks with syntax highlighting,
|
||||
diff blocks, blockquotes, lists, thematic breaks. See
|
||||
`org/markdown-renderer.org`.
|
||||
~org/markdown-renderer.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(render-markdown "# Hello\n\nThis is **bold**.")
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Dialog + Toast
|
||||
*** Dialog + Toast
|
||||
|
||||
Modal dialog stack. `alert-dialog`, `confirm-dialog`, `select-dialog`,
|
||||
`prompt-dialog` are convenience constructors. Toasts are transient
|
||||
notifications that auto-dismiss. See `org/dialog.org`.
|
||||
Modal dialog stack. ~alert-dialog~, ~confirm-dialog~, ~select-dialog~,
|
||||
~prompt-dialog~ are convenience constructors. Toasts are transient
|
||||
notifications that auto-dismiss. See ~org/dialog.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(push-dialog (make-instance 'dialog :size :medium))
|
||||
(alert-dialog "Notice" "Operation complete")
|
||||
(toast "Saved!" :variant :success)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Mouse
|
||||
*** Mouse
|
||||
|
||||
Mixin class providing mouse event handler slots. `hit-test` finds the
|
||||
Mixin class providing mouse event handler slots. ~hit-test~ finds the
|
||||
deepest component at a coordinate. Text selection tracks drag gestures.
|
||||
Scrollboxes integrate wheel events. See `org/mouse.org`.
|
||||
Scrollboxes integrate wheel events. See ~org/mouse.org~.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(defclass my-panel (mouse-mixin) ...)
|
||||
(handle-mouse-event component mouse-event)
|
||||
(hit-test root x y) → deepest matching component
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
### Slot system
|
||||
*** Slot system
|
||||
|
||||
Plugin system for extensible rendering slots. Register named rendering
|
||||
functions, then render them by slot name. Useful for toolbars, status
|
||||
bars, and plugin architectures.
|
||||
|
||||
```lisp
|
||||
#+BEGIN_SRC lisp
|
||||
(defslot :status-bar :order 0
|
||||
(lambda (&rest args)
|
||||
(draw-text backend 0 0 "Ready" :text-muted nil)))
|
||||
(slot-render :status-bar)
|
||||
```
|
||||
#+END_SRC
|
||||
|
||||
## Backend features
|
||||
* Backend features
|
||||
|
||||
| Feature | modern | simple |
|
||||
|-------------------|--------|--------|
|
||||
|-------------------+--------+--------|
|
||||
| Truecolor (24-bit)| Yes | No |
|
||||
| Bold/italic | Yes | No |
|
||||
| OSC 8 hyperlinks | Yes | No |
|
||||
@@ -298,16 +298,17 @@ bars, and plugin architectures.
|
||||
| Box drawing chars | Unicode| ASCII |
|
||||
| Pipe-safe | No | Yes |
|
||||
|
||||
Backend selection happens automatically via `detect-backend`. It checks:
|
||||
Backend selection happens automatically via ~detect-backend~. It checks:
|
||||
|
||||
1. Is stdout a TTY? (if not → simple-backend)
|
||||
2. Does `COLORTERM` contain "truecolor" or "24bit"?
|
||||
2. Does ~COLORTERM~ contain "truecolor" or "24bit"?
|
||||
3. Send DA1 query — does the terminal respond with modern feature codes?
|
||||
|
||||
Result is cached in `*detected-backend*`.
|
||||
Result is cached in ~*detected-backend*~.
|
||||
|
||||
## Development
|
||||
* Development
|
||||
|
||||
```bash
|
||||
#+BEGIN_SRC bash
|
||||
# Run all tests (483 checks, 13 suites)
|
||||
sbcl --script run-all-tests.lisp
|
||||
|
||||
@@ -315,29 +316,29 @@ sbcl --script run-all-tests.lisp
|
||||
sbcl --script demo.lisp
|
||||
|
||||
# Tangle org files (regenerate .lisp from .org sources)
|
||||
python3 scripts/tangle.py org/*.org
|
||||
```
|
||||
python3 ~/.hermes/skills/software-development/org-babel-tangle/scripts/tangle.py org/*.org
|
||||
#+END_SRC
|
||||
|
||||
Literate programming: `.org` files in `org/` are the source of truth for
|
||||
Literate programming: ~.org~ files in ~org/~ are the source of truth for
|
||||
the input system, scrollbox/tabbar, dialog, mouse, select, slot,
|
||||
framebuffer, and markdown modules. The backend (`modern.lisp`,
|
||||
`simple.lisp`) and basic components (`box.lisp`, `text.lisp`, `render.lisp`,
|
||||
`theme.lisp`, `dirty.lisp`) are written directly.
|
||||
framebuffer, and markdown modules. The backend (~modern.lisp~,
|
||||
~simple.lisp~) and basic components (~box.lisp~, ~text.lisp~, ~render.lisp~,
|
||||
~theme.lisp~, ~dirty.lisp~) are written directly.
|
||||
|
||||
Project structure:
|
||||
|
||||
```
|
||||
#+BEGIN_EXAMPLE
|
||||
cl-tty/
|
||||
├── cl-tty.asd # ASDF system definition
|
||||
├── demo.lisp # Interactive demo
|
||||
├── run-all-tests.lisp # Test runner
|
||||
├── backend/ # Backend protocol + implementations
|
||||
├── src/backend/ # Backend protocol + implementations
|
||||
│ ├── package.lisp
|
||||
│ ├── classes.lisp # Generic definitions
|
||||
│ ├── simple.lisp # ASCII fallback backend
|
||||
│ ├── modern.lisp # Truecolor escape backend
|
||||
│ └── detection.lisp # Auto-detect backend from env
|
||||
├── layout/ # Flexbox layout engine
|
||||
│ ├── classes.lisp # Generic definitions
|
||||
│ ├── simple.lisp # ASCII fallback backend
|
||||
│ ├── modern.lisp # Truecolor escape backend
|
||||
│ └── detection.lisp # Auto-detect backend from env
|
||||
├── src/layout/ # Flexbox layout engine
|
||||
│ └── layout.lisp
|
||||
├── src/
|
||||
│ ├── rendering/ # Framebuffer backend + diff + flush
|
||||
@@ -369,8 +370,8 @@ cl-tty/
|
||||
└── docs/
|
||||
├── ROADMAP.org # Versioned roadmap
|
||||
└── ARCHITECTURE.org # Design docs
|
||||
```
|
||||
#+END_EXAMPLE
|
||||
|
||||
## License
|
||||
* License
|
||||
|
||||
GNU General Public License v3.0
|
||||
|
||||
Reference in New Issue
Block a user