From 8a1e5a3555aa2afe5014f80bcfdb4d533eac0e4a Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Fri, 30 May 2025 07:34:24 -0400 Subject: [PATCH] Merge pull request #25952 from overleaf/em-split-editor-facade Split EditorFacade functionality for history OT (2nd attempt) GitOrigin-RevId: 2bc6d6c54a9f336fd4a69f0eb548dd06b9f06f5f --- .../features/ide-react/editor/share-js-doc.ts | 2 +- .../source-editor/extensions/realtime.ts | 73 ++++++++++++++----- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts b/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts index 96e866afec..a773684dcb 100644 --- a/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts +++ b/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts @@ -365,7 +365,7 @@ export class ShareJsDoc extends EventEmitter { attachToCM6(cm6: EditorFacade) { this.attachToEditor(cm6, () => { - cm6.attachShareJs(this._doc, getMeta('ol-maxDocLength')) + cm6.attachShareJs(this._doc, getMeta('ol-maxDocLength'), this.type) }) } diff --git a/services/web/frontend/js/features/source-editor/extensions/realtime.ts b/services/web/frontend/js/features/source-editor/extensions/realtime.ts index 36d9956a76..9118e4f151 100644 --- a/services/web/frontend/js/features/source-editor/extensions/realtime.ts +++ b/services/web/frontend/js/features/source-editor/extensions/realtime.ts @@ -5,6 +5,7 @@ import RangesTracker from '@overleaf/ranges-tracker' import { ShareDoc } from '../../../../../types/share-doc' import { debugConsole } from '@/utils/debugging' import { DocumentContainer } from '@/features/ide-react/editor/document-container' +import { OTType } from '@/features/ide-react/editor/share-js-doc' /* * Integrate CodeMirror 6 with the real-time system, via ShareJS. @@ -76,15 +77,22 @@ export const realtime = ( return Prec.highest([realtimePlugin, ensureRealtimePlugin]) } +type OTAdapter = { + handleUpdateFromCM( + transactions: readonly Transaction[], + ranges?: RangesTracker + ): void + attachShareJs(): void +} + export class EditorFacade extends EventEmitter { - public shareDoc: ShareDoc | null + private otAdapter: OTAdapter | null public events: EventEmitter - private maxDocLength?: number constructor(public view: EditorView) { super() this.view = view - this.shareDoc = null + this.otAdapter = null this.events = new EventEmitter() } @@ -118,23 +126,56 @@ export class EditorFacade extends EventEmitter { this.cmChange({ from: position, to: position + text.length }, origin) } + attachShareJs(shareDoc: ShareDoc, maxDocLength?: number, type?: OTType) { + this.otAdapter = + type === 'history-ot' + ? new HistoryOTAdapter(this, shareDoc, maxDocLength) + : new ShareLatexOTAdapter(this, shareDoc, maxDocLength) + this.otAdapter.attachShareJs() + } + + detachShareJs() { + this.otAdapter = null + } + + handleUpdateFromCM( + transactions: readonly Transaction[], + ranges?: RangesTracker + ) { + if (this.otAdapter == null) { + throw new Error('Trying to process updates with no otAdapter') + } + + this.otAdapter.handleUpdateFromCM(transactions, ranges) + } +} + +class ShareLatexOTAdapter { + constructor( + public editor: EditorFacade, + private shareDoc: ShareDoc, + private maxDocLength?: number + ) { + this.editor = editor + this.shareDoc = shareDoc + this.maxDocLength = maxDocLength + } + // Connect to ShareJS, passing changes to the CodeMirror view // as new transactions. // This is a broad immitation of helper functions supplied in // the sharejs library. (See vendor/libs/sharejs, in particular // the 'attach_ace' helper) - attachShareJs(shareDoc: ShareDoc, maxDocLength?: number) { - this.shareDoc = shareDoc - this.maxDocLength = maxDocLength - + attachShareJs() { + const shareDoc = this.shareDoc const check = () => { // run in a timeout so it checks the editor content once this update has been applied window.setTimeout(() => { - const editorText = this.getValue() + const editorText = this.editor.getValue() const otText = shareDoc.getText() if (editorText !== otText) { - shareDoc.emit('error', 'Text does not match in CodeMirror 6') + this.shareDoc.emit('error', 'Text does not match in CodeMirror 6') debugConsole.error('Text does not match!') debugConsole.error('editor: ' + editorText) debugConsole.error('ot: ' + otText) @@ -143,12 +184,12 @@ export class EditorFacade extends EventEmitter { } const onInsert = (pos: number, text: string) => { - this.cmInsert(pos, text, 'remote') + this.editor.cmInsert(pos, text, 'remote') check() } const onDelete = (pos: number, text: string) => { - this.cmDelete(pos, text, 'remote') + this.editor.cmDelete(pos, text, 'remote') check() } @@ -161,7 +202,7 @@ export class EditorFacade extends EventEmitter { shareDoc.removeListener('insert', onInsert) shareDoc.removeListener('delete', onDelete) delete shareDoc.detach_cm6 - this.shareDoc = null + this.editor.detachShareJs() } } @@ -175,10 +216,6 @@ export class EditorFacade extends EventEmitter { const trackedDeletesLength = ranges != null ? ranges.getTrackedDeletesLength() : 0 - if (!shareDoc) { - throw new Error('Trying to process updates with no shareDoc') - } - for (const transaction of transactions) { if (transaction.docChanged) { const origin = chooseOrigin(transaction) @@ -234,7 +271,7 @@ export class EditorFacade extends EventEmitter { removed, } - this.emit('change', this, changeDescription) + this.editor.emit('change', this.editor, changeDescription) } ) } @@ -242,6 +279,8 @@ export class EditorFacade extends EventEmitter { } } +class HistoryOTAdapter extends ShareLatexOTAdapter {} + export const trackChangesAnnotation = Annotation.define() const chooseOrigin = (transaction: Transaction) => {