Skip to content

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 19 May 08:17
· 84 commits to main since this release
v0.10.0
4532de4

Added

  • ExRatatui.Widgets.CodeBlock — syntax-highlighted source code. Display-only widget powered by syntect's bundled SyntaxSet and ThemeSet. Fields: :content, :language (any syntect token name; nil for 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 with Block, Popup, and WidgetList like 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 full CodeBlock in the tree. Backed by a single new NIF highlight_code/3 (scheduled on DirtyCpu) returning Vec<Vec<HighlightedSpan>> (NifMap with content, fg/bg as Option<(u8, u8, u8)>, plus bold/italic/underlined flags). ExRatatui.CodeBlock.resolve_theme/1 is the canonical theme-atom resolver used by both the widget encoder and the helper — single source of truth. ExRatatui.CodeBlock.from_native/1 is a documented conversion seam for callers using Native.highlight_code/3 directly (e.g. hot loops reusing the same theme).

  • [:ex_ratatui, :code_block, :highlight] telemetry span. Each ExRatatui.CodeBlock.highlight/3 call emits start + stop via :telemetry.span/3 with language (string or nil), theme (resolved syntect name), and bytes (source byte size); the stop event adds line_count. Mirrors the [:ex_ratatui, :image, :decode] span shape.

  • Shared widgets::highlighter Rust module. OnceLock-cached SyntaxSet (load_defaults_newlines) + ThemeSet (load_defaults), lines_for(code, language, theme) returning Vec<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 existing syntect-tui adapter 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 & content next to BigText, and a new "Widgets: Code" ex_doc group covering ExRatatui.CodeBlock + ExRatatui.Widgets.CodeBlock.

  • Bundled Elixir syntax. native/ex_ratatui/syntaxes/Elixir.sublime-syntax is vendored from elixir-editors/elixir-sublime-syntax (MIT, copyright Po Chen — LICENSE alongside) and added to syntect's SyntaxSet at startup via into_builder()SyntaxDefinition::load_from_str(include_str!(...)).add().build(). language: "elixir" now produces real per-token highlighting (defmodule/do/end as 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.Markdown fenced code blocks still use base16-ocean.darktui-markdown 0.3.7 hardcodes the syntect theme internally and does not expose it through its public API. A :code_theme knob 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 via CodeBlock directly.

  • 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 the font8x8 bitmap font. ExRatatui.BigText.new/2 coerces text-like input through the shared text-coercion path (binary / %Span{} / %Line{} / %Text{} / homogeneous lists), validates :pixel_size and :alignment, and merges any outer %Text{} style underneath the widget's own. Eight pixel_size densities 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 with Block, Popup, and WidgetList like 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 every pixel_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/2 returns 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 TransportCaps value the render path consults: CellSession forces :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 a Picker::from_query_stdio probe via ExRatatui.Image.auto_local_protocol/1 so :auto resolves to the detected protocol with the right font size; SSH and Distributed accept an :image_protocol opt at start time, optionally paired with :image_font_size for 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 at Image.new/2 are always honored.

  • Image rendering over ExRatatui.Distributed. Image widgets cross node boundaries via a snapshot path: the server runtime calls image_snapshot/1 on 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 fresh ImageResource per 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: true runtime opt for mount/1. ExRatatui.App apps can opt into auto-probing the local terminal by returning {:ok, state, probe_image_protocol: true}. The runtime calls ExRatatui.Image.auto_local_protocol/1 once after mount on the :local transport — :auto images then resolve to the detected protocol without the app needing access to the terminal reference. Skipped under test_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's Resize::needs_resize_pixels + fit_area_proportionally byte-for-byte. Useful for status panels, layout decisions, or just understanding why :fit and :crop produce identical output when the source is smaller than the target.

  • :background accepts the full t:ExRatatui.Style.color/0 shape. Named atoms (:red, :dark_gray, …), {:rgb, r, g, b}, {:indexed, n} xterm 256-color codes, raw {r, g, b} tuples, and nil all 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. Each Image.new/2 call emits start + stop with format (sniffed from magic bytes — :png / :jpeg / :gif / :webp / :bmp / :unknown), bytes, and width / height on success. Per-render encode timing stays inside the existing [:ex_ratatui, :render, :frame] span.

  • Examples. examples/image_demo.exs is an interactive viewer with runtime protocol / resize toggling and a live status panel showing rendered output dimensions; supports --ssh and --distributed flags for smoke-testing those transports end-to-end. examples/headless_image.exs renders an image through CellSession (with ANSI fg/bg per cell) for Livebook / snapshot consumers. Both accept IMAGE_PATH to skip the network fetch, default to picsum.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::Fit doesn'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 with EX_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-Rust icy_sixel.

Fixed

  • Precompiled musl NIFs now load under musl runtimes. native/ex_ratatui/.cargo/config.toml's x86_64-unknown-linux-musl and aarch64-unknown-linux-musl targets now pass -C link-arg=-static-libgcc alongside the existing target-feature=-crt-static. Without it the NIF .so linked against the build host's glibc libgcc_s.so.1 and 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.