Avoid using isEqual for outline comparison (#16093)

GitOrigin-RevId: 8901f77eb25295882f6563fa2f1835d18d332c59
This commit is contained in:
Alf Eaton 2023-12-05 09:43:15 +00:00 committed by Copybot
parent c4b05f7a73
commit 3f98752986
2 changed files with 65 additions and 49 deletions

View file

@ -1,16 +1,15 @@
import { createPortal } from 'react-dom'
import { useCodeMirrorStateContext } from './codemirror-editor'
import React, { useCallback, useMemo, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import OutlinePane from '../../outline/components/outline-pane'
import { documentOutline } from '../languages/latex/document-outline'
import isValidTeXFile from '../../../main/is-valid-tex-file'
import useScopeValue from '../../../shared/hooks/use-scope-value'
import useScopeEventEmitter from '../../../shared/hooks/use-scope-event-emitter'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { nestOutline } from '../utils/tree-query'
import { nestOutline, Outline } from '../utils/tree-query'
import { ProjectionStatus } from '../utils/tree-operations/projection'
import useEventListener from '../../../shared/hooks/use-event-listener'
import useDeepCompareMemo from '../../../shared/hooks/use-deep-compare-memo'
import useDebounce from '../../../shared/hooks/use-debounce'
const closestSectionLineNumber = (
@ -30,6 +29,39 @@ const closestSectionLineNumber = (
return highestLine
}
type PartialFlatOutline = {
level: number
title: string
line: number
}[]
const outlineChanged = (
a: PartialFlatOutline | undefined,
b: PartialFlatOutline
): boolean => {
if (!a) {
return true
}
if (a.length !== b.length) {
return true
}
for (let i = 0; i < a.length; i++) {
const aItem = a[i]
const bItem = b[i]
if (
aItem.level !== bItem.level ||
aItem.line !== bItem.line ||
aItem.title !== bItem.title
) {
return true
}
}
return false
}
export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
const state = useCodeMirrorStateContext()
const debouncedState = useDebounce(state, 100)
@ -90,28 +122,37 @@ export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
}, [])
)
const outlineStatus = useMemo(
() =>
debouncedState.field(documentOutline, false)?.status ||
ProjectionStatus.Pending,
[debouncedState]
)
const outlineResult = debouncedState.field(documentOutline, false)
const flatOutline = useMemo(() => {
const outlineResult = debouncedState.field(documentOutline, false)
if (outlineResult?.status !== ProjectionStatus.Pending) {
// We have a (potentially partial) outline.
return outlineResult?.items!.map(element => {
// Remove {from, to} to not trip up deep comparison
const { level, title, line } = element
return { level, title, line }
})
// when the outline projection changes, calculate the flat outline
const flatOutline = useMemo<PartialFlatOutline | undefined>(() => {
if (!outlineResult || outlineResult.status === ProjectionStatus.Pending) {
return undefined
}
return undefined
}, [debouncedState])
const outline = useDeepCompareMemo(() => {
return flatOutline ? nestOutline(flatOutline) : []
// We have a (potentially partial) outline.
return outlineResult.items.map(element => {
const { level, title, line } = element
return { level, title, line }
})
}, [outlineResult])
const [outline, setOutline] = useState<Outline[]>([])
const prevFlatOutlineRef = useRef<PartialFlatOutline | undefined>(undefined)
// when the flat outline changes, calculate the nested outline
useEffect(() => {
const prevFlatOutline = prevFlatOutlineRef.current
prevFlatOutlineRef.current = flatOutline
if (flatOutline) {
if (outlineChanged(prevFlatOutline, flatOutline)) {
setOutline(nestOutline(flatOutline))
}
} else {
setOutline([])
}
}, [flatOutline])
const jumpToLine = useCallback(
@ -123,13 +164,6 @@ export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
[goToLineEmitter]
)
const onToggle = useCallback(
isOpen => {
outlineToggledEmitter(isOpen)
},
[outlineToggledEmitter]
)
const highlightedLine = useMemo(
() => closestSectionLineNumber(flatOutline, currentlyHighlightedLine),
[flatOutline, currentlyHighlightedLine]
@ -143,13 +177,13 @@ export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
return createPortal(
<OutlinePane
outline={outline}
onToggle={onToggle}
onToggle={outlineToggledEmitter}
eventTracking={eventTracking}
isTexFile={isTexFile && !binaryFileOpened}
jumpToLine={jumpToLine}
highlightedLine={highlightedLine}
show
isPartial={outlineStatus === ProjectionStatus.Partial}
isPartial={outlineResult?.status === ProjectionStatus.Partial}
/>,
outlineDomElement
)

View file

@ -1,18 +0,0 @@
import { DependencyList, useMemo, useRef } from 'react'
import { isEqual } from 'lodash'
function useDeepCompare(dependencies: DependencyList) {
const ref = useRef<DependencyList>([])
if (!isEqual(ref.current, dependencies)) {
ref.current = dependencies
}
return ref.current
}
export default function useDeepCompareMemo<T>(
factory: () => T,
dependencies: DependencyList
) {
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(factory, useDeepCompare(dependencies))
}