Skip to content

Expose core unicode display-width to plugins (charWidth/stringWidth)#2401

Merged
sinelaw merged 1 commit into
sinelaw:masterfrom
Agrejus:agrejus/core-unicode-width
Jun 19, 2026
Merged

Expose core unicode display-width to plugins (charWidth/stringWidth)#2401
sinelaw merged 1 commit into
sinelaw:masterfrom
Agrejus:agrejus/core-unicode-width

Conversation

@Agrejus

@Agrejus Agrejus commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What this adds

A single source of truth for terminal display width and two plugin APIs built on it:

  • fresh-core::display_width::{char_width, str_width} — backed by the unicode-width crate.
  • editor.charWidth(codePoint: number): number — width of one code point (0 for control/zero-width, 2 for CJK/fullwidth and most emoji, else 1).
  • editor.stringWidth(text: string): number — width of a whole string.

Why

This directly addresses @sinelaw's review note on #2325:

"This should be replaced by a core function exposed to plugins, and since we already do unicode width in the rust core, the implementation should use the same underlying unicode logic we have in the rust core instead of duplicating it."

Plugins otherwise have to re-derive their own Unicode width tables (e.g. markdown_compose's hand-rolled charDisplayWidth), which inevitably drift from how the editor actually measures cells. With these APIs a plugin measures width with the exact same logic the renderer uses for layout and cursor positioning, so column widths, wrapping, and alignment agree by construction.

Design

  • fresh-core::display_width is now canonical. fresh-editor's primitives::display_width re-exports char_width/str_width from it (its byte↔column helpers and the DisplayWidth trait are unchanged), so there is one implementation rather than two copies.
  • The width crate had to live in fresh-core (not fresh-editor) because the plugin runtime — fresh-plugin-runtime, which fresh-editor depends on, not the reverse — needs to call it. The runtime invokes the core function directly (pure, no command round-trip), matching the existing synchronous backend methods.
  • fresh.d.ts updated with the two methods; ts_export API-surface list updated. (Only the two additions are in fresh.d.ts — I deliberately avoided a full regenerate, which would have pulled in unrelated pre-existing drift between the committed file and the generator.)

Risk

Low. Purely additive APIs; the editor's own width behavior is unchanged (the re-export is the identical unicode-width call it already made).

Tests / CI (all green locally)

fmt, clippy --all-features --all-targets, doc (nightly), schema diff, check --no-default-features --features runtime, plus unit tests for the new core module, the editor re-export, and the ts_export API-present / generated-.d.ts-validates tests (now covering charWidth/stringWidth).


Split out of #2325 (closed) as one of the non-graphics pieces. Independent of the other splits — branches from master. The follow-up markdown-compose PR will consume these to delete its duplicated charDisplayWidth table.

…h/stringWidth)

Adds a single source of truth for terminal display width in `fresh-core`
(`display_width::{char_width, str_width}`, backed by the `unicode-width`
crate) and exposes it to plugins as `editor.charWidth(codePoint)` and
`editor.stringWidth(text)`.

Why: plugins currently have to re-derive their own Unicode width tables (e.g.
markdown_compose's hand-rolled `charDisplayWidth`), which inevitably drift from
how the editor itself measures cells. With this, a plugin measures width with
the exact same logic the renderer uses for layout and cursor positioning — so
column widths, wrapping, and alignment agree by construction.

- `fresh-core::display_width` is now canonical; `fresh-editor`'s
  `primitives::display_width` re-exports `char_width`/`str_width` from it
  (its byte/column helpers and the `DisplayWidth` trait are unchanged), so
  there's one implementation, not two.
- The plugin runtime calls the core function directly (pure, no command
  round-trip), matching the existing synchronous backend methods.

No behavior change to the editor; this only adds APIs. The follow-up
markdown-compose PR will use these to delete its duplicated width table
(addresses @sinelaw's review note on sinelaw#2325).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sinelaw sinelaw merged commit 2e979bd into sinelaw:master Jun 19, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants