Skip to content

Commit c4a27c7

Browse files
authored
fix(web): stabilize Monaco diff cleanup (#78)
Change-Id: Ib013aac3b4954bf8bb93de525d2dac62e70b0886
1 parent 12a42db commit c4a27c7

2 files changed

Lines changed: 88 additions & 43 deletions

File tree

web/src/features/umodel/UModelPage.tsx

Lines changed: 82 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
1+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type MutableRefObject } from 'react'
22
import ReactDOM from 'react-dom'
3-
import Editor, { DiffEditor, useMonaco } from '@monaco-editor/react'
3+
import Editor, { useMonaco } from '@monaco-editor/react'
4+
import type { editor as MonacoEditor } from 'monaco-editor'
45
import * as YAML from 'js-yaml'
56
import {
67
Box,
@@ -1708,58 +1709,96 @@ function SafeDiffEditor({
17081709
modified: string
17091710
}) {
17101711
const monaco = useMonaco()
1711-
const monacoRef = useRef(monaco)
1712-
const modelPathsRef = useRef<Set<string>>(new Set())
1712+
const containerRef = useRef<HTMLDivElement | null>(null)
1713+
const editorRef = useRef<MonacoEditor.IStandaloneDiffEditor | null>(null)
1714+
const originalModelRef = useRef<MonacoEditor.ITextModel | null>(null)
1715+
const modifiedModelRef = useRef<MonacoEditor.ITextModel | null>(null)
17131716
const safeModelKey = encodeURIComponent(modelKey)
17141717
const originalModelPath = `inmemory://umodel-diff/${safeModelKey}.original.json`
17151718
const modifiedModelPath = `inmemory://umodel-diff/${safeModelKey}.modified.json`
17161719

17171720
useEffect(() => {
1718-
monacoRef.current = monaco
1721+
if (!monaco || !containerRef.current) return undefined
1722+
1723+
const diffEditor = monaco.editor.createDiffEditor(containerRef.current, {
1724+
automaticLayout: true,
1725+
fontFamily: 'SF Mono, Fira Code, Consolas, monospace',
1726+
fontSize: 12,
1727+
minimap: { enabled: false },
1728+
originalEditable: false,
1729+
readOnly: true,
1730+
renderMarginRevertIcon: false,
1731+
renderSideBySide: true,
1732+
scrollBeyondLastLine: false,
1733+
wordWrap: 'on',
1734+
})
1735+
editorRef.current = diffEditor
1736+
1737+
return () => {
1738+
editorRef.current = null
1739+
diffEditor.setModel(null)
1740+
diffEditor.dispose()
1741+
1742+
const originalModel = originalModelRef.current
1743+
const modifiedModel = modifiedModelRef.current
1744+
originalModelRef.current = null
1745+
modifiedModelRef.current = null
1746+
scheduleDiffModelDispose(originalModel, originalModelRef, modifiedModelRef)
1747+
scheduleDiffModelDispose(modifiedModel, originalModelRef, modifiedModelRef)
1748+
}
17191749
}, [monaco])
17201750

17211751
useEffect(() => {
1722-
modelPathsRef.current.add(originalModelPath)
1723-
modelPathsRef.current.add(modifiedModelPath)
1724-
}, [modifiedModelPath, originalModelPath])
1752+
if (!monaco || !editorRef.current) return
17251753

1726-
useEffect(() => {
1727-
return () => {
1728-
const monacoInstance = monacoRef.current
1729-
const modelPaths = Array.from(modelPathsRef.current)
1730-
window.setTimeout(() => {
1731-
for (const path of modelPaths) {
1732-
const model = monacoInstance?.editor.getModel(monacoInstance.Uri.parse(path))
1733-
model?.dispose()
1734-
}
1735-
}, 0)
1754+
const originalUri = monaco.Uri.parse(originalModelPath)
1755+
const modifiedUri = monaco.Uri.parse(modifiedModelPath)
1756+
const originalModel = upsertMonacoModel(monaco, originalUri, original)
1757+
const modifiedModel = upsertMonacoModel(monaco, modifiedUri, modified)
1758+
const previousOriginal = originalModelRef.current
1759+
const previousModified = modifiedModelRef.current
1760+
1761+
editorRef.current.setModel({ original: originalModel, modified: modifiedModel })
1762+
originalModelRef.current = originalModel
1763+
modifiedModelRef.current = modifiedModel
1764+
1765+
if (previousOriginal !== originalModel) {
1766+
scheduleDiffModelDispose(previousOriginal, originalModelRef, modifiedModelRef)
17361767
}
1737-
}, [])
1768+
if (previousModified !== modifiedModel) {
1769+
scheduleDiffModelDispose(previousModified, originalModelRef, modifiedModelRef)
1770+
}
1771+
}, [modified, modifiedModelPath, monaco, original, originalModelPath])
17381772

1739-
return (
1740-
<DiffEditor
1741-
original={original}
1742-
modified={modified}
1743-
language="json"
1744-
originalModelPath={originalModelPath}
1745-
modifiedModelPath={modifiedModelPath}
1746-
keepCurrentOriginalModel
1747-
keepCurrentModifiedModel
1748-
theme="vs"
1749-
options={{
1750-
automaticLayout: true,
1751-
fontFamily: 'SF Mono, Fira Code, Consolas, monospace',
1752-
fontSize: 12,
1753-
minimap: { enabled: false },
1754-
originalEditable: false,
1755-
readOnly: true,
1756-
renderMarginRevertIcon: false,
1757-
renderSideBySide: true,
1758-
scrollBeyondLastLine: false,
1759-
wordWrap: 'on',
1760-
}}
1761-
/>
1762-
)
1773+
return <div className="ume-diff-editor-host" ref={containerRef} />
1774+
}
1775+
1776+
function upsertMonacoModel(
1777+
monaco: NonNullable<ReturnType<typeof useMonaco>>,
1778+
uri: ReturnType<NonNullable<ReturnType<typeof useMonaco>>['Uri']['parse']>,
1779+
value: string,
1780+
) {
1781+
const model = monaco.editor.getModel(uri)
1782+
if (model) {
1783+
if (model.getValue() !== value) model.setValue(value)
1784+
return model
1785+
}
1786+
return monaco.editor.createModel(value, 'json', uri)
1787+
}
1788+
1789+
function scheduleDiffModelDispose(
1790+
model: MonacoEditor.ITextModel | null,
1791+
originalModelRef: MutableRefObject<MonacoEditor.ITextModel | null>,
1792+
modifiedModelRef: MutableRefObject<MonacoEditor.ITextModel | null>,
1793+
) {
1794+
if (!model) return
1795+
1796+
window.setTimeout(() => {
1797+
if (model.isDisposed()) return
1798+
if (originalModelRef.current === model || modifiedModelRef.current === model) return
1799+
if (model.isAttachedToEditor()) return
1800+
model.dispose()
1801+
}, 1_000)
17631802
}
17641803

17651804
function rowToElement(row: Record<string, unknown>): UModelElement {

web/src/features/umodel/umodel.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,6 +3182,12 @@ body.ume-resizing-detail .ume-detail-panel {
31823182
box-shadow: var(--ume-shadow-card);
31833183
}
31843184

3185+
.ume-diff-editor-host {
3186+
min-height: 0;
3187+
height: 100%;
3188+
overflow: hidden;
3189+
}
3190+
31853191
.ume-diff-viewer-title {
31863192
min-width: 0;
31873193
display: grid;

0 commit comments

Comments
 (0)