From 79cb7fd4bc1ceb03db0e7937b75bf83b7ff663bf Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Fri, 23 Oct 2026 15:56:16 -0700 Subject: [PATCH] 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 --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: Claude --- .../editor/monaco-content-sync.test.ts | 21 +++++++++++++++++++ .../components/editor/monaco-content-sync.ts | 15 ++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/components/editor/monaco-content-sync.test.ts b/src/renderer/src/components/editor/monaco-content-sync.test.ts index 30f833d379c..96996207963 100644 --- a/src/renderer/src/components/editor/monaco-content-sync.test.ts +++ b/src/renderer/src/components/editor/monaco-content-sync.test.ts @@ -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', diff --git a/src/renderer/src/components/editor/monaco-content-sync.ts b/src/renderer/src/components/editor/monaco-content-sync.ts index fa19928b89d..60bf36107b4 100644 --- a/src/renderer/src/components/editor/monaco-content-sync.ts +++ b/src/renderer/src/components/editor/monaco-content-sync.ts @@ -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)|(?