perf: gate contextual copy hint polling (#4024)
This commit is contained in:
parent
819998db04
commit
cab52d1635
|
|
@ -34,6 +34,122 @@ describe('setupContextualCopy', () => {
|
|||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('does not poll a focused editor when no contextual copy hint is visible', () => {
|
||||
const setInterval = vi.fn(() => 1)
|
||||
vi.stubGlobal('window', {
|
||||
clearInterval: vi.fn(),
|
||||
clearTimeout: vi.fn(),
|
||||
setInterval,
|
||||
setTimeout: vi.fn(() => 2)
|
||||
})
|
||||
vi.stubGlobal('document', {
|
||||
createElement: () => ({
|
||||
className: '',
|
||||
offsetHeight: 28,
|
||||
style: { display: '' },
|
||||
textContent: ''
|
||||
})
|
||||
})
|
||||
|
||||
const editorInstance = {
|
||||
addContentWidget: vi.fn(),
|
||||
getContainerDomNode: () => ({
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
}),
|
||||
getModel: () => null,
|
||||
getSelection: () => null,
|
||||
hasTextFocus: () => true,
|
||||
layoutContentWidget: vi.fn(),
|
||||
onDidBlurEditorText: () => ({ dispose: vi.fn() }),
|
||||
onDidChangeCursorSelection: () => ({ dispose: vi.fn() }),
|
||||
onDidDispose: () => ({ dispose: vi.fn() }),
|
||||
onDidFocusEditorText: () => ({ dispose: vi.fn() }),
|
||||
onDidScrollChange: () => ({ dispose: vi.fn() }),
|
||||
removeContentWidget: vi.fn()
|
||||
} as unknown as editor.IStandaloneCodeEditor
|
||||
|
||||
setupContextualCopy({
|
||||
editorInstance,
|
||||
filePath: 'src/example.ts',
|
||||
setCopyToast: vi.fn(),
|
||||
propsRef: {
|
||||
current: {
|
||||
language: 'typescript',
|
||||
relativePath: 'src/example.ts'
|
||||
}
|
||||
},
|
||||
copyToastTimeoutRef: { current: null }
|
||||
})
|
||||
|
||||
expect(setInterval).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('polls a focused editor while a contextual copy hint is visible', () => {
|
||||
const setInterval = vi.fn(() => 1)
|
||||
vi.stubGlobal('window', {
|
||||
clearInterval: vi.fn(),
|
||||
clearTimeout: vi.fn(),
|
||||
setInterval,
|
||||
setTimeout: vi.fn(() => 2)
|
||||
})
|
||||
vi.stubGlobal('document', {
|
||||
createElement: () => ({
|
||||
className: '',
|
||||
offsetHeight: 28,
|
||||
style: { display: '' },
|
||||
textContent: ''
|
||||
})
|
||||
})
|
||||
|
||||
const selection = {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 2,
|
||||
endColumn: 4,
|
||||
isEmpty: () => false,
|
||||
getStartPosition: () => ({ lineNumber: 1, column: 1 }),
|
||||
getEndPosition: () => ({ lineNumber: 2, column: 4 })
|
||||
}
|
||||
const editorInstance = {
|
||||
addContentWidget: vi.fn(),
|
||||
getContainerDomNode: () => ({
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
}),
|
||||
getLayoutInfo: () => ({ height: 500 }),
|
||||
getModel: () => ({
|
||||
getLineMaxColumn: () => 4,
|
||||
getValueInRange: () => 'one\ntwo'
|
||||
}),
|
||||
getScrolledVisiblePosition: () => ({ top: 20, left: 8, height: 16 }),
|
||||
getSelection: () => selection,
|
||||
hasTextFocus: () => true,
|
||||
layoutContentWidget: vi.fn(),
|
||||
onDidBlurEditorText: () => ({ dispose: vi.fn() }),
|
||||
onDidChangeCursorSelection: () => ({ dispose: vi.fn() }),
|
||||
onDidDispose: () => ({ dispose: vi.fn() }),
|
||||
onDidFocusEditorText: () => ({ dispose: vi.fn() }),
|
||||
onDidScrollChange: () => ({ dispose: vi.fn() }),
|
||||
removeContentWidget: vi.fn()
|
||||
} as unknown as editor.IStandaloneCodeEditor
|
||||
|
||||
setupContextualCopy({
|
||||
editorInstance,
|
||||
filePath: 'src/example.ts',
|
||||
setCopyToast: vi.fn(),
|
||||
propsRef: {
|
||||
current: {
|
||||
language: 'typescript',
|
||||
relativePath: 'src/example.ts'
|
||||
}
|
||||
},
|
||||
copyToastTimeoutRef: { current: null }
|
||||
})
|
||||
|
||||
expect(setInterval).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('clears editor-scoped contextual copy cleanup on dispose', () => {
|
||||
const clearTimeout = vi.fn()
|
||||
const clearInterval = vi.fn()
|
||||
|
|
|
|||
|
|
@ -160,13 +160,17 @@ export function setupContextualCopy({
|
|||
editorInstance.layoutContentWidget(copyHintWidget)
|
||||
}
|
||||
|
||||
const isCopyHintVisible = (): boolean => copyHintNode.style.display === 'block'
|
||||
|
||||
const startCopyHintPolling = (): void => {
|
||||
updateCopyHint()
|
||||
if (copyHintInterval !== null) {
|
||||
return
|
||||
}
|
||||
copyHintInterval = window.setInterval(() => {
|
||||
updateCopyHint()
|
||||
if (!isCopyHintVisible()) {
|
||||
stopCopyHintPolling()
|
||||
}
|
||||
}, 150)
|
||||
}
|
||||
|
||||
|
|
@ -177,6 +181,17 @@ export function setupContextualCopy({
|
|||
}
|
||||
}
|
||||
|
||||
const refreshCopyHintAndPolling = (): void => {
|
||||
updateCopyHint()
|
||||
if (editorInstance.hasTextFocus() && isCopyHintVisible()) {
|
||||
// Why: the interval only tracks a visible content widget. Keeping it
|
||||
// alive while the focused editor has no selection burns idle CPU.
|
||||
startCopyHintPolling()
|
||||
} else {
|
||||
stopCopyHintPolling()
|
||||
}
|
||||
}
|
||||
|
||||
const getContextualCopyText = (): string | null => {
|
||||
const model = editorInstance.getModel()
|
||||
const selection = editorInstance.getSelection()
|
||||
|
|
@ -262,13 +277,13 @@ export function setupContextualCopy({
|
|||
if (getSelectionKey() !== lastCopiedSelectionKey) {
|
||||
lastCopiedSelectionKey = null
|
||||
}
|
||||
updateCopyHint()
|
||||
refreshCopyHintAndPolling()
|
||||
})
|
||||
const scrollListener = editorInstance.onDidScrollChange(() => {
|
||||
updateCopyHint()
|
||||
refreshCopyHintAndPolling()
|
||||
})
|
||||
const focusListener = editorInstance.onDidFocusEditorText(() => {
|
||||
startCopyHintPolling()
|
||||
refreshCopyHintAndPolling()
|
||||
})
|
||||
const blurListener = editorInstance.onDidBlurEditorText(() => {
|
||||
stopCopyHintPolling()
|
||||
|
|
@ -291,8 +306,8 @@ export function setupContextualCopy({
|
|||
void copySelectionWithContext()
|
||||
}
|
||||
editorDomNode.addEventListener('keydown', handleKeyDown, true)
|
||||
editorDomNode.addEventListener('mouseup', updateCopyHint, true)
|
||||
editorDomNode.addEventListener('keyup', updateCopyHint, true)
|
||||
editorDomNode.addEventListener('mouseup', refreshCopyHintAndPolling, true)
|
||||
editorDomNode.addEventListener('keyup', refreshCopyHintAndPolling, true)
|
||||
editorInstance.onDidDispose(() => {
|
||||
// Why: Monaco owns these emitters, but disposing explicitly keeps this
|
||||
// feature's lifecycle symmetrical with the DOM listener cleanup below.
|
||||
|
|
@ -312,13 +327,13 @@ export function setupContextualCopy({
|
|||
primarySelectionTimer = null
|
||||
}
|
||||
editorDomNode.removeEventListener('keydown', handleKeyDown, true)
|
||||
editorDomNode.removeEventListener('mouseup', updateCopyHint, true)
|
||||
editorDomNode.removeEventListener('keyup', updateCopyHint, true)
|
||||
editorDomNode.removeEventListener('mouseup', refreshCopyHintAndPolling, true)
|
||||
editorDomNode.removeEventListener('keyup', refreshCopyHintAndPolling, true)
|
||||
stopCopyHintPolling()
|
||||
editorInstance.removeContentWidget(copyHintWidget)
|
||||
})
|
||||
if (editorInstance.hasTextFocus()) {
|
||||
startCopyHintPolling()
|
||||
refreshCopyHintAndPolling()
|
||||
} else {
|
||||
updateCopyHint()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue