Skip to content

Commit

Permalink
wrap intervals and ranges extractors in useMemo hook for running them…
Browse files Browse the repository at this point in the history
… only if editor.children is changed
  • Loading branch information
dsvgit committed Feb 7, 2023
1 parent 1eb10c5 commit dfc4239
Showing 1 changed file with 130 additions and 126 deletions.
256 changes: 130 additions & 126 deletions site/examples/code-highlighting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'prismjs/components/prism-python'
import 'prismjs/components/prism-php'
import 'prismjs/components/prism-sql'
import 'prismjs/components/prism-java'
import React, { useCallback, useState } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import {
createEditor,
Node,
Expand Down Expand Up @@ -134,40 +134,41 @@ const useDecorate = (editor: Editor) => {
// set editor.intervalsStarts with set of these intervals start index (for optimization)
const ExtractIntervals = () => {
const editor = useSlate()
const children = editor.children

const intervals = new Map<string, [number, number][]>()
const intervalsStarts = new Map<string, Set<number>>()
let prev = null
// the loop has children.length + 1 iterations in order to close the last interval
for (let i = 0; i < children.length + 1; i++) {
const element = children[i] as Element

if (!prev || !element || prev.type !== element.type) {
if (prev) {
// update the end of an interval
intervals.get(prev.type).slice(-1)[0][1] = i - 1
}

if (element) {
if (!intervals.has(element.type)) {
intervals.set(element.type, [])
useMemo(() => {
const intervals = new Map<string, [number, number][]>()
const intervalsStarts = new Map<string, Set<number>>()
let prev = null
// the loop has children.length + 1 iterations in order to close the last interval
for (let i = 0; i < editor.children.length + 1; i++) {
const element = editor.children[i] as Element

if (!prev || !element || prev.type !== element.type) {
if (prev) {
// update the end of an interval
intervals.get(prev.type).slice(-1)[0][1] = i - 1
}
// push the start of an interval
intervals.get(element.type).push([i, 0])

if (!intervalsStarts.has(element.type)) {
intervalsStarts.set(element.type, new Set())
if (element) {
if (!intervals.has(element.type)) {
intervals.set(element.type, [])
}
// push the start of an interval
intervals.get(element.type).push([i, 0])

if (!intervalsStarts.has(element.type)) {
intervalsStarts.set(element.type, new Set())
}
intervalsStarts.get(element.type).add(i)
}
intervalsStarts.get(element.type).add(i)
}
}

prev = element
}
prev = element
}

editor.intervals = intervals
editor.intervalsStarts = intervalsStarts
editor.intervals = intervals
editor.intervalsStarts = intervalsStarts
}, [editor.children])

return null
}
Expand All @@ -186,134 +187,137 @@ class PrismTokenNode {
const ExtractRanges = () => {
const editor = useSlate()

const ranges = new Map<string, Range[]>()
useMemo(() => {
const ranges = new Map<string, Range[]>()

const intervals = editor.intervals.get(CodeLineType) || [] // get only code line intervals
for (const interval of intervals) {
const firstElement = editor.children[interval[0]] as CodeLineElement
const language = firstElement.language
const intervals = editor.intervals.get(CodeLineType) || [] // get only code line intervals
for (const interval of intervals) {
const firstElement = editor.children[interval[0]] as CodeLineElement
const language = firstElement.language

const text = editor.children
.slice(interval[0], interval[1] + 1)
.map(element => Node.string(element))
.join('\n')
const tokens = Prism.tokenize(text, Prism.languages[language])
const text = editor.children
.slice(interval[0], interval[1] + 1)
.map(element => Node.string(element))
.join('\n')
const tokens = Prism.tokenize(text, Prism.languages[language])

const stack: PrismTokenNode[] = []
const stack: PrismTokenNode[] = []

// fill initial stack with reversed tokens
for (let i = tokens.length - 1; i >= 0; i--) {
stack.push(new PrismTokenNode(tokens[i], null))
}
// fill initial stack with reversed tokens
for (let i = tokens.length - 1; i >= 0; i--) {
stack.push(new PrismTokenNode(tokens[i], null))
}

let startOffset = 0
let index = interval[0]
while (stack.length) {
const currentTokenNode = stack.pop()!
const token = currentTokenNode.token
const length = token.length

if (typeof token !== 'string' && Array.isArray(token.content)) {
// add reversed content (children) tokens to stack
for (let i = token.content.length - 1; i >= 0; i--) {
stack.push(new PrismTokenNode(token.content[i], currentTokenNode))
let startOffset = 0
let index = interval[0]
while (stack.length) {
const currentTokenNode = stack.pop()!
const token = currentTokenNode.token
const length = token.length

if (typeof token !== 'string' && Array.isArray(token.content)) {
// add reversed content (children) tokens to stack
for (let i = token.content.length - 1; i >= 0; i--) {
stack.push(new PrismTokenNode(token.content[i], currentTokenNode))
}
continue
}
continue
}

// split string into lines to be sure that there are no multiline tokens
const str = typeof token === 'string' ? token : (token.content as string)
if (str !== '\n') {
const lines = str.split(/(\n)/)
if (lines.length > 1) {
// if it is multiline token split it add it back to stack each line separately in reversed order
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i]

stack.push(
new PrismTokenNode(
typeof token === 'string'
? line
: new Prism.Token(token.type, line, token.alias, line),
currentTokenNode
// split string into lines to be sure that there are no multiline tokens
const str =
typeof token === 'string' ? token : (token.content as string)
if (str !== '\n') {
const lines = str.split(/(\n)/)
if (lines.length > 1) {
// if it is multiline token split it add it back to stack each line separately in reversed order
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i]

stack.push(
new PrismTokenNode(
typeof token === 'string'
? line
: new Prism.Token(token.type, line, token.alias, line),
currentTokenNode
)
)
)
}
continue
}
} else {
// if it is line break reset startOffset and increment index
index++
startOffset = 0
continue
}
} else {
// if it is line break reset startOffset and increment index
index++
startOffset = 0
continue
}

// calculate endOffset
const endOffset = startOffset + length
// calculate endOffset
const endOffset = startOffset + length

// create types set based on token's and all ancestors' types
const types = new Set<string>(['token'])
let tokenNodeCursor = currentTokenNode
while (tokenNodeCursor) {
const { token } = tokenNodeCursor
// create types set based on token's and all ancestors' types
const types = new Set<string>(['token'])
let tokenNodeCursor = currentTokenNode
while (tokenNodeCursor) {
const { token } = tokenNodeCursor

if (typeof token !== 'string') {
const type = token.type
const aliasArray = Array.isArray(token.alias)
? token.alias
: [token.alias]
if (typeof token !== 'string') {
const type = token.type
const aliasArray = Array.isArray(token.alias)
? token.alias
: [token.alias]

const isTag = type === 'tag' || aliasArray.includes('tag')
const isTag = type === 'tag' || aliasArray.includes('tag')

if (types.has('script') && isTag) {
// skip "tag" type and following, if types already has "script" type
// matters for correct jsx, tsx attributes highlighting
break
}
if (types.has('script') && isTag) {
// skip "tag" type and following, if types already has "script" type
// matters for correct jsx, tsx attributes highlighting
break
}

types.add(type)
types.add(type)

for (const alias of aliasArray) {
alias && types.add(alias)
for (const alias of aliasArray) {
alias && types.add(alias)
}
}

tokenNodeCursor = tokenNodeCursor.parent
}

tokenNodeCursor = tokenNodeCursor.parent
}
// fill ranges map for decorate function
if (types.size) {
const key = ReactEditor.findKey(editor, editor.children[index])
if (!ranges.has(key.id)) {
ranges.set(key.id, [])
}

// fill ranges map for decorate function
if (types.size) {
const key = ReactEditor.findKey(editor, editor.children[index])
if (!ranges.has(key.id)) {
ranges.set(key.id, [])
}
const anchor: Point = {
basePath: [index, 0],
path: [],
offset: startOffset,
}
const focus: Point = {
basePath: [index, 0],
path: [],
offset: endOffset,
}

const anchor: Point = {
basePath: [index, 0],
path: [],
offset: startOffset,
}
const focus: Point = {
basePath: [index, 0],
path: [],
offset: endOffset,
}
const range: Range = { anchor, focus }

const range: Range = { anchor, focus }
for (const type of types) {
range[type] = true
}

for (const type of types) {
range[type] = true
ranges.get(key.id)!.push(range)
}

ranges.get(key.id)!.push(range)
// move startOffset to end
startOffset = endOffset
}

// move startOffset to end
startOffset = endOffset
}
}

editor.ranges = ranges
editor.ranges = ranges
}, [editor.children])

return null
}
Expand Down

0 comments on commit dfc4239

Please sign in to comment.