1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-11 17:53:59 +02:00

Avoid transforming dirty paths if the op won't touch them (#4735)

* Avoid transforming dirty paths if the op won't touch them

* Avoid the loop entirely if op doesn't affect path
This commit is contained in:
Steve Marquis
2021-12-18 07:39:09 -08:00
committed by GitHub
parent ccafb6982f
commit e5427dddfc
5 changed files with 61 additions and 12 deletions

View File

@@ -0,0 +1,5 @@
---
'slate': patch
---
Optimize path transforms during normalization

View File

@@ -13,7 +13,7 @@ import {
Text, Text,
Transforms, Transforms,
} from './' } from './'
import { DIRTY_PATHS, FLUSHING } from './utils/weak-maps' import { DIRTY_PATHS, DIRTY_PATH_KEYS, FLUSHING } from './utils/weak-maps'
/** /**
* Create a new Slate `Editor` object. * Create a new Slate `Editor` object.
@@ -42,33 +42,41 @@ export const createEditor = (): Editor => {
RangeRef.transform(ref, op) RangeRef.transform(ref, op)
} }
const set = new Set() const oldDirtyPaths = DIRTY_PATHS.get(editor) || []
const dirtyPaths: Path[] = [] const oldDirtyPathKeys = DIRTY_PATH_KEYS.get(editor) || new Set()
let dirtyPaths: Path[]
let dirtyPathKeys: Set<string>
const add = (path: Path | null) => { const add = (path: Path | null) => {
if (path) { if (path) {
const key = path.join(',') const key = path.join(',')
if (!set.has(key)) { if (!dirtyPathKeys.has(key)) {
set.add(key) dirtyPathKeys.add(key)
dirtyPaths.push(path) dirtyPaths.push(path)
} }
} }
} }
const oldDirtyPaths = DIRTY_PATHS.get(editor) || [] if (Path.operationCanTransformPath(op)) {
const newDirtyPaths = getDirtyPaths(op) dirtyPaths = []
dirtyPathKeys = new Set()
for (const path of oldDirtyPaths) { for (const path of oldDirtyPaths) {
const newPath = Path.transform(path, op) const newPath = Path.transform(path, op)
add(newPath) add(newPath)
} }
} else {
dirtyPaths = oldDirtyPaths
dirtyPathKeys = oldDirtyPathKeys
}
const newDirtyPaths = getDirtyPaths(op)
for (const path of newDirtyPaths) { for (const path of newDirtyPaths) {
add(path) add(path)
} }
DIRTY_PATHS.set(editor, dirtyPaths) DIRTY_PATHS.set(editor, dirtyPaths)
DIRTY_PATH_KEYS.set(editor, dirtyPathKeys)
Transforms.transform(editor, op) Transforms.transform(editor, op)
editor.operations.push(op) editor.operations.push(op)
Editor.normalize(editor) Editor.normalize(editor)

View File

@@ -19,6 +19,7 @@ import {
} from '..' } from '..'
import { import {
DIRTY_PATHS, DIRTY_PATHS,
DIRTY_PATH_KEYS,
NORMALIZING, NORMALIZING,
PATH_REFS, PATH_REFS,
POINT_REFS, POINT_REFS,
@@ -979,13 +980,26 @@ export const Editor: EditorInterface = {
return DIRTY_PATHS.get(editor) || [] return DIRTY_PATHS.get(editor) || []
} }
const getDirtyPathKeys = (editor: Editor) => {
return DIRTY_PATH_KEYS.get(editor) || new Set()
}
const popDirtyPath = (editor: Editor): Path => {
const path = getDirtyPaths(editor).pop()!
const key = path.join(',')
getDirtyPathKeys(editor).delete(key)
return path
}
if (!Editor.isNormalizing(editor)) { if (!Editor.isNormalizing(editor)) {
return return
} }
if (force) { if (force) {
const allPaths = Array.from(Node.nodes(editor), ([, p]) => p) const allPaths = Array.from(Node.nodes(editor), ([, p]) => p)
const allPathKeys = new Set(allPaths.map(p => p.join(',')))
DIRTY_PATHS.set(editor, allPaths) DIRTY_PATHS.set(editor, allPaths)
DIRTY_PATH_KEYS.set(editor, allPathKeys)
} }
if (getDirtyPaths(editor).length === 0) { if (getDirtyPaths(editor).length === 0) {
@@ -1026,7 +1040,7 @@ export const Editor: EditorInterface = {
`) `)
} }
const dirtyPath = getDirtyPaths(editor).pop()! const dirtyPath = popDirtyPath(editor)
// If the node doesn't exist in the tree, it does not need to be normalized. // If the node doesn't exist in the tree, it does not need to be normalized.
if (Node.has(editor, dirtyPath)) { if (Node.has(editor, dirtyPath)) {

View File

@@ -34,6 +34,7 @@ export interface PathInterface {
} }
) => Path[] ) => Path[]
next: (path: Path) => Path next: (path: Path) => Path
operationCanTransformPath: (operation: Operation) => boolean
parent: (path: Path) => Path parent: (path: Path) => Path
previous: (path: Path) => Path previous: (path: Path) => Path
relative: (path: Path, ancestor: Path) => Path relative: (path: Path, ancestor: Path) => Path
@@ -291,6 +292,26 @@ export const Path: PathInterface = {
return path.slice(0, -1).concat(last + 1) return path.slice(0, -1).concat(last + 1)
}, },
/**
* Returns whether this operation can affect paths or not. Used as an
* optimization when updating dirty paths during normalization
*
* NOTE: This *must* be kept in sync with the implementation of 'transform'
* below
*/
operationCanTransformPath(operation: Operation): boolean {
switch (operation.type) {
case 'insert_node':
case 'remove_node':
case 'merge_node':
case 'split_node':
case 'move_node':
return true
default:
return false
}
},
/** /**
* Given a path, return a new path referring to the parent node above it. * Given a path, return a new path referring to the parent node above it.
*/ */

View File

@@ -1,6 +1,7 @@
import { Editor, Path, PathRef, PointRef, RangeRef } from '..' import { Editor, Path, PathRef, PointRef, RangeRef } from '..'
export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap() export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap()
export const DIRTY_PATH_KEYS: WeakMap<Editor, Set<string>> = new WeakMap()
export const FLUSHING: WeakMap<Editor, boolean> = new WeakMap() export const FLUSHING: WeakMap<Editor, boolean> = new WeakMap()
export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap() export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap()
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap() export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()