- Set diffAlgorithm to 'legacy' for Monaco DiffEditor - Add maxComputationTime of 10s to avoid UI freeze on huge files This addresses the issue where sessions with large JSON files (50k-100k+ lines) would cause the UI to freeze. The 'legacy' algorithm is faster than 'advanced' for large files, similar to VSCode's workaround for the same issue. See: https://github.com/microsoft/vscode/issues/184037
137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
|
import { loadMonaco } from "../../lib/monaco/setup"
|
|
import { getOrCreateTextModel } from "../../lib/monaco/model-cache"
|
|
import { inferMonacoLanguageId } from "../../lib/monaco/language"
|
|
import { ensureMonacoLanguageLoaded } from "../../lib/monaco/setup"
|
|
import { useTheme } from "../../lib/theme"
|
|
|
|
interface MonacoDiffViewerProps {
|
|
scopeKey: string
|
|
path: string
|
|
before: string
|
|
after: string
|
|
viewMode?: "split" | "unified"
|
|
contextMode?: "expanded" | "collapsed"
|
|
wordWrap?: "on" | "off"
|
|
}
|
|
|
|
export function MonacoDiffViewer(props: MonacoDiffViewerProps) {
|
|
const { isDark } = useTheme()
|
|
let host: HTMLDivElement | undefined
|
|
|
|
let diffEditor: any = null
|
|
let monaco: any = null
|
|
const [ready, setReady] = createSignal(false)
|
|
|
|
const disposeEditor = () => {
|
|
try {
|
|
diffEditor?.setModel(null as any)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
try {
|
|
diffEditor?.dispose()
|
|
} catch {
|
|
// ignore
|
|
}
|
|
diffEditor = null
|
|
}
|
|
|
|
onMount(() => {
|
|
let cancelled = false
|
|
void (async () => {
|
|
monaco = await loadMonaco()
|
|
if (cancelled) return
|
|
if (!host || !monaco) return
|
|
|
|
monaco.editor.setTheme(isDark() ? "vs-dark" : "vs")
|
|
diffEditor = monaco.editor.createDiffEditor(host, {
|
|
readOnly: true,
|
|
automaticLayout: true,
|
|
renderSideBySide: true,
|
|
renderSideBySideInlineBreakpoint: 0,
|
|
renderMarginRevertIcon: false,
|
|
minimap: { enabled: false },
|
|
scrollBeyondLastLine: false,
|
|
renderWhitespace: "selection",
|
|
fontSize: 13,
|
|
wordWrap: props.wordWrap === "on" ? "on" : "off",
|
|
glyphMargin: false,
|
|
folding: false,
|
|
// Keep enough gutter space so unified diffs don't overlap `+`/`-` markers.
|
|
lineNumbersMinChars: 4,
|
|
lineDecorationsWidth: 12,
|
|
// Use legacy diff algorithm for better performance with large files
|
|
// See: https://github.com/microsoft/vscode/issues/184037
|
|
diffAlgorithm: "legacy",
|
|
// Limit computation time to avoid freezing on large files
|
|
maxComputationTime: 10000,
|
|
})
|
|
|
|
setReady(true)
|
|
})()
|
|
|
|
onCleanup(() => {
|
|
cancelled = true
|
|
setReady(false)
|
|
disposeEditor()
|
|
})
|
|
})
|
|
|
|
createEffect(() => {
|
|
if (!ready() || !monaco || !diffEditor) return
|
|
monaco.editor.setTheme(isDark() ? "vs-dark" : "vs")
|
|
})
|
|
|
|
createEffect(() => {
|
|
if (!ready() || !monaco || !diffEditor) return
|
|
const viewMode = props.viewMode === "unified" ? "unified" : "split"
|
|
const contextMode = props.contextMode === "collapsed" ? "collapsed" : "expanded"
|
|
const wordWrap = props.wordWrap === "on" ? "on" : "off"
|
|
|
|
diffEditor.updateOptions({
|
|
renderSideBySide: viewMode === "split",
|
|
renderSideBySideInlineBreakpoint: 0,
|
|
hideUnchangedRegions:
|
|
contextMode === "collapsed"
|
|
? { enabled: true }
|
|
: { enabled: false },
|
|
wordWrap,
|
|
})
|
|
|
|
try {
|
|
diffEditor.getOriginalEditor?.()?.updateOptions?.({ wordWrap })
|
|
} catch {
|
|
// ignore
|
|
}
|
|
|
|
try {
|
|
diffEditor.getModifiedEditor?.()?.updateOptions?.({ wordWrap })
|
|
} catch {
|
|
// ignore
|
|
}
|
|
})
|
|
|
|
createEffect(() => {
|
|
if (!ready() || !monaco || !diffEditor) return
|
|
const languageId = inferMonacoLanguageId(monaco, props.path)
|
|
const beforeKey = `${props.scopeKey}:diff:${props.path}:before`
|
|
const afterKey = `${props.scopeKey}:diff:${props.path}:after`
|
|
|
|
const original = getOrCreateTextModel({ monaco, cacheKey: beforeKey, value: props.before, languageId })
|
|
const modified = getOrCreateTextModel({ monaco, cacheKey: afterKey, value: props.after, languageId })
|
|
diffEditor.setModel({ original, modified })
|
|
|
|
void ensureMonacoLanguageLoaded(languageId).then(() => {
|
|
try {
|
|
monaco.editor.setModelLanguage(original, languageId)
|
|
monaco.editor.setModelLanguage(modified, languageId)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
})
|
|
})
|
|
|
|
return <div class="monaco-viewer" ref={host} />
|
|
}
|