crt-ansi
Version, currently 0.1.02 versions
- 0.2.0latestMar 14, 2026
- 0.1.0not indexedMar 14, 2026
github.com/trans/crt-ansi
Pure ANSI TUI backend for CRT
Nothing has been indexed for 0.1.0 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
crt-ansi:
github: trans/crt-ansi
version: ~> 0.1.0Then run:
shards installshard.yml
No shard.yml has been indexed for 0.1.0. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
crt-ansi
Low-level ANSI terminal rendering engine for Crystal.
- Double-buffered, diff-based output (only changed cells are emitted)
- Unicode and emoji-aware cell placement
- Terminal capability detection (color depth, bold/italic/etc., hyperlinks)
- Canvas interface shared by
RenderandViewport - Panel builder for bordered, filled, text-wrapped regions
- Keyboard and mouse input parsing
- Screen lifecycle (alt screen, raw mode, cursor, resize)
Intended as an engine foundation for higher-level widget systems.
Installation
Add the dependency to your shard.yml:
dependencies:
crt-ansi:
github: trans/crt-ansi
Quick Start
require "crt-ansi"
CRT::Ansi::Screen.open do |screen|
screen.write(0, 0, "Hello, terminal!")
screen.present
screen.read_key
end
Drawing
Screen, Render, and Viewport all include the Canvas module, giving
them the same drawing API.
# Place a single grapheme
screen.put(x, y, "X", style)
# Write a string (returns cursor x after last char)
screen.write(x, y, "Hello", style)
# Read a cell back
cell = screen.cell(x, y)
cell.grapheme # => "H"
cell.style # => Style
# Clear the buffer
screen.clear
# Flush to terminal (only emits diffs after the first frame)
screen.present
Box Drawing
screen.box(x, y, w: 20, h: 5) # single-line box
screen.box(x, y, w: 20, h: 5, border: Border::Double) # double-line box
screen.box(x, y, w: 20) # horizontal line
screen.box(x, y, h: 10) # vertical line
Panels
Panels are a fluent builder for bordered regions with text, fill, and shadow.
screen.panel(2, 2, w: 30, h: 8)
.border(Border::Rounded, style: Style.new(fg: Color.rgb(100, 180, 255)))
.fill(Style.new(bg: Color.rgb(20, 30, 50)))
.text("Hello from CRT::Ansi!",
style: Style.new(fg: Color.rgb(200, 220, 255)),
wrap: Wrap::Word, pad: 1)
.shadow
.draw
Viewport
A virtual buffer for scrollable content. Draw into it like any canvas, then blit a visible window onto the screen.
vp = CRT::Ansi::Viewport.new(width: 80, height: 500)
vp.write(0, 0, "Line 1")
vp.write(0, 200, "Way down here")
vp.box(0, 300, w: 20, h: 5)
# Copy rows 190-200 of the viewport onto the screen at position (5, 3)
screen.blit(vp, x: 5, y: 3, w: 40, h: 10, scroll_y: 190)
screen.present
Styles
Styles are immutable value types composed via with_* methods or
constructed directly.
include CRT::Ansi
# Direct construction
style = Style.new(bold: true, fg: Color.rgb(255, 100, 0))
# Composition
style = Style.default
.with_bold
.with_fg(Color.rgb(255, 100, 0))
.with_bg(Color.indexed(17))
.with_hyperlink("https://example.com")
# Merge (right side wins on conflicts)
combined = Style::BOLD.merge(Style.new(italic: true))
# Shortcut constants
Style::BOLD
Style::DIM
Style::ITALIC
Style::UNDERLINE
Style::INVERSE
Style::STRIKE
Colors
Color.default # terminal default
Color.indexed(196) # 256-color palette
Color.rgb(255, 100, 0) # 24-bit truecolor
Color output adapts to detected terminal capabilities -- truecolor values are downsampled to 256 or 16 colors when needed.
Frame Loop
Screen#run provides a frame-rate-limited loop that yields once per
frame and calls present automatically. Use poll_event inside
the block for non-blocking input.
CRT::Ansi::Screen.open(mouse: true) do |screen|
y = 0
screen.run(fps: 30) do
while event = screen.poll_event
case event
when CRT::Ansi::Key
break if event.char == "q"
y += 1 if event.code.down?
y -= 1 if event.code.up?
when CRT::Ansi::Mouse
# event.x, event.y, event.button, event.action
end
end
screen.clear
screen.write(0, 0, "Scroll: #{y}")
end
end
For blocking input (no animation needed), use read_event directly:
CRT::Ansi::Screen.open do |screen|
loop do
screen.clear
screen.write(0, 0, "Press q to quit")
screen.present
key = screen.read_key
break if key && key.char == "q"
end
end
Dirty Flag Pattern
present already diffs -- zero changes means zero IO. If you want
to also skip the drawing calls, use a local dirty flag:
screen.run(fps: 30) do
while event = screen.poll_event
dirty = true
# handle event, update state
end
if dirty
screen.clear
# draw
dirty = false
end
end
Concurrency
With Crystal's multi-threading (-Dpreview_mt), keep all drawing on
a single fiber. Other fibers communicate via channels:
updates = Channel(String).new
spawn do
# background work
updates.send("new data")
end
screen.run(fps: 30) do
while event = screen.poll_event
# handle input
end
select
when msg = updates.receive?
# apply update, redraw
else
end
end
The buffer is not thread-safe by design -- the single-fiber draw loop is the correct pattern, not mutexes.
Terminal Capabilities
Capabilities are auto-detected from terminfo and environment variables, or can be configured manually.
# Auto-detect (called by Screen.open)
CRT::Ansi.configure!
# Manual configuration
ctx = CRT::Ansi::Context.new(
capabilities: CRT::Ansi::Capabilities.new(
color_support: CRT::Ansi::Capabilities::ColorSupport::Truecolor,
hyperlinks: true,
)
)
render = CRT::Ansi::Render.new(STDOUT, 80, 24, context: ctx)
Architecture
Screen Terminal lifecycle, IO, resize, input
└─ Render Double-buffered diff renderer (includes Canvas)
└─ Buffer Cell grid with grapheme/style storage
Viewport Virtual scrollable buffer (includes Canvas)
└─ Buffer
Canvas Shared drawing interface (put, write, box, panel, blit)
Panel Fluent builder for bordered regions
Style Immutable text attributes + color + hyperlink
Context Capabilities + terminal adapter (threaded, not global)
Examples
See the examples/ directory:
crystal run examples/demo.cr # rendering + animation
crystal run examples/panel.cr # panel builder showcase
crystal run examples/input.cr # keyboard input
crystal run examples/mouse.cr # mouse tracking
Development
crystal spec
Contributors
- Thomas Sawyer - creator and maintainer
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.1.0- Tagged
- Mar 14, 2026
- Commit
d950db4be0bf- Indexed
- not yet
Dependents
Repository
github.com/trans/crt-ansi
Metadata
- Created
- Aug 12, 2026
- Updated
- Sep 25, 2026
- Synced
- Sep 25, 2026
- Versions
- 2