11import React , { ForwardedRef , forwardRef , ReactElement , useEffect , useMemo , useRef , useState } from 'react'
22import 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'
57import { useDeepMemo , useLastValueRef , useLastVersion , useThemeColor } from './hooks'
68import './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