mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
perf(editor): debounce markdown doc link decorations (#1830)
Debounce Monaco markdown doc-link decoration refreshes so rapid content changes coalesce into one full-model scan, while preserving immediate initial render and cleanup behavior.
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getMarkdownDocLinkDecorationRanges } from './monaco-markdown-doc-link-decorations'
|
||||
import type { editor } from 'monaco-editor'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
createMarkdownDocLinkDecorationController,
|
||||
getMarkdownDocLinkDecorationRanges,
|
||||
MARKDOWN_DOC_LINK_DECORATION_REFRESH_DELAY_MS
|
||||
} from './monaco-markdown-doc-link-decorations'
|
||||
|
||||
describe('getMarkdownDocLinkDecorationRanges', () => {
|
||||
it('returns Monaco ranges for valid doc links', () => {
|
||||
@@ -30,3 +35,92 @@ describe('getMarkdownDocLinkDecorationRanges', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('createMarkdownDocLinkDecorationController', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('debounces full-model decoration rebuilds during rapid content changes', () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
let modelValue = '[[initial.md]]'
|
||||
let contentListener = (): void => {}
|
||||
const set = vi.fn()
|
||||
const clear = vi.fn()
|
||||
const dispose = vi.fn()
|
||||
|
||||
const editorInstance = {
|
||||
createDecorationsCollection: () => ({ set, clear }),
|
||||
getModel: () => ({ getValue: () => modelValue }),
|
||||
onDidChangeModelContent: (listener: () => void) => {
|
||||
contentListener = listener
|
||||
return { dispose }
|
||||
}
|
||||
} as unknown as editor.IStandaloneCodeEditor
|
||||
|
||||
const controller = createMarkdownDocLinkDecorationController(editorInstance, () => 'markdown')
|
||||
expect(set).toHaveBeenCalledTimes(1)
|
||||
|
||||
set.mockClear()
|
||||
modelValue = '[[first.md]]'
|
||||
contentListener?.()
|
||||
vi.advanceTimersByTime(MARKDOWN_DOC_LINK_DECORATION_REFRESH_DELAY_MS - 1)
|
||||
modelValue = '[[second.md]]'
|
||||
contentListener?.()
|
||||
modelValue = '[[final.md]]'
|
||||
contentListener?.()
|
||||
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
vi.advanceTimersByTime(MARKDOWN_DOC_LINK_DECORATION_REFRESH_DELAY_MS - 1)
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
|
||||
vi.advanceTimersByTime(1)
|
||||
expect(set).toHaveBeenCalledTimes(1)
|
||||
expect(set.mock.calls[0]?.[0]).toEqual([
|
||||
{
|
||||
range: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 13
|
||||
},
|
||||
options: {
|
||||
inlineClassName: 'monaco-markdown-doc-link',
|
||||
stickiness: 1
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
controller.dispose()
|
||||
expect(dispose).toHaveBeenCalledTimes(1)
|
||||
expect(clear).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('cancels pending decoration rebuilds on dispose', () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
let contentListener = (): void => {}
|
||||
const set = vi.fn()
|
||||
const clear = vi.fn()
|
||||
|
||||
const editorInstance = {
|
||||
createDecorationsCollection: () => ({ set, clear }),
|
||||
getModel: () => ({ getValue: () => '[[initial.md]]' }),
|
||||
onDidChangeModelContent: (listener: () => void) => {
|
||||
contentListener = listener
|
||||
return { dispose: vi.fn() }
|
||||
}
|
||||
} as unknown as editor.IStandaloneCodeEditor
|
||||
|
||||
const controller = createMarkdownDocLinkDecorationController(editorInstance, () => 'markdown')
|
||||
set.mockClear()
|
||||
|
||||
contentListener?.()
|
||||
controller.dispose()
|
||||
vi.advanceTimersByTime(MARKDOWN_DOC_LINK_DECORATION_REFRESH_DELAY_MS)
|
||||
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
expect(clear).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -71,13 +71,25 @@ export type MarkdownDocLinkDecorationController = {
|
||||
dispose: () => void
|
||||
}
|
||||
|
||||
export const MARKDOWN_DOC_LINK_DECORATION_REFRESH_DELAY_MS = 120
|
||||
|
||||
export function createMarkdownDocLinkDecorationController(
|
||||
editorInstance: editor.IStandaloneCodeEditor,
|
||||
getLanguage: () => string
|
||||
): MarkdownDocLinkDecorationController {
|
||||
const collection = editorInstance.createDecorationsCollection()
|
||||
let refreshTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const refresh = (): void => {
|
||||
const cancelPendingRefresh = (): void => {
|
||||
if (refreshTimer === null) {
|
||||
return
|
||||
}
|
||||
clearTimeout(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
|
||||
const refreshNow = (): void => {
|
||||
cancelPendingRefresh()
|
||||
const model = editorInstance.getModel()
|
||||
if (!model || getLanguage() !== 'markdown') {
|
||||
collection.clear()
|
||||
@@ -94,12 +106,24 @@ export function createMarkdownDocLinkDecorationController(
|
||||
)
|
||||
}
|
||||
|
||||
const refresh = (): void => {
|
||||
if (getLanguage() !== 'markdown') {
|
||||
refreshNow()
|
||||
return
|
||||
}
|
||||
cancelPendingRefresh()
|
||||
// Why: wiki-link decoration scans read the full Monaco model. During typing
|
||||
// the exact highlight can lag briefly; coalescing avoids one full scan per key.
|
||||
refreshTimer = setTimeout(refreshNow, MARKDOWN_DOC_LINK_DECORATION_REFRESH_DELAY_MS)
|
||||
}
|
||||
|
||||
const listener: IDisposable = editorInstance.onDidChangeModelContent(refresh)
|
||||
refresh()
|
||||
refreshNow()
|
||||
|
||||
return {
|
||||
refresh,
|
||||
dispose: () => {
|
||||
cancelPendingRefresh()
|
||||
listener.dispose()
|
||||
collection.clear()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user