Rust

The Rust crates drive the native renderer from Rust, without JavaScript. They are experimental and not published: build them from a checkout of the repository. The crates are in packages/rust:

  • opentui-sys declares the C ABI of opentui.h. It is generated from the header.
  • opentui wraps the ABI safely and adds widgets, an event loop, focus, input, and headless tests.
  • opentui-ssh serves apps over SSH, one app per session.

Build and test#

The crates link the native library of the same checkout. Build it first, then run Cargo from the repository root:

bun install
bun run build
cargo test --manifest-path packages/rust/Cargo.toml

bun run build:native in the core package also builds the native library. To link other artifacts, such as the directory of an npm platform package, set OPENTUI_LIB_DIR to that directory. It wins over the checkout’s build. The npm platform packages ship only the shared library, so on Linux and macOS they need the default features off (see below). Give an absolute path: Cargo reads a relative one from the opentui-sys crate’s directory.

The bindings match one exact opentui.h. The build fails when the library’s header differs from the one the bindings were generated from. After a header change, run bun run generate:abi in the core package.

On Linux and macOS the crates link the static library by default, through the default static feature, so an app needs no OpenTUI file at run time. With default-features = false (or --no-default-features) they link the shared library instead. Cargo merges features across the app’s graph, so every crate in it that depends on opentui must turn the defaults off. Windows links opentui.dll with or without the feature: ship opentui.dll beside the app’s .exe. See C for the native artifacts and the targets they support.

Changes0.9.0
0.9.0
rust: The experimental crates opentui-sys, opentui, and opentui-ssh moved into the OpenTUI repository and are not published, so depend on them by Git revision. On Linux and macOS they link the static library by default; for the shared library, turn off default features in every crate that depends on them. On Windows, ship opentui.dll beside the app. (#1946, #1949, #1984, #1992)
rust: Markdown parses with the native engine and follows CommonMark and GFM, so rules and setext headings can render differently. Its code fences now wrap by word; a standalone Code still does not wrap. Input::max_length counts UTF-16 units, as Core does. (#2049, #2089, #2017)
rust: An app’s priority-0 highlight paints under syntax colors. Textareas that share one style table share one syntax theme: the one set last on any of them. After clear_highlights, a theme change brings syntax colors back only at the next edit. (#2026, #2030)
rust: On Unix, Ctrl+Z stops the app’s whole process group, and a suspend or release drops the rest of the input from that read. On Windows, Host::release hands the terminal to its task as on Unix. ScrollbackSurface::commit_rows fails after a change of width or width method until the surface renders again. (#1950, #1958, #2043, #2082)
rust: App::on_animation_tick and Widget::on_animation_tick run animations on Core’s shared clock, so animations with equal rates tick in the same frames. An animation bound to a widget parks while the widget is hidden, detached, or clipped out of view. AnimationTick::follow adds a widget to an animation, and Widget::downgrade returns a WeakWidget that does not keep the widget alive. (#2120, #2134)
rust: ScrollBox::on_scroll reports each move of a scroll box once, with its position and cause. (#2129)
rust: Code::finish() frees the parse tree of a view whose text stopped growing, and the next change parses again. A transcript with 100 written files uses 36 MB less heap. (#2126)
rust: Code, Diff, and Textarea views no longer wrap their text at width 0 before their first frame, and Code views and Diff rows share their theme and signs instead of copying them. 100 Code views mounted before their first draw take 20 MB instead of 438 MB. (#2124, #2123)
rust: Timers, deferred tasks, and quit() calls that a frame starts run on time in an otherwise idle app. Before, they waited for the next unrelated wakeup, so a headless app could fail to end. (#2118)
rust: A split-footer app whose terminal does not answer the cursor-position query sleeps between startup replies instead of using a full core for five seconds. (#2122)
rust: An app that ends with deferred tasks, timers, handlers, or spawned tasks still queued no longer panics or hangs in teardown. (#2132, #2120)