fix(editor): avoid redundant CRLF normalization when syncing Monaco (#32583)

* fix(editor): reuse unchanged model text before EOL normalization

* fix(editor): preserve paired CRLF separators during live updates

* test(editor): drop the Monaco content-sync heap probe

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
OrcaWin
2026-10-23 15:56:16 -07:00
committed by GitHub
co-authored by Claude OrcaWin
parent 5f0c4a50bd
commit 79cb7fd4bc
2 changed files with 31 additions and 5 deletions
@@ -102,6 +102,27 @@ describe('monaco content sync', () => {
})
it.each([
[
'CRLF file content into a CRLF model',
'first\r\nsecond\r\nlast',
'\r\n',
'first\r\nsecond\r\nlast\r\nnext',
'\r\nnext'
],
[
'mixed file content into a CRLF model',
'first\r\nsecond\r\nlast',
'\r\n',
'first\r\nsecond\rlast\nnext',
'\r\nnext'
],
[
'adjacent bare and paired CRs into a CRLF model',
'first\r\nsecond\r\nlast',
'\r\n',
'first\r\nsecond\r\nlast\r\r\n\nnext\r',
'\r\n\r\n\r\nnext\r\n'
],
[
'CRLF file content into an LF model',
'first\nsecond\nlast',
@@ -2,14 +2,19 @@ import type { editor } from 'monaco-editor'
export type MonacoContentSyncMode = 'undoable' | 'read-only-live-tail'
function normalizeToModelEol(content: string, model: editor.ITextModel): string {
function normalizeToModelEol(
content: string,
model: editor.ITextModel,
currentContent: string
): string {
const eol = model.getEOL()
// Why: Monaco normalizes model line endings, while filesystem content keeps
// its raw EOLs. Compare the representation Monaco can actually retain.
if (eol === '\n' && !content.includes('\r')) {
if ((eol === '\n' && !content.includes('\r')) || content === currentContent) {
return content
}
return content.replace(/\r\n|\r|\n/g, eol)
const separators = eol === '\r\n' ? /\r(?!\n)|(?<!\r)\n/g : /\r\n|\r|\n/g
return content.replace(separators, eol)
}
function applyModelEdit(
@@ -69,7 +74,7 @@ export function syncContentOnMount(
return false
}
const currentContent = model.getValue()
const normalizedContent = normalizeToModelEol(content, model)
const normalizedContent = normalizeToModelEol(content, model, currentContent)
if (currentContent === normalizedContent) {
return false
}
@@ -98,7 +103,7 @@ export function syncContentUpdate(
return
}
const currentContent = model.getValue()
const normalizedContent = normalizeToModelEol(content, model)
const normalizedContent = normalizeToModelEol(content, model, currentContent)
if (currentContent.length === normalizedContent.length) {
replaceModelContent(editorInstance, model, currentContent, normalizedContent, mode, true)
return