Skip to content

feat(rust): update cycle, node methods, commands, transforms and listeners - #119

Merged
tmm merged 1 commit into
mainfrom
rust-update
Nov 14, 2026
Merged

tmm merged 1 commit into
mainfrom
rust-update

Conversation

@tmm

@tmm tmm commented Nov 13, 2026

Copy link
Copy Markdown
Member

Stacked on #117 (rust-core); review that one first. This is the first part of M1 in rust/DESIGN.md: the Rust port can now be edited, not only parsed and written.

What changes

  • Update cycle, ported line by line from LexicalUpdates.ts, LexicalNormalization.ts and LexicalGC.ts:
    • editor.update, with nested and queued updates and tags;
    • normalization of adjacent simple text, node transforms (leaves first, root last) and garbage collection of detached nodes;
    • the selection check and discarding a failed update;
    • text content and update listeners;
    • commands: createCommand, registerCommand with the five priorities, dispatchCommand;
    • setEditorState, which marks a parsed state's nodes dirty so normalization and transforms run on it.
  • getWritable marks nodes dirty in JS's order, which is the order update listeners see. The Rc's uniqueness stands in for _cloneNotNeeded, and the dirty sets are insertion-ordered like JS's Map and Set.
  • Node methods with UTF-16 offsets: remove, replace, insertBefore/insertAfter, select*, splice (with selection repair), append, clear, the element setters, splitText, mergeWithSibling, spliceText, setTextContent and the text setters and toggles, plus the TabNode and RootNode overrides. Also the selection model, and the helpers node methods use to keep it valid.
  • Conformance: an interpreter for ops.ts's script language replays scripts/*. A script that needs selection editing or carets is counted as waiting on them, never as passing.

A headless editor has no event loop, so a non-discrete update commits at the next read or discrete update rather than in a microtask (DESIGN.md).

How it was checked

In rust/: cargo fmt --check, clippy -D warnings, cargo test (including a new test that doctored script vectors fail), rustdoc -D warnings, cargo +1.88 check.

Vectors Cases Match JS Waiting on selection editing or carets Fail
scripts/updates 12 12 0 0
scripts/commands 3 3 0 0
scripts/listeners 3 2 1 0
scripts/transforms 5 4 1 0
scripts/selection 19 2 17 0
scripts/caret 5 0 5 0

meta, serialization/core and property/documents are unchanged.

I also changed eight fields across the script vectors (state, compact JSON, selection, error flag, splitText keys, dirty flags, tags, handler order); each change made its case fail. setEditorState was run against the JS package on a document with mergeable, empty and formatted text, with and without a transform, and on an empty state. Listener calls, transform calls, errors and JSON were identical.

No changeset: rust/ is not published.

Base automatically changed from rust-core to main November 14, 2026 16:26
…eners

The first part of M1 (rust/DESIGN.md): the core model can now be edited,
not only parsed and written.

- The update cycle, ported line by line from LexicalUpdates.ts:
  editor.update (discrete or committed at the next read), nested and
  queued updates, tags, normalization of adjacent simple text
  (LexicalNormalization.ts), node transforms (leaves first, the root
  last), garbage collection of detached nodes (LexicalGC.ts), the
  selection check, discarding a failed update, and the text content and
  update listeners. Commands: createCommand, registerCommand with the five
  priorities, dispatchCommand. setEditorState, which marks a parsed
  state's nodes dirty so normalization and transforms run on it.
- getWritable marks nodes dirty in JS's order (the order update listeners
  see), with Rc uniqueness standing in for _cloneNotNeeded; the dirty sets
  are insertion-ordered like JS's Map and Set.
- Node methods with UTF-16 offsets: remove, replace, insertBefore,
  insertAfter, select*, splice (with selection repair), append, clear,
  the element setters, splitText, mergeWithSibling, spliceText,
  setTextContent and the text setters and toggles, with TabNode's and
  RootNode's overrides. The selection model and the helpers node methods
  use to keep it valid (LexicalSelection.ts); selection editing and
  carets come next.
- lexical-conformance replays scripts/* with an interpreter of ops.ts's
  script language. A script that needs selection editing or carets is
  counted as waiting on them, never as passing.

Checked in rust/: cargo fmt --check, clippy -D warnings, cargo test
(including new tests that doctored script vectors fail), rustdoc
-D warnings, cargo +1.88 check. Conformance: scripts/updates 12/12,
scripts/commands 3/3, scripts/listeners 2/2 and scripts/transforms 4/4 of
the cases without typing, scripts/selection 2/2 without selection
editing; meta, serialization/core and property/documents unchanged;
0 failures. setEditorState was also checked against the JS package on a
document with mergeable, empty and formatted text, with and without a
transform, and on an empty state: identical listener calls, transform
calls, errors and JSON.

Amp-Thread-ID: https://ampcode.com/threads/T-16d45229-bfa8-417a-a091-d3e3888d830d
@tmm
tmm merged commit 3d909dc into main Nov 14, 2026
20 checks passed
@tmm
tmm deleted the rust-update branch November 14, 2026 16:40
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