From 40036d54680bd6c0f233857d949248416af9e437 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 9 Apr 2024 11:56:59 +0300 Subject: [PATCH 1/3] Support multiple cursors added support of sharing multiple cursors via awareness messages --- src/y-monaco.js | 381 +++++++++++++++++++++++++----------------------- 1 file changed, 202 insertions(+), 179 deletions(-) diff --git a/src/y-monaco.js b/src/y-monaco.js index ebf550b..99177c5 100644 --- a/src/y-monaco.js +++ b/src/y-monaco.js @@ -5,16 +5,27 @@ import { createMutex } from 'lib0/mutex' import { Awareness } from 'y-protocols/awareness' // eslint-disable-line class RelativeSelection { - /** - * @param {Y.RelativePosition} start - * @param {Y.RelativePosition} end - * @param {monaco.SelectionDirection} direction - */ - constructor (start, end, direction) { - this.start = start - this.end = end - this.direction = direction - } + /** + * @param {Y.RelativePosition} start + * @param {Y.RelativePosition} end + * @param {monaco.SelectionDirection} direction + */ + constructor (start, end, direction) { + this.start = start + this.end = end + this.direction = direction + } +} + +class YTextSelection { + /** + * @param {Y.RelativePosition} anchor + * @param {Y.RelativePosition} head + */ + constructor (anchor, head) { + this.anchor = anchor + this.head = head + } } /** @@ -23,15 +34,15 @@ class RelativeSelection { * @param {Y.Text} type */ const createRelativeSelection = (editor, monacoModel, type) => { - const sel = editor.getSelection() - if (sel !== null) { - const startPos = sel.getStartPosition() - const endPos = sel.getEndPosition() - const start = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(startPos)) - const end = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(endPos)) - return new RelativeSelection(start, end, sel.getDirection()) - } - return null + const sel = editor.getSelection() + if (sel !== null) { + const startPos = sel.getStartPosition() + const endPos = sel.getEndPosition() + const start = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(startPos)) + const end = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(endPos)) + return new RelativeSelection(start, end, sel.getDirection()) + } + return null } /** @@ -42,181 +53,193 @@ const createRelativeSelection = (editor, monacoModel, type) => { * @return {null|monaco.Selection} */ const createMonacoSelectionFromRelativeSelection = (editor, type, relSel, doc) => { - const start = Y.createAbsolutePositionFromRelativePosition(relSel.start, doc) - const end = Y.createAbsolutePositionFromRelativePosition(relSel.end, doc) - if (start !== null && end !== null && start.type === type && end.type === type) { - const model = /** @type {monaco.editor.ITextModel} */ (editor.getModel()) - const startPos = model.getPositionAt(start.index) - const endPos = model.getPositionAt(end.index) - return monaco.Selection.createWithDirection(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column, relSel.direction) - } - return null + const start = Y.createAbsolutePositionFromRelativePosition(relSel.start, doc) + const end = Y.createAbsolutePositionFromRelativePosition(relSel.end, doc) + if (start !== null && end !== null && start.type === type && end.type === type) { + const model = /** @type {monaco.editor.ITextModel} */ (editor.getModel()) + const startPos = model.getPositionAt(start.index) + const endPos = model.getPositionAt(end.index) + return monaco.Selection.createWithDirection(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column, relSel.direction) + } + return null } export class MonacoBinding { - /** - * @param {Y.Text} ytext - * @param {monaco.editor.ITextModel} monacoModel - * @param {Set} [editors] - * @param {Awareness?} [awareness] - */ - constructor (ytext, monacoModel, editors = new Set(), awareness = null) { - this.doc = /** @type {Y.Doc} */ (ytext.doc) - this.ytext = ytext - this.monacoModel = monacoModel - this.editors = editors - this.mux = createMutex() /** - * @type {Map} + * @param {Y.Text} ytext + * @param {monaco.editor.ITextModel} monacoModel + * @param {Set} [editors] + * @param {Awareness?} [awareness] */ - this._savedSelections = new Map() - this._beforeTransaction = () => { - this.mux(() => { + constructor (ytext, monacoModel, editors = new Set(), awareness = null) { + this.doc = /** @type {Y.Doc} */ (ytext.doc) + this.ytext = ytext + this.monacoModel = monacoModel + this.editors = editors + this.mux = createMutex() + /** + * @type {Map} + */ this._savedSelections = new Map() - editors.forEach(editor => { - if (editor.getModel() === monacoModel) { - const rsel = createRelativeSelection(editor, monacoModel, ytext) - if (rsel !== null) { - this._savedSelections.set(editor, rsel) - } - } - }) - }) - } - this.doc.on('beforeAllTransactions', this._beforeTransaction) - this._decorations = new Map() - this._rerenderDecorations = () => { - editors.forEach(editor => { - if (awareness && editor.getModel() === monacoModel) { - // render decorations - const currentDecorations = this._decorations.get(editor) || [] - /** - * @type {Array} - */ - const newDecorations = [] - awareness.getStates().forEach((state, clientID) => { - if (clientID !== this.doc.clientID && state.selection != null && state.selection.anchor != null && state.selection.head != null) { - const anchorAbs = Y.createAbsolutePositionFromRelativePosition(state.selection.anchor, this.doc) - const headAbs = Y.createAbsolutePositionFromRelativePosition(state.selection.head, this.doc) - if (anchorAbs !== null && headAbs !== null && anchorAbs.type === ytext && headAbs.type === ytext) { - let start, end, afterContentClassName, beforeContentClassName - if (anchorAbs.index < headAbs.index) { - start = monacoModel.getPositionAt(anchorAbs.index) - end = monacoModel.getPositionAt(headAbs.index) - afterContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID - beforeContentClassName = null + this._beforeTransaction = () => { + this.mux(() => { + this._savedSelections = new Map() + editors.forEach(editor => { + if (editor.getModel() === monacoModel) { + const rsel = createRelativeSelection(editor, monacoModel, ytext) + if (rsel !== null) { + this._savedSelections.set(editor, rsel) + } + } + }) + }) + } + this.doc.on('beforeAllTransactions', this._beforeTransaction) + this._decorations = new Map() + this._rerenderDecorations = () => { + editors.forEach(editor => { + if (awareness && editor.getModel() === monacoModel) { + // render decorations + const currentDecorations = this._decorations.get(editor) || [] + /** + * @type {Array} + */ + const newDecorations = [] + awareness.getStates().forEach((state, clientID) => { + if (clientID !== this.doc.clientID && state.selections != null) { + state.selections.forEach((selection) => { + if (selection.anchor == null || selection.head == null) { + return + } + const anchorAbs = Y.createAbsolutePositionFromRelativePosition(selection.anchor, this.doc) + const headAbs = Y.createAbsolutePositionFromRelativePosition(selection.head, this.doc) + if (anchorAbs !== null && headAbs !== null && anchorAbs.type === ytext && headAbs.type === ytext) { + let start, end, afterContentClassName, beforeContentClassName + if (anchorAbs.index < headAbs.index) { + start = monacoModel.getPositionAt(anchorAbs.index) + end = monacoModel.getPositionAt(headAbs.index) + afterContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID + beforeContentClassName = null + } else { + start = monacoModel.getPositionAt(headAbs.index) + end = monacoModel.getPositionAt(anchorAbs.index) + afterContentClassName = null + beforeContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID + } + newDecorations.push({ + range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column), + options: { + className: 'yRemoteSelection yRemoteSelection-' + clientID, + afterContentClassName, + beforeContentClassName + } + }) + } + }) + } + }) + this._decorations.set(editor, editor.deltaDecorations(currentDecorations, newDecorations)) } else { - start = monacoModel.getPositionAt(headAbs.index) - end = monacoModel.getPositionAt(anchorAbs.index) - afterContentClassName = null - beforeContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID + // ignore decorations + this._decorations.delete(editor) } - newDecorations.push({ - range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column), - options: { - className: 'yRemoteSelection yRemoteSelection-' + clientID, - afterContentClassName, - beforeContentClassName - } + }) + } + /** + * @param {Y.YTextEvent} event + */ + this._ytextObserver = event => { + this.mux(() => { + let index = 0 + event.delta.forEach(op => { + if (op.retain !== undefined) { + index += op.retain + } else if (op.insert !== undefined) { + const pos = monacoModel.getPositionAt(index) + const range = new monaco.Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column) + const insert = /** @type {string} */ (op.insert) + monacoModel.applyEdits([{ range, text: insert }]) + index += insert.length + } else if (op.delete !== undefined) { + const pos = monacoModel.getPositionAt(index) + const endPos = monacoModel.getPositionAt(index + op.delete) + const range = new monaco.Selection(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column) + monacoModel.applyEdits([{ range, text: '' }]) + } else { + throw error.unexpectedCase() + } + }) + this._savedSelections.forEach((rsel, editor) => { + const sel = createMonacoSelectionFromRelativeSelection(editor, ytext, rsel, this.doc) + if (sel !== null) { + editor.setSelection(sel) + } }) - } + }) + this._rerenderDecorations() + } + ytext.observe(this._ytextObserver) + { + const ytextValue = ytext.toString() + if (monacoModel.getValue() !== ytextValue) { + monacoModel.setValue(ytextValue) } - }) - this._decorations.set(editor, editor.deltaDecorations(currentDecorations, newDecorations)) - } else { - // ignore decorations - this._decorations.delete(editor) } - }) - } - /** - * @param {Y.YTextEvent} event - */ - this._ytextObserver = event => { - this.mux(() => { - let index = 0 - event.delta.forEach(op => { - if (op.retain !== undefined) { - index += op.retain - } else if (op.insert !== undefined) { - const pos = monacoModel.getPositionAt(index) - const range = new monaco.Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column) - const insert = /** @type {string} */ (op.insert) - monacoModel.applyEdits([{ range, text: insert }]) - index += insert.length - } else if (op.delete !== undefined) { - const pos = monacoModel.getPositionAt(index) - const endPos = monacoModel.getPositionAt(index + op.delete) - const range = new monaco.Selection(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column) - monacoModel.applyEdits([{ range, text: '' }]) - } else { - throw error.unexpectedCase() - } + this._monacoChangeHandler = monacoModel.onDidChangeContent(event => { + // apply changes from right to left + this.mux(() => { + this.doc.transact(() => { + event.changes.sort((change1, change2) => change2.rangeOffset - change1.rangeOffset).forEach(change => { + ytext.delete(change.rangeOffset, change.rangeLength) + ytext.insert(change.rangeOffset, change.text) + }) + }, this) + }) }) - this._savedSelections.forEach((rsel, editor) => { - const sel = createMonacoSelectionFromRelativeSelection(editor, ytext, rsel, this.doc) - if (sel !== null) { - editor.setSelection(sel) - } + this._monacoDisposeHandler = monacoModel.onWillDispose(() => { + this.destroy() }) - }) - this._rerenderDecorations() - } - ytext.observe(this._ytextObserver) - { - const ytextValue = ytext.toString() - if (monacoModel.getValue() !== ytextValue) { - monacoModel.setValue(ytextValue) - } - } - this._monacoChangeHandler = monacoModel.onDidChangeContent(event => { - // apply changes from right to left - this.mux(() => { - this.doc.transact(() => { - event.changes.sort((change1, change2) => change2.rangeOffset - change1.rangeOffset).forEach(change => { - ytext.delete(change.rangeOffset, change.rangeLength) - ytext.insert(change.rangeOffset, change.text) - }) - }, this) - }) - }) - this._monacoDisposeHandler = monacoModel.onWillDispose(() => { - this.destroy() - }) - if (awareness) { - editors.forEach(editor => { - editor.onDidChangeCursorSelection(() => { - if (editor.getModel() === monacoModel) { - const sel = editor.getSelection() - if (sel === null) { - return - } - let anchor = monacoModel.getOffsetAt(sel.getStartPosition()) - let head = monacoModel.getOffsetAt(sel.getEndPosition()) - if (sel.getDirection() === monaco.SelectionDirection.RTL) { - const tmp = anchor - anchor = head - head = tmp - } - awareness.setLocalStateField('selection', { - anchor: Y.createRelativePositionFromTypeIndex(ytext, anchor), - head: Y.createRelativePositionFromTypeIndex(ytext, head) + if (awareness) { + editors.forEach(editor => { + editor.onDidChangeCursorSelection(() => { + if (editor.getModel() === monacoModel) { + const selections = editor.getSelections() + if (selections === null) { + return + } + /** + * @type {Array} + */ + const ytextSelections = [] + selections.forEach(sel => { + let anchor = monacoModel.getOffsetAt(sel.getStartPosition()) + let head = monacoModel.getOffsetAt(sel.getEndPosition()) + if (sel.getDirection() === monaco.SelectionDirection.RTL) { + const tmp = anchor + anchor = head + head = tmp + } + ytextSelections.push({ + anchor: Y.createRelativePositionFromTypeIndex(ytext, anchor), + head: Y.createRelativePositionFromTypeIndex(ytext, head) + }) + }) + awareness.setLocalStateField('selections', ytextSelections) + } + }) + awareness.on('change', this._rerenderDecorations) }) - } - }) - awareness.on('change', this._rerenderDecorations) - }) - this.awareness = awareness + this.awareness = awareness + } } - } - destroy () { - this._monacoChangeHandler.dispose() - this._monacoDisposeHandler.dispose() - this.ytext.unobserve(this._ytextObserver) - this.doc.off('beforeAllTransactions', this._beforeTransaction) - if (this.awareness) { - this.awareness.off('change', this._rerenderDecorations) + destroy () { + this._monacoChangeHandler.dispose() + this._monacoDisposeHandler.dispose() + this.ytext.unobserve(this._ytextObserver) + this.doc.off('beforeAllTransactions', this._beforeTransaction) + if (this.awareness) { + this.awareness.off('change', this._rerenderDecorations) + } } - } } From db9216f8fbfb0b942881efe35faed33447815dfd Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 9 Apr 2024 12:00:28 +0300 Subject: [PATCH 2/3] fix spaces --- src/y-monaco.js | 400 ++++++++++++++++++++++++------------------------ 1 file changed, 200 insertions(+), 200 deletions(-) diff --git a/src/y-monaco.js b/src/y-monaco.js index 99177c5..59bb994 100644 --- a/src/y-monaco.js +++ b/src/y-monaco.js @@ -5,27 +5,27 @@ import { createMutex } from 'lib0/mutex' import { Awareness } from 'y-protocols/awareness' // eslint-disable-line class RelativeSelection { - /** - * @param {Y.RelativePosition} start - * @param {Y.RelativePosition} end - * @param {monaco.SelectionDirection} direction - */ - constructor (start, end, direction) { - this.start = start - this.end = end - this.direction = direction - } + /** + * @param {Y.RelativePosition} start + * @param {Y.RelativePosition} end + * @param {monaco.SelectionDirection} direction + */ + constructor(start, end, direction) { + this.start = start + this.end = end + this.direction = direction + } } class YTextSelection { - /** - * @param {Y.RelativePosition} anchor - * @param {Y.RelativePosition} head - */ - constructor (anchor, head) { - this.anchor = anchor - this.head = head - } + /** + * @param {Y.RelativePosition} anchor + * @param {Y.RelativePosition} head + */ + constructor(anchor, head) { + this.anchor = anchor + this.head = head + } } /** @@ -34,15 +34,15 @@ class YTextSelection { * @param {Y.Text} type */ const createRelativeSelection = (editor, monacoModel, type) => { - const sel = editor.getSelection() - if (sel !== null) { - const startPos = sel.getStartPosition() - const endPos = sel.getEndPosition() - const start = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(startPos)) - const end = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(endPos)) - return new RelativeSelection(start, end, sel.getDirection()) - } - return null + const sel = editor.getSelection() + if (sel !== null) { + const startPos = sel.getStartPosition() + const endPos = sel.getEndPosition() + const start = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(startPos)) + const end = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(endPos)) + return new RelativeSelection(start, end, sel.getDirection()) + } + return null } /** @@ -53,193 +53,193 @@ const createRelativeSelection = (editor, monacoModel, type) => { * @return {null|monaco.Selection} */ const createMonacoSelectionFromRelativeSelection = (editor, type, relSel, doc) => { - const start = Y.createAbsolutePositionFromRelativePosition(relSel.start, doc) - const end = Y.createAbsolutePositionFromRelativePosition(relSel.end, doc) - if (start !== null && end !== null && start.type === type && end.type === type) { - const model = /** @type {monaco.editor.ITextModel} */ (editor.getModel()) - const startPos = model.getPositionAt(start.index) - const endPos = model.getPositionAt(end.index) - return monaco.Selection.createWithDirection(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column, relSel.direction) - } - return null + const start = Y.createAbsolutePositionFromRelativePosition(relSel.start, doc) + const end = Y.createAbsolutePositionFromRelativePosition(relSel.end, doc) + if (start !== null && end !== null && start.type === type && end.type === type) { + const model = /** @type {monaco.editor.ITextModel} */ (editor.getModel()) + const startPos = model.getPositionAt(start.index) + const endPos = model.getPositionAt(end.index) + return monaco.Selection.createWithDirection(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column, relSel.direction) + } + return null } export class MonacoBinding { + /** + * @param {Y.Text} ytext + * @param {monaco.editor.ITextModel} monacoModel + * @param {Set} [editors] + * @param {Awareness?} [awareness] + */ + constructor(ytext, monacoModel, editors = new Set(), awareness = null) { + this.doc = /** @type {Y.Doc} */ (ytext.doc) + this.ytext = ytext + this.monacoModel = monacoModel + this.editors = editors + this.mux = createMutex() /** - * @param {Y.Text} ytext - * @param {monaco.editor.ITextModel} monacoModel - * @param {Set} [editors] - * @param {Awareness?} [awareness] + * @type {Map} */ - constructor (ytext, monacoModel, editors = new Set(), awareness = null) { - this.doc = /** @type {Y.Doc} */ (ytext.doc) - this.ytext = ytext - this.monacoModel = monacoModel - this.editors = editors - this.mux = createMutex() - /** - * @type {Map} - */ + this._savedSelections = new Map() + this._beforeTransaction = () => { + this.mux(() => { this._savedSelections = new Map() - this._beforeTransaction = () => { - this.mux(() => { - this._savedSelections = new Map() - editors.forEach(editor => { - if (editor.getModel() === monacoModel) { - const rsel = createRelativeSelection(editor, monacoModel, ytext) - if (rsel !== null) { - this._savedSelections.set(editor, rsel) - } - } - }) - }) - } - this.doc.on('beforeAllTransactions', this._beforeTransaction) - this._decorations = new Map() - this._rerenderDecorations = () => { - editors.forEach(editor => { - if (awareness && editor.getModel() === monacoModel) { - // render decorations - const currentDecorations = this._decorations.get(editor) || [] - /** - * @type {Array} - */ - const newDecorations = [] - awareness.getStates().forEach((state, clientID) => { - if (clientID !== this.doc.clientID && state.selections != null) { - state.selections.forEach((selection) => { - if (selection.anchor == null || selection.head == null) { - return - } - const anchorAbs = Y.createAbsolutePositionFromRelativePosition(selection.anchor, this.doc) - const headAbs = Y.createAbsolutePositionFromRelativePosition(selection.head, this.doc) - if (anchorAbs !== null && headAbs !== null && anchorAbs.type === ytext && headAbs.type === ytext) { - let start, end, afterContentClassName, beforeContentClassName - if (anchorAbs.index < headAbs.index) { - start = monacoModel.getPositionAt(anchorAbs.index) - end = monacoModel.getPositionAt(headAbs.index) - afterContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID - beforeContentClassName = null - } else { - start = monacoModel.getPositionAt(headAbs.index) - end = monacoModel.getPositionAt(anchorAbs.index) - afterContentClassName = null - beforeContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID - } - newDecorations.push({ - range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column), - options: { - className: 'yRemoteSelection yRemoteSelection-' + clientID, - afterContentClassName, - beforeContentClassName - } - }) - } - }) - } - }) - this._decorations.set(editor, editor.deltaDecorations(currentDecorations, newDecorations)) - } else { - // ignore decorations - this._decorations.delete(editor) + editors.forEach(editor => { + if (editor.getModel() === monacoModel) { + const rsel = createRelativeSelection(editor, monacoModel, ytext) + if (rsel !== null) { + this._savedSelections.set(editor, rsel) + } + } + }) + }) + } + this.doc.on('beforeAllTransactions', this._beforeTransaction) + this._decorations = new Map() + this._rerenderDecorations = () => { + editors.forEach(editor => { + if (awareness && editor.getModel() === monacoModel) { + // render decorations + const currentDecorations = this._decorations.get(editor) || [] + /** + * @type {Array} + */ + const newDecorations = [] + awareness.getStates().forEach((state, clientID) => { + if (clientID !== this.doc.clientID && state.selections != null) { + state.selections.forEach((selection) => { + if (selection.anchor == null || selection.head == null) { + return } - }) - } - /** - * @param {Y.YTextEvent} event - */ - this._ytextObserver = event => { - this.mux(() => { - let index = 0 - event.delta.forEach(op => { - if (op.retain !== undefined) { - index += op.retain - } else if (op.insert !== undefined) { - const pos = monacoModel.getPositionAt(index) - const range = new monaco.Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column) - const insert = /** @type {string} */ (op.insert) - monacoModel.applyEdits([{ range, text: insert }]) - index += insert.length - } else if (op.delete !== undefined) { - const pos = monacoModel.getPositionAt(index) - const endPos = monacoModel.getPositionAt(index + op.delete) - const range = new monaco.Selection(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column) - monacoModel.applyEdits([{ range, text: '' }]) - } else { - throw error.unexpectedCase() + const anchorAbs = Y.createAbsolutePositionFromRelativePosition(selection.anchor, this.doc) + const headAbs = Y.createAbsolutePositionFromRelativePosition(selection.head, this.doc) + if (anchorAbs !== null && headAbs !== null && anchorAbs.type === ytext && headAbs.type === ytext) { + let start, end, afterContentClassName, beforeContentClassName + if (anchorAbs.index < headAbs.index) { + start = monacoModel.getPositionAt(anchorAbs.index) + end = monacoModel.getPositionAt(headAbs.index) + afterContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID + beforeContentClassName = null + } else { + start = monacoModel.getPositionAt(headAbs.index) + end = monacoModel.getPositionAt(anchorAbs.index) + afterContentClassName = null + beforeContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID + } + newDecorations.push({ + range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column), + options: { + className: 'yRemoteSelection yRemoteSelection-' + clientID, + afterContentClassName, + beforeContentClassName } - }) - this._savedSelections.forEach((rsel, editor) => { - const sel = createMonacoSelectionFromRelativeSelection(editor, ytext, rsel, this.doc) - if (sel !== null) { - editor.setSelection(sel) - } - }) - }) - this._rerenderDecorations() - } - ytext.observe(this._ytextObserver) - { - const ytextValue = ytext.toString() - if (monacoModel.getValue() !== ytextValue) { - monacoModel.setValue(ytextValue) + }) + } + }) } + }) + this._decorations.set(editor, editor.deltaDecorations(currentDecorations, newDecorations)) + } else { + // ignore decorations + this._decorations.delete(editor) } - this._monacoChangeHandler = monacoModel.onDidChangeContent(event => { - // apply changes from right to left - this.mux(() => { - this.doc.transact(() => { - event.changes.sort((change1, change2) => change2.rangeOffset - change1.rangeOffset).forEach(change => { - ytext.delete(change.rangeOffset, change.rangeLength) - ytext.insert(change.rangeOffset, change.text) - }) - }, this) - }) + }) + } + /** + * @param {Y.YTextEvent} event + */ + this._ytextObserver = event => { + this.mux(() => { + let index = 0 + event.delta.forEach(op => { + if (op.retain !== undefined) { + index += op.retain + } else if (op.insert !== undefined) { + const pos = monacoModel.getPositionAt(index) + const range = new monaco.Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column) + const insert = /** @type {string} */ (op.insert) + monacoModel.applyEdits([{ range, text: insert }]) + index += insert.length + } else if (op.delete !== undefined) { + const pos = monacoModel.getPositionAt(index) + const endPos = monacoModel.getPositionAt(index + op.delete) + const range = new monaco.Selection(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column) + monacoModel.applyEdits([{ range, text: '' }]) + } else { + throw error.unexpectedCase() + } }) - this._monacoDisposeHandler = monacoModel.onWillDispose(() => { - this.destroy() + this._savedSelections.forEach((rsel, editor) => { + const sel = createMonacoSelectionFromRelativeSelection(editor, ytext, rsel, this.doc) + if (sel !== null) { + editor.setSelection(sel) + } }) - if (awareness) { - editors.forEach(editor => { - editor.onDidChangeCursorSelection(() => { - if (editor.getModel() === monacoModel) { - const selections = editor.getSelections() - if (selections === null) { - return - } - /** - * @type {Array} - */ - const ytextSelections = [] - selections.forEach(sel => { - let anchor = monacoModel.getOffsetAt(sel.getStartPosition()) - let head = monacoModel.getOffsetAt(sel.getEndPosition()) - if (sel.getDirection() === monaco.SelectionDirection.RTL) { - const tmp = anchor - anchor = head - head = tmp - } - ytextSelections.push({ - anchor: Y.createRelativePositionFromTypeIndex(ytext, anchor), - head: Y.createRelativePositionFromTypeIndex(ytext, head) - }) - }) - awareness.setLocalStateField('selections', ytextSelections) - } - }) - awareness.on('change', this._rerenderDecorations) + }) + this._rerenderDecorations() + } + ytext.observe(this._ytextObserver) + { + const ytextValue = ytext.toString() + if (monacoModel.getValue() !== ytextValue) { + monacoModel.setValue(ytextValue) + } + } + this._monacoChangeHandler = monacoModel.onDidChangeContent(event => { + // apply changes from right to left + this.mux(() => { + this.doc.transact(() => { + event.changes.sort((change1, change2) => change2.rangeOffset - change1.rangeOffset).forEach(change => { + ytext.delete(change.rangeOffset, change.rangeLength) + ytext.insert(change.rangeOffset, change.text) + }) + }, this) + }) + }) + this._monacoDisposeHandler = monacoModel.onWillDispose(() => { + this.destroy() + }) + if (awareness) { + editors.forEach(editor => { + editor.onDidChangeCursorSelection(() => { + if (editor.getModel() === monacoModel) { + const selections = editor.getSelections() + if (selections === null) { + return + } + /** + * @type {Array} + */ + const ytextSelections = [] + selections.forEach(sel => { + let anchor = monacoModel.getOffsetAt(sel.getStartPosition()) + let head = monacoModel.getOffsetAt(sel.getEndPosition()) + if (sel.getDirection() === monaco.SelectionDirection.RTL) { + const tmp = anchor + anchor = head + head = tmp + } + ytextSelections.push({ + anchor: Y.createRelativePositionFromTypeIndex(ytext, anchor), + head: Y.createRelativePositionFromTypeIndex(ytext, head) + }) }) - this.awareness = awareness - } + awareness.setLocalStateField('selections', ytextSelections) + } + }) + awareness.on('change', this._rerenderDecorations) + }) + this.awareness = awareness } + } - destroy () { - this._monacoChangeHandler.dispose() - this._monacoDisposeHandler.dispose() - this.ytext.unobserve(this._ytextObserver) - this.doc.off('beforeAllTransactions', this._beforeTransaction) - if (this.awareness) { - this.awareness.off('change', this._rerenderDecorations) - } + destroy() { + this._monacoChangeHandler.dispose() + this._monacoDisposeHandler.dispose() + this.ytext.unobserve(this._ytextObserver) + this.doc.off('beforeAllTransactions', this._beforeTransaction) + if (this.awareness) { + this.awareness.off('change', this._rerenderDecorations) } + } } From cf68a221f630a384cc8ff3b6a43e3771a75943e9 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 9 Apr 2024 12:03:41 +0300 Subject: [PATCH 3/3] fix spaces --- src/y-monaco.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/y-monaco.js b/src/y-monaco.js index 59bb994..0230836 100644 --- a/src/y-monaco.js +++ b/src/y-monaco.js @@ -10,7 +10,7 @@ class RelativeSelection { * @param {Y.RelativePosition} end * @param {monaco.SelectionDirection} direction */ - constructor(start, end, direction) { + constructor (start, end, direction) { this.start = start this.end = end this.direction = direction @@ -22,7 +22,7 @@ class YTextSelection { * @param {Y.RelativePosition} anchor * @param {Y.RelativePosition} head */ - constructor(anchor, head) { + constructor (anchor, head) { this.anchor = anchor this.head = head } @@ -71,7 +71,7 @@ export class MonacoBinding { * @param {Set} [editors] * @param {Awareness?} [awareness] */ - constructor(ytext, monacoModel, editors = new Set(), awareness = null) { + constructor (ytext, monacoModel, editors = new Set(), awareness = null) { this.doc = /** @type {Y.Doc} */ (ytext.doc) this.ytext = ytext this.monacoModel = monacoModel @@ -233,7 +233,7 @@ export class MonacoBinding { } } - destroy() { + destroy () { this._monacoChangeHandler.dispose() this._monacoDisposeHandler.dispose() this.ytext.unobserve(this._ytextObserver)