Skip to content

Commit fdb1bd3

Browse files
samuel-olivierLoïc Mangeonjean
authored andcommitted
refactor: make model logic async
1 parent 417c1ba commit fdb1bd3

File tree

1 file changed

+68
-56
lines changed

1 file changed

+68
-56
lines changed

src/MonacoEditor.tsx

Lines changed: 68 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { ForwardedRef, forwardRef, ReactElement, useEffect, useMemo, useRef, useState } from 'react'
22
import debounce from 'lodash.debounce'
3-
import { monaco, createEditor, getMonacoLanguage, updateEditorKeybindingsMode, registerEditorOpenHandler } from '@codingame/monaco-editor-wrapper'
4-
import { IEditorOptions } from 'vscode/service-override/modelEditor'
3+
import * as monaco from 'monaco-editor'
4+
import { createEditor, getMonacoLanguage, updateEditorKeybindingsMode, registerEditorOpenHandler, createModelReference } from '@codingame/monaco-editor-wrapper'
5+
import { IEditorOptions, IResolvedTextEditorModel } from '@codingame/monaco-vscode-editor-service-override'
6+
import { IReference, ITextFileEditorModel } from 'vscode/monaco'
57
import { useDeepMemo, useLastValueRef, useLastVersion, useThemeColor } from './hooks'
68
import './style'
79

@@ -93,7 +95,7 @@ export interface MonacoEditorProps {
9395
*
9496
* Default is opening a new editor in a popup
9597
*/
96-
onEditorOpenRequest?: (model: monaco.editor.ITextModel, options: IEditorOptions | undefined, source: monaco.editor.ICodeEditor, sideBySide?: boolean) => Promise<monaco.editor.ICodeEditor | null>
98+
onEditorOpenRequest?: (model: IReference<IResolvedTextEditorModel>, options: IEditorOptions | undefined, source: monaco.editor.ICodeEditor, sideBySide?: boolean) => Promise<monaco.editor.ICodeEditor | null>
9799

98100
/**
99101
* if true, the models created by the component will be disposed when they are no longer displayed in the editor
@@ -115,11 +117,11 @@ function MonacoEditor ({
115117
markers,
116118
saveViewState = defaultSaveViewState,
117119
restoreViewState = defaultRestoreViewState,
118-
onEditorOpenRequest,
119-
disposeModels = true
120+
onEditorOpenRequest
120121
}: MonacoEditorProps, ref: ForwardedRef<monaco.editor.IStandaloneCodeEditor>): ReactElement {
121122
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>()
122123
const modelRef = useRef<monaco.editor.ITextModel>()
124+
const [modelReady, setModelReady] = useState(false)
123125
const preventTriggerChangeEventRef = useRef<boolean>(false)
124126

125127
const [height, setHeight] = useState<number | string>(requestedHeight !== 'auto' ? requestedHeight : 50)
@@ -134,15 +136,10 @@ function MonacoEditor ({
134136
const memoizedOptions = useDeepMemo(() => options, [options])
135137
const allOptions = useMemo<monaco.editor.IEditorOptions>(() => {
136138
return removeKeyBindingsManagedOptions({
137-
...memoizedOptions,
138-
automaticLayout: true
139+
...memoizedOptions
139140
}, keyBindingsMode)
140141
}, [memoizedOptions, keyBindingsMode])
141142

142-
const modelUri = useMemo(() => {
143-
return fileUri != null ? monaco.Uri.parse(fileUri) : undefined
144-
}, [fileUri])
145-
146143
const fixedCode = useMemo(() => value != null ? fixCode(value) : null, [value])
147144

148145
const valueRef = useLastValueRef(fixedCode)
@@ -151,49 +148,14 @@ function MonacoEditor ({
151148

152149
const hasValue = fixedCode != null
153150

154-
// Create/Update model
155-
useEffect(() => {
156-
if (modelUri != null || hasValue) {
157-
const value = valueRef.current
158-
const existingModel = modelUri != null ? monaco.editor.getModel(modelUri) : null
159-
const model = existingModel ?? monaco.editor.createModel(value!, monacoLanguage, modelUri)
160-
if (monacoLanguage != null && model.getLanguageId() !== monacoLanguage) {
161-
monaco.editor.setModelLanguage(model, monacoLanguage)
162-
}
163-
modelRef.current = model
164-
editorRef.current?.setModel(model)
165-
if (editorRef.current != null) {
166-
lastRestoreViewState(editorRef.current, model)
167-
}
168-
return () => {
169-
if (!disposeModels) {
170-
return
171-
}
172-
lastSaveViewState(editorRef.current!, model)
173-
if (existingModel == null) {
174-
// Only dispose if we are the one who created the model
175-
modelRef.current = undefined
176-
model.dispose()
177-
}
178-
}
179-
} else {
180-
modelRef.current = undefined
181-
editorRef.current?.setModel(null)
182-
}
183-
return undefined
184-
}, [monacoLanguage, modelUri, valueRef, lastSaveViewState, lastRestoreViewState, disposeModels, hasValue])
185-
186151
// Create editor
187152
useEffect(() => {
188153
const containerElement = containerRef.current
189154
if (containerElement != null) {
190-
const model = modelRef.current
191155
const editor = createEditor(
192156
containerElement,
193157
{
194-
model,
195-
// We need to pass options here due to https://github.com/microsoft/monaco-editor/issues/2873
196-
...allOptions,
158+
automaticLayout: true,
197159
// We need to override all IStandaloneEditorConstructionOptions fields to prevent conflicts with proper editor options (especially `language`)
198160
value: undefined,
199161
language: undefined,
@@ -206,9 +168,6 @@ function MonacoEditor ({
206168
}
207169
)
208170
editorRef.current = editor
209-
if (model != null) {
210-
lastRestoreViewState(editor, model)
211-
}
212171

213172
if (ref != null) {
214173
if (typeof ref === 'function') {
@@ -224,15 +183,65 @@ function MonacoEditor ({
224183
}
225184
}
226185
return undefined
227-
// eslint-disable-next-line react-hooks/exhaustive-deps
186+
// eslint-disable-next-line react-hooks/exhaustive-deps
228187
}, [])
229188

189+
// Create/Update model
190+
useEffect(() => {
191+
if (fileUri == null && !hasValue) {
192+
modelRef.current = undefined
193+
editorRef.current!.setModel(null)
194+
return
195+
}
196+
let cancelled = false
197+
async function updateModel () {
198+
modelRef.current = undefined
199+
editorRef.current!.setModel(null)
200+
setModelReady(false)
201+
202+
const value = valueRef.current
203+
let modelIRef: IReference<ITextFileEditorModel> | undefined
204+
let model: monaco.editor.ITextModel
205+
if (fileUri != null) {
206+
modelIRef = await createModelReference(monaco.Uri.parse(fileUri), value!)
207+
if (cancelled) {
208+
modelIRef.dispose()
209+
return () => {}
210+
}
211+
model = modelIRef.object.textEditorModel!
212+
if (monacoLanguage != null && model.getLanguageId() !== monacoLanguage) {
213+
monaco.editor.setModelLanguage(model, monacoLanguage)
214+
}
215+
} else {
216+
model = monaco.editor.createModel(value!, monacoLanguage)
217+
}
218+
219+
modelRef.current = model
220+
setModelReady(true)
221+
editorRef.current!.setModel(model)
222+
if (editorRef.current != null) {
223+
lastRestoreViewState(editorRef.current, model)
224+
}
225+
return () => {
226+
if (editorRef.current != null) {
227+
lastSaveViewState(editorRef.current, model)
228+
}
229+
modelRef.current = undefined
230+
}
231+
}
232+
const disposePromise = updateModel()
233+
return () => {
234+
cancelled = true
235+
disposePromise.then(dispose => dispose(), console.error)
236+
}
237+
}, [monacoLanguage, fileUri, valueRef, lastSaveViewState, lastRestoreViewState, hasValue])
238+
230239
// Update value
231240
useEffect(() => {
232-
if (fixedCode != null) {
233-
const model = modelRef.current!
241+
if (modelReady && fixedCode != null) {
242+
const model = modelRef.current
234243
const editor = editorRef.current!
235-
if (fixedCode !== model.getValue()) {
244+
if (model != null && fixedCode !== model.getValue()) {
236245
preventTriggerChangeEventRef.current = true
237246
console.debug('Replacing whole editor content')
238247
editor.pushUndoStop()
@@ -241,7 +250,7 @@ function MonacoEditor ({
241250
preventTriggerChangeEventRef.current = false
242251
}
243252
}
244-
}, [fixedCode])
253+
}, [fixedCode, modelReady])
245254

246255
// Update options from props
247256
useEffect(() => {
@@ -259,6 +268,9 @@ function MonacoEditor ({
259268

260269
// Update markers
261270
useEffect(() => {
271+
if (!modelReady) {
272+
return
273+
}
262274
const model = modelRef.current
263275
if (markers != null && model != null) {
264276
monaco.editor.setModelMarkers(model, 'customMarkers', markers)
@@ -267,7 +279,7 @@ function MonacoEditor ({
267279
}
268280
}
269281
return undefined
270-
}, [markers])
282+
}, [markers, modelReady])
271283

272284
// Call onChange callback
273285
useEffect(() => {

0 commit comments

Comments
 (0)