v0.10.0
Added
-
ExRatatui.Widgets.CodeBlock— syntax-highlighted source code. Display-only widget powered by syntect's bundledSyntaxSetandThemeSet. Fields::content,:language(any syntect token name;nilfor plain text fallback),:theme(seven curated atoms —:base16_ocean_dark,:base16_ocean_light,:base16_eighties_dark,:base16_mocha_dark,:inspired_github,:solarized_dark,:solarized_light— or any raw string for custom theme sets),:line_numbers+:starting_line(right-aligned dim gutter with│separator, width grows with the last visible line),:highlight_lines(list of ints + ranges like[3, 7..9], normalised to a sorted unique list, rendered with a theme-derived background that brightens dark themes and dims light themes by 20/256 per channel). Composes withBlock,Popup, andWidgetListlike every other widget. One new widget type ("code_block") on the render decoder. -
ExRatatui.CodeBlock.highlight/3— raw highlighted lines for composite widgets. Returns[%ExRatatui.Text.Line{}]with per-token styled spans for callers building DiffViewer / Inspector-style composites without dropping a fullCodeBlockin the tree. Backed by a single new NIFhighlight_code/3(scheduled onDirtyCpu) returningVec<Vec<HighlightedSpan>>(NifMap withcontent,fg/bgasOption<(u8, u8, u8)>, plusbold/italic/underlinedflags).ExRatatui.CodeBlock.resolve_theme/1is the canonical theme-atom resolver used by both the widget encoder and the helper — single source of truth.ExRatatui.CodeBlock.from_native/1is a documented conversion seam for callers usingNative.highlight_code/3directly (e.g. hot loops reusing the same theme). -
[:ex_ratatui, :code_block, :highlight]telemetry span. EachExRatatui.CodeBlock.highlight/3call emits start + stop via:telemetry.span/3withlanguage(string ornil),theme(resolved syntect name), andbytes(source byte size); the stop event addsline_count. Mirrors the[:ex_ratatui, :image, :decode]span shape. -
Shared
widgets::highlighterRust module.OnceLock-cachedSyntaxSet(load_defaults_newlines) +ThemeSet(load_defaults),lines_for(code, language, theme)returningVec<Line<'static>>,theme_bg(theme)for the emphasis-color base. The ~25-line syntect→ratatui style/color/modifier translation is inlined (MIT-attributed in the module header) because the existingsyntect-tuiadapter is pinned to older ratatui versions (0.28/0.29) and we're on 0.30. -
examples/code_block_demo.exs. Interactive viewer that cycles through all seven themes and five sample languages (Rust, Python, Ruby, JavaScript, JSON), toggles the line-number gutter, and toggles emphasis on lines 3..5. Status panel echoes the active config live. -
Cheatsheet entry under
Text & contentnext to BigText, and a new"Widgets: Code"ex_doc group coveringExRatatui.CodeBlock+ExRatatui.Widgets.CodeBlock. -
Bundled Elixir syntax.
native/ex_ratatui/syntaxes/Elixir.sublime-syntaxis vendored from elixir-editors/elixir-sublime-syntax (MIT, copyright Po Chen — LICENSE alongside) and added to syntect'sSyntaxSetat startup viainto_builder()→SyntaxDefinition::load_from_str(include_str!(...))→.add()→.build().language: "elixir"now produces real per-token highlighting (defmodule/do/endas keywords, atoms, strings, sigils — six distinct fg colors on a typical snippet under base16-ocean.dark). Adds ~80 KB to the binary. Other BEAM languages: Erlang ships in syntect's defaults; EEx / HEEx / Surface are not yet bundled — same approach can extend to them when needed. -
Known limitation:
ExRatatui.Widgets.Markdownfenced code blocks still usebase16-ocean.dark— tui-markdown 0.3.7 hardcodes the syntect theme internally and does not expose it through its public API. A:code_themeknob on the Markdown widget depends on upstream cooperation and is out of scope for this release; users who need themed fenced blocks today can pre-extract the source and render viaCodeBlockdirectly. -
ExRatatui.Widgets.BigText— oversized 8×8 pixel text for slide titles and banners. Drop-in widget backed by tui-big-text 0.8.4; each character is rasterised through thefont8x8bitmap font.ExRatatui.BigText.new/2coerces text-like input through the shared text-coercion path (binary /%Span{}/%Line{}/%Text{}/ homogeneous lists), validates:pixel_sizeand:alignment, and merges any outer%Text{}style underneath the widget's own. Eightpixel_sizedensities cover the full upstream variant set::full(default — 1 cell per pixel, 8 rows tall),:half_height,:half_width,:quadrant,:third_height,:sextant,:quarter_height,:octant(1 row × half cols). Composes withBlock,Popup, andWidgetListlike every other widget. Adds one new widget type ("big_text") to the render decoder; no new NIFs. -
examples/big_text_demo.exs. Interactive viewer that cycles everypixel_size, three alignments, six colors, and four sample slide titles at runtime. Status panel echoes the active settings live. Helpful when picking a variant for a real slide deck. -
ExRatatui.Widgets.Image— image rendering across every transport. Decodes PNG / JPEG / GIF / WebP / BMP bytes and renders them through ratatui-image with Kitty, Sixel, iTerm2, or Unicode halfblocks.ExRatatui.Image.new/2returns a stateful widget handle ({:ok, %ExRatatui.Widgets.Image{}}) or{:error, {:decode_failed, msg}}; protocol / resize mode / background are set at construction and stored on a NIF resource so re-encoding only happens when the resolved protocol or rect dimensions change. Three resize strategies::fit(preserve aspect inside the rect),:crop(preserve aspect, fill, crop overflow),:scale(stretch to fill). -
Transport-aware protocol resolution. Each transport stamps a
TransportCapsvalue the render path consults:CellSessionforces:halfblocks(escape sequences can't survive cell diffing — Livebook / Kino apps that share model code with a real terminal just work); the local terminal can cache aPicker::from_query_stdioprobe viaExRatatui.Image.auto_local_protocol/1so:autoresolves to the detected protocol with the right font size; SSH and Distributed accept an:image_protocolopt at start time, optionally paired with:image_font_sizefor accurate Kitty / Sixel / iTerm2 scaling (ExRatatui.SSH.Daemon.start_link(..., image_protocol: :kitty, image_font_size: {10, 20}),ExRatatui.Distributed.attach(..., image_protocol: :kitty, image_font_size: {10, 20})). Explicit per-image protocol selections atImage.new/2are always honored. -
Image rendering over
ExRatatui.Distributed. Image widgets cross node boundaries via a snapshot path: the server runtime callsimage_snapshot/1on each%ExRatatui.Widgets.Image{state: ref}in the render tree before sending the widget list over the wire (a NIF ResourceArc can't cross nodes). The client node re-decodes the bytes into a freshImageResourceper draw. Snapshot wire shape is{bytes, protocol, resize, background}. Costs roughly the source PNG size per frame on the wire — fine for stills, watch bandwidth for animations on large images. -
probe_image_protocol: trueruntime opt formount/1.ExRatatui.Appapps can opt into auto-probing the local terminal by returning{:ok, state, probe_image_protocol: true}. The runtime callsExRatatui.Image.auto_local_protocol/1once after mount on the:localtransport —:autoimages then resolve to the detected protocol without the app needing access to the terminal reference. Skipped undertest_mode. Other transports ignore the opt. -
ExRatatui.Image.render_size/4. Pure-Elixir prediction of the rendered output pixel dimensions for a given (source dims, cell area, font size, resize mode) combination. Mirrors ratatui-image'sResize::needs_resize_pixels+fit_area_proportionallybyte-for-byte. Useful for status panels, layout decisions, or just understanding why:fitand:cropproduce identical output when the source is smaller than the target. -
:backgroundaccepts the fullt:ExRatatui.Style.color/0shape. Named atoms (:red,:dark_gray, …),{:rgb, r, g, b},{:indexed, n}xterm 256-color codes, raw{r, g, b}tuples, andnilall work. Named and indexed values are converted to RGB at the Elixir boundary via the standard ANSI palette. -
New public API surface.
ExRatatui.Image.{new,dimensions,probe_terminal,auto_local_protocol,render_size}/{1,2,4},ExRatatui.Session.{set_image_protocol,set_image_font_size}/2,ExRatatui.set_image_protocol/2. Eight new NIFs:image_new/2,image_dimensions/1,image_snapshot/1,image_probe_terminal/0,session_set_image_protocol/2,session_set_image_font_size/2,terminal_set_image_protocol/2,terminal_set_local_probe/3. -
[:ex_ratatui, :image, :decode]telemetry span. EachImage.new/2call emits start + stop withformat(sniffed from magic bytes —:png/:jpeg/:gif/:webp/:bmp/:unknown),bytes, andwidth/heighton success. Per-render encode timing stays inside the existing[:ex_ratatui, :render, :frame]span. -
Examples.
examples/image_demo.exsis an interactive viewer with runtime protocol / resize toggling and a live status panel showing rendered output dimensions; supports--sshand--distributedflags for smoke-testing those transports end-to-end.examples/headless_image.exsrenders an image throughCellSession(with ANSI fg/bg per cell) for Livebook / snapshot consumers. Both acceptIMAGE_PATHto skip the network fetch, default topicsum.photos, and embed a 1×1 fallback PNG for offline use. -
Images guide and cheatsheet entry. Walks through the API, the full transport / protocol resolution table, the font-size caveat for Kitty / Sixel / iTerm2 scaling, telemetry, and known v1 limitations (no GIF animation, no SVG, no streaming decode,
Resize::Fitdoesn't upscale).
Changed
-
Rust toolchain bumped to 1.86+ to match ratatui-image 11.0.2's
rust-version(edition 2024). ex_ratatui itself stays on edition 2021; precompiled binaries are unaffected — you only hit this if you build the NIF yourself withEX_RATATUI_BUILD=1. -
Binary size grows by ~1.6 MB. ratatui-image pulls in
image(with PNG / JPEG / GIF / WebP / BMP decoders) and bundled Kitty / Sixel / iTerm2 encoders. No new system dependencies — chafa is feature-gated off, sixel uses pure-Rusticy_sixel.
Fixed
- Precompiled musl NIFs now load under musl runtimes.
native/ex_ratatui/.cargo/config.toml'sx86_64-unknown-linux-muslandaarch64-unknown-linux-musltargets now pass-C link-arg=-static-libgccalongside the existingtarget-feature=-crt-static. Without it the NIF.solinked against the build host's glibclibgcc_s.so.1and the musl loader on the consumer side refused it. Alpine and other musl-libc deploys can now consume the precompiled artifact directly — no source rebuild required.