Simon Klee 3ab0b954a1 core: measure edit text without an edit buffer
`EditBuffer.measureText(text)` returns the editor offset at the end of a
text: its cells under the buffer's width method, tab width, and control
notation, plus one per line break. The result depends only on the text
and those three settings, but an app needed a live native `EditBuffer`
to get it. opencode passed a measure closure into otherwise pure helpers,
kept a spare detached `EditBuffer` for when no textarea was mounted, and
built a renderer and a textarea in tests only to measure strings.

`measureEditText(text, { widthMethod, tabWidth })` returns what
`measureText()` and the end offset after `setText()` give on an edit
buffer with those settings, without a buffer or an owner. `tabWidth`
defaults to the tab width of a new edit buffer and rounds as
`setTabWidth()` does. It calls the new `ot_edit_measure_text(context,
bytes, byte_count, width_method, tab_width, out_width)`. That entry point
checks the width method as `ot_unicode_create` does, rejects a tab width
over 255 and rounds it as `ot_edit_buffer_set_tab_width` does, applies
the byte limit and UTF-8 check of `set_text`, and leaves the output
unchanged on failure. Tab width 0 rounds to the tab width of a new
buffer, so TypeScript passes 0 for the default and keeps no second copy
of it.

Both entry points share one native function, `Context.measureEditText`.
It adds the width of each line between LF, CR, and CRLF, as
`textToSegments` splits them, and one per break. It does not build
segments, so neither entry point allocates any more. `validateTextBytes`
takes the tab width instead of a buffer.

Each native call needs a Context, so the library owns one for these
measures: `FFIRenderLib` creates it on the first call and releases it in
`dispose()`, as it does for audio engines. A module-level
`ResourceContext` would be smaller, but `dispose()` refuses while any
other Context is live, so one measure would make the public
`resolveRenderLib().dispose()` fail with no way to release that Context.
No test or tool otherwise checks for leaked Contexts. Creating the
Context and measuring need no Yoga mutation and use read admission, so a
measure works in input handlers, during render, and in a layout
callback, as `EditBuffer.measureText()` does.

A differential table in `edit-buffer.test.ts` runs under Bun and Node.
For every public width method and the tab widths default, 0, 1, 3, 8,
and 255, it measures C0 controls, DEL, C1, tabs, CJK with a combining
mark, ZWJ, skin-tone, and flag emoji, CR, LF, CRLF, mixed lines, a CRLF
across the 16-byte blocks of `findLineBreaks` before a long line, and the
empty string. `measureEditText()`, `measureText()`, and the end offset
after `setText()` agree on each. New rows in the native malformed-argument
table cover a null span, malformed UTF-8, an unknown width method, tab
width 256, a null output, and one byte over the limit; one byte less
measures the widest tabs within u32. The dispose test measures first in
a Yoga callback, keeps the measure Context through a refused dispose,
and releases it in the next. Hidden controls, CRLF as two breaks, LF-only
breaks, no break count, no tab rounding, a wrong default, each removed
argument check, write admission, an `assertMutable()` guard, and each
dispose change make a test fail.
2026-10-12 08:42:16 +02:00
2026-10-11 22:17:57 +02:00
2025-07-21 17:47:38 +02:00
2026-03-23 15:59:52 +01:00
2026-10-11 22:17:57 +02:00
2025-08-12 12:01:55 +02:00

OpenTUI logo

npm version Core build status

OpenTUI is a library to build terminal user interfaces.

  • It is written in Zig.
  • You write TypeScript directly or through React and Solid.
  • You arrange boxes and text with flexbox.
  • You add selects, inputs, and scroll boxes with keyboard and mouse controls.
  • You can play sounds, show images, and render 3D graphics.

OpenCode uses OpenTUI in production for millions of users.

Website | Documentation | Packages

Packages

  • @opentui/core - Native terminal UI library with TypeScript bindings.
  • @opentui/react - React renderer for building terminal user interfaces with OpenTUI Core.
  • @opentui/solid - Solid renderer for building terminal user interfaces with OpenTUI Core.
  • @opentui/keymap - Key binding and command routing library for terminal and browser applications.
  • @opentui/qrcode - QR code encoder and renderable with React and Solid surfaces.
  • @opentui/three - Three.js WebGPU renderer for OpenTUI.
  • @opentui/ssh - SSH server integration for OpenTUI applications.

Private packages

  • @opentui/native - Private workspace with the Zig implementation, native tests, and benchmarks.
  • @opentui/examples - Private workspace with example applications and a standalone examples executable.
  • @opentui/web - Private workspace with the documentation website and the AI agent skill.

AI agent skill

Install the OpenTUI documentation as a skill for your AI coding assistant with npx skills:

npx skills add https://opentui.com --skill opentui

Add -g to install the skill globally. The skill matches the latest release, and npx skills update moves it to each new release. Install from https://opentui.com/docs/next to follow the main branch. A skill installed with npx skills add anomalyco/opentui follows the main branch; run the command above to switch it to releases.

Development

Development requires Bun 1.4.1 or later and Zig 0.16.0.

See AGENTS.md for engineering, tooling, and verification conventions.

bun install

Run package scripts from their package directory. Run bun run build from the repository root after you change native code or cross-package build outputs. Ordinary TypeScript changes do not require a root build.

Run repository tests and static checks from the repository root:

bun run test
bun run fmt:check
bun run lint

To link the source packages into a Bun-managed project, install that project's dependencies, then run ./scripts/link-opentui-dev.sh /path/to/project from the repository root. Add --react or --solid when needed.

Contributing

OpenTUI is open for contributions.

License

OpenTUI is licensed under the MIT License.

S
Description
No description provided
Readme MIT
75 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 63.1%
Zig 27.8%
MDX 6.1%
C 1%
Astro 0.8%
Other 1.1%