feat(editor): add threaded block map with O(1) lookups#2195
Draft
christianhg wants to merge 1 commit intomainfrom
Draft
feat(editor): add threaded block map with O(1) lookups#2195christianhg wants to merge 1 commit intomainfrom
christianhg wants to merge 1 commit intomainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
c55e32d to
a1fbd5d
Compare
a1fbd5d to
a25ed38
Compare
a25ed38 to
f40079d
Compare
…t navigation
Add BlockMap data structure that maps block _key to BlockMapEntry with
node reference, index, and prev/next pointers for O(1) document-order
navigation. This replaces index-based lookups across the editor.
Data structure:
- BlockMapEntry: { node, index, prev, next, parent, field }
- BlockMap: Map<string, BlockMapEntry>
- buildBlockMap(): full rebuild from value (mount + structural ops)
- updateBlockMap(): incremental per-operation updates
Migration:
- Add blockMap to PortableTextSlateEditor and EditorSnapshot
- Mark blockIndexMap as @deprecated on both types
- Migrate all selectors: getFocusBlock, getAnchorBlock, getNextBlock,
getPreviousBlock, getSelectedBlocks, getSelectedTextBlocks,
getSelectedValue, getSelectedChildren
- Migrate comparePoints and isOverlappingSelection
- Migrate toSlateRange to use blockMap.get(key).index
- Migrate all operations to use blockMap for block lookups
- Migrate render.element.tsx, create-editable-api.ts,
selection-state-context.tsx, range-decorations-machine.ts
- Update create-test-snapshot.ts and to-slate-range.test.ts
Container support (parent/field on BlockMapEntry) is scaffolded but
getContainerField returns null for now — flat documents only.
Container awareness will be added when containerTypes lands on schema.
All 317 tests pass, full monorepo types clean (39/39).
f40079d to
f420e6d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add threaded block map with O(1) lookups and linked-list navigation
What
Introduces a new
BlockMapdata structure that replacesblockIndexMapas the primary way to look up and navigate blocks. Each entry stores its index,prev/nextpointers (threaded in document reading order), andparent/fieldpointers for future container support.The old
blockIndexMapis kept populated and marked@deprecatedfor backwards compatibility.Why
blockIndexMapis a flatMap<string, number>that maps_keyto array index. It has two problems:No navigation. Getting the next or previous block requires walking the value array. Selectors like
getNextBlockandgetPreviousBlockhad to scan by index arithmetic.Flat only. Keys are bare
_keystrings, which are only unique within their sibling array. When containers introduce nested blocks, two blocks at different depths could share a_key, making the map ambiguous.The block map solves both: O(1) lookup by key, O(1) next/prev via linked list, and path-based keys that are unique at any depth.
How it works
Each
BlockMapEntrystores:Block nodes are not stored on the entry. Access the node via
context.value[entry.index]. One source of truth, no data duplication.Key encoding: Top-level blocks use their bare
_keyas the map key. Nested blocks (when containers land) will use a length-prefixed path encoding:parentMapKey/7:content/6:block1. Length prefixes prevent ambiguity since_keycan contain any character.Surgical updates: The map is updated per Slate operation instead of being rebuilt from scratch:
insert_node(block-level)remove_node(block-level)split_node(block-level)merge_node(block-level)set_node(key change)move_nodeWhat changed
New files:
block-map.ts:BlockMap,BlockMapEntry,blockMapKey(),buildBlockMap(),updateBlockMap()block-map.test.ts: 36 tests covering build, surgical updates, key encoding, and collision resistanceMigrated to block map (13 files):
getFocusBlock,getAnchorBlock,getNextBlock,getPreviousBlock,getSelectedBlocks,getSelectedChildren,getSelectedTextBlocks,getSelectedValueto-slate-range.ts: block index lookupcreate-editable-api.ts:focusBlock,getBlockoperation.child.unset.ts: block lookupslate-plugin.update-value.ts: callsupdateBlockMapafter each operationslate-plugin.unique-keys.ts: duplicate key detectionTests
36 unit tests covering:
blockMapKey: top-level, nested, deeply nested, special characters, collision resistancebuildBlockMap: empty, single block, multiple blocks, mixed types, rebuild clears stateAll existing tests continue to pass (353 total).