Skip to content

Commit 0dcfd24

Browse files
committed
Built static site
Signed-off-by: Dimitris Kolovos <dimitris.kolovos@york.ac.uk>
1 parent d77c983 commit 0dcfd24

File tree

6 files changed

+253
-4
lines changed

6 files changed

+253
-4
lines changed

playground/dist/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playground/dist/bundle.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playground/js/LiveShareManager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as Y from 'yjs'
22
import { WebsocketProvider } from 'y-websocket'
3-
import { MonacoBinding } from 'y-monaco'
3+
import { MonacoBinding } from './y-monaco.js'
44
import * as monaco from 'monaco-editor'
55
import { backend, consolePanel, outputPanel, preloader } from './Playground';
66
import 'metro4';

playground/js/y-monaco.js

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import * as Y from 'yjs'
2+
import * as monaco from 'monaco-editor'
3+
import * as error from 'lib0/error'
4+
import { createMutex } from 'lib0/mutex'
5+
import { Awareness } from 'y-protocols/awareness' // eslint-disable-line
6+
7+
class RelativeSelection {
8+
/**
9+
* @param {Y.RelativePosition} start
10+
* @param {Y.RelativePosition} end
11+
* @param {monaco.SelectionDirection} direction
12+
*/
13+
constructor (start, end, direction) {
14+
this.start = start
15+
this.end = end
16+
this.direction = direction
17+
}
18+
}
19+
20+
/**
21+
* @param {monaco.editor.IStandaloneCodeEditor} editor
22+
* @param {monaco.editor.ITextModel} monacoModel
23+
* @param {Y.Text} type
24+
*/
25+
const createRelativeSelection = (editor, monacoModel, type) => {
26+
const sel = editor.getSelection()
27+
if (sel !== null) {
28+
const startPos = sel.getStartPosition()
29+
const endPos = sel.getEndPosition()
30+
const start = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(startPos))
31+
const end = Y.createRelativePositionFromTypeIndex(type, monacoModel.getOffsetAt(endPos))
32+
return new RelativeSelection(start, end, sel.getDirection())
33+
}
34+
return null
35+
}
36+
37+
/**
38+
* @param {monaco.editor.IEditor} editor
39+
* @param {Y.Text} type
40+
* @param {RelativeSelection} relSel
41+
* @param {Y.Doc} doc
42+
* @return {null|monaco.Selection}
43+
*/
44+
const createMonacoSelectionFromRelativeSelection = (editor, type, relSel, doc) => {
45+
const start = Y.createAbsolutePositionFromRelativePosition(relSel.start, doc)
46+
const end = Y.createAbsolutePositionFromRelativePosition(relSel.end, doc)
47+
if (start !== null && end !== null && start.type === type && end.type === type) {
48+
const model = /** @type {monaco.editor.ITextModel} */ (editor.getModel())
49+
const startPos = model.getPositionAt(start.index)
50+
const endPos = model.getPositionAt(end.index)
51+
return monaco.Selection.createWithDirection(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column, relSel.direction)
52+
}
53+
return null
54+
}
55+
56+
export class MonacoBinding {
57+
/**
58+
* @param {Y.Text} ytext
59+
* @param {monaco.editor.ITextModel} monacoModel
60+
* @param {Set<monaco.editor.IStandaloneCodeEditor>} [editors]
61+
* @param {Awareness?} [awareness]
62+
*/
63+
constructor (ytext, monacoModel, editors = new Set(), awareness = null) {
64+
this.doc = /** @type {Y.Doc} */ (ytext.doc)
65+
this.ytext = ytext
66+
this.monacoModel = monacoModel
67+
this.editors = editors
68+
this.mux = createMutex()
69+
/**
70+
* @type {Map<monaco.editor.IStandaloneCodeEditor, RelativeSelection>}
71+
*/
72+
this._savedSelections = new Map()
73+
this._beforeTransaction = () => {
74+
this.mux(() => {
75+
this._savedSelections = new Map()
76+
editors.forEach(editor => {
77+
if (editor.getModel() === monacoModel) {
78+
const rsel = createRelativeSelection(editor, monacoModel, ytext)
79+
if (rsel !== null) {
80+
this._savedSelections.set(editor, rsel)
81+
}
82+
}
83+
})
84+
})
85+
}
86+
this.doc.on('beforeAllTransactions', this._beforeTransaction)
87+
this._decorations = new Map()
88+
this._rerenderDecorations = () => {
89+
editors.forEach(editor => {
90+
if (awareness && editor.getModel() === monacoModel) {
91+
// render decorations
92+
const currentDecorations = this._decorations.get(editor) || []
93+
/**
94+
* @type {Array<monaco.editor.IModelDeltaDecoration>}
95+
*/
96+
const newDecorations = []
97+
awareness.getStates().forEach((state, clientID) => {
98+
if (clientID !== this.doc.clientID && state.selection != null && state.selection.anchor != null && state.selection.head != null) {
99+
const anchorAbs = Y.createAbsolutePositionFromRelativePosition(state.selection.anchor, this.doc)
100+
const headAbs = Y.createAbsolutePositionFromRelativePosition(state.selection.head, this.doc)
101+
if (anchorAbs !== null && headAbs !== null && anchorAbs.type === ytext && headAbs.type === ytext) {
102+
let start, end, afterContentClassName, beforeContentClassName
103+
if (anchorAbs.index < headAbs.index) {
104+
start = monacoModel.getPositionAt(anchorAbs.index)
105+
end = monacoModel.getPositionAt(headAbs.index)
106+
afterContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID
107+
beforeContentClassName = null
108+
} else {
109+
start = monacoModel.getPositionAt(headAbs.index)
110+
end = monacoModel.getPositionAt(anchorAbs.index)
111+
afterContentClassName = null
112+
beforeContentClassName = 'yRemoteSelectionHead yRemoteSelectionHead-' + clientID
113+
}
114+
newDecorations.push({
115+
range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column),
116+
options: {
117+
className: 'yRemoteSelection yRemoteSelection-' + clientID,
118+
afterContentClassName,
119+
beforeContentClassName
120+
}
121+
})
122+
}
123+
}
124+
})
125+
this._decorations.set(editor, editor.deltaDecorations(currentDecorations, newDecorations))
126+
} else {
127+
// ignore decorations
128+
this._decorations.delete(editor)
129+
}
130+
})
131+
}
132+
/**
133+
* @param {Y.YTextEvent} event
134+
*/
135+
this._ytextObserver = event => {
136+
this.mux(() => {
137+
let index = 0
138+
event.delta.forEach(op => {
139+
if (op.retain !== undefined) {
140+
index += op.retain
141+
} else if (op.insert !== undefined) {
142+
const pos = monacoModel.getPositionAt(index)
143+
const range = new monaco.Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column)
144+
const insert = /** @type {string} */ (op.insert)
145+
monacoModel.applyEdits([{ range, text: insert }])
146+
index += insert.length
147+
} else if (op.delete !== undefined) {
148+
const pos = monacoModel.getPositionAt(index)
149+
const endPos = monacoModel.getPositionAt(index + op.delete)
150+
const range = new monaco.Selection(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column)
151+
monacoModel.applyEdits([{ range, text: '' }])
152+
} else {
153+
throw error.unexpectedCase()
154+
}
155+
})
156+
this._savedSelections.forEach((rsel, editor) => {
157+
const sel = createMonacoSelectionFromRelativeSelection(editor, ytext, rsel, this.doc)
158+
if (sel !== null) {
159+
editor.setSelection(sel)
160+
}
161+
})
162+
})
163+
this._rerenderDecorations()
164+
}
165+
ytext.observe(this._ytextObserver)
166+
{
167+
const ytextValue = ytext.toString()
168+
if (monacoModel.getValue() !== ytextValue) {
169+
const eol = monacoModel.getEndOfLineSequence();
170+
monacoModel.setValue(ytextValue)
171+
monacoModel.setEOL(eol);
172+
}
173+
}
174+
this._monacoChangeHandler = monacoModel.onDidChangeContent(event => {
175+
// apply changes from right to left
176+
this.mux(() => {
177+
this.doc.transact(() => {
178+
event.changes.sort((change1, change2) => change2.rangeOffset - change1.rangeOffset).forEach(change => {
179+
ytext.delete(change.rangeOffset, change.rangeLength)
180+
ytext.insert(change.rangeOffset, change.text)
181+
})
182+
}, this)
183+
})
184+
})
185+
this._monacoDisposeHandler = monacoModel.onWillDispose(() => {
186+
this.destroy()
187+
})
188+
/**
189+
* @param {monaco.editor.IStandaloneCodeEditor} editor
190+
*/
191+
this._monacoSelectionChangeHandler = (editor) => {
192+
if(!awareness) return
193+
const sel = editor.getSelection()
194+
if (sel === null) {
195+
return
196+
}
197+
let anchor = monacoModel.getOffsetAt(sel.getStartPosition())
198+
let head = monacoModel.getOffsetAt(sel.getEndPosition())
199+
if (sel.getDirection() === monaco.SelectionDirection.RTL) {
200+
const tmp = anchor
201+
anchor = head
202+
head = tmp
203+
}
204+
awareness.setLocalStateField('selection', {
205+
anchor: Y.createRelativePositionFromTypeIndex(ytext, anchor),
206+
head: Y.createRelativePositionFromTypeIndex(ytext, head)
207+
})
208+
}
209+
if (awareness) {
210+
editors.forEach(editor => {
211+
editor.onDidChangeCursorSelection((e) => {
212+
if (editor.getModel() !== monacoModel) return
213+
// Workaround for the wrong event order bug in monaco-editor, ref: https://github.com/microsoft/monaco-editor/issues/2774
214+
if (
215+
e.reason === /** @type {monaco.editor.CursorChangeReason.Undo} */ (5) ||
216+
e.reason === /** @type {monaco.editor.CursorChangeReason.Redo} */ (6)
217+
) {
218+
setTimeout(() => this._monacoSelectionChangeHandler(editor))
219+
} else {
220+
this._monacoSelectionChangeHandler(editor)
221+
}
222+
})
223+
awareness.on('change', this._rerenderDecorations)
224+
})
225+
this.awareness = awareness
226+
}
227+
}
228+
229+
destroy () {
230+
this._monacoChangeHandler.dispose()
231+
this._monacoDisposeHandler.dispose()
232+
this.ytext.unobserve(this._ytextObserver)
233+
this.doc.off('beforeAllTransactions', this._beforeTransaction)
234+
if (this.awareness) {
235+
this.awareness.off('change', this._rerenderDecorations)
236+
}
237+
}
238+
}

playground/readme/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,15 @@
17721772
</span>
17731773
</a>
17741774

1775+
</li>
1776+
1777+
<li class="md-nav__item">
1778+
<a href="#y-monaco" class="md-nav__link">
1779+
<span class="md-ellipsis">
1780+
y-monaco
1781+
</span>
1782+
</a>
1783+
17751784
</li>
17761785

17771786
</ul>
@@ -1822,6 +1831,8 @@ <h2 id="running-yjs-locally">Running YJS locally<a class="headerlink" href="#run
18221831
<li><code>npm i y-websocket</code></li>
18231832
<li><code>HOST=localhost PORT=1234 npx y-websocket</code></li>
18241833
</ul>
1834+
<h2 id="y-monaco">y-monaco<a class="headerlink" href="#y-monaco" title="Permanent link">&para;</a></h2>
1835+
<p><code>y-monaco.js</code> is a local copy of a <a href="https://github.com/kolovos/y-monaco/blob/master/src/y-monaco.js">forked version</a> of the <code>y-monaco</code> package. The original package suffers from two issues (<a href="https://github.com/yjs/y-monaco/issues/6#issuecomment-2163299125">#1</a> and <a href="https://github.com/yjs/y-monaco/pull/23">#2</a>) that have been fixed in the fork. The fixes have been proposed by others to the package maintainer however, they have not been accepted yet. Once the fixes are accepted by the package maintainer, we can use the npm package again.</p>
18251836

18261837

18271838

search/search_index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)