@@ -22,10 +22,12 @@ export type MonacoEditorProps = {
2222 onEditorStartDone ?: ( editorApp ?: EditorApp ) => void ;
2323 onLanguageClientsStartDone ?: ( lcsManager : LanguageClientManager ) => void ;
2424 onTextChanged ?: ( textChanges : TextContents ) => void ;
25- onConfigProcessed ?: ( editorApp ?: EditorApp ) => void ;
25+ onConfigProcessed ?: ( result : { textUpdated : boolean , modelUpdated : boolean } , editorApp ?: EditorApp ) => void ;
2626 onError ?: ( error : Error ) => void ;
2727 onDisposeEditor ?: ( ) => void ;
2828 onDisposeLanguageClient ?: ( ) => void ;
29+ reprocessConfig ?: boolean ;
30+ enforceLanguageClientDispose ?: boolean ;
2931 logLevel ?: LogLevel | number ;
3032}
3133
@@ -61,7 +63,7 @@ const executeQueue = async () => {
6163 const lengthBefore = runQueue . length ;
6264 const queueObj = runQueue . shift ( ) ;
6365 debugLogging ( '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<' ) ;
64- debugLogging ( `QUEUE ${ queueObj ?. id } start: SIZE before: ${ lengthBefore } ` , true ) ;
66+ debugLogging ( `QUEUE ${ queueObj ?. id } start: SIZE before: ${ lengthBefore } ` ) ;
6567 await queueObj ?. func ( queueObj . currentContainer ) ;
6668 debugLogging ( `QUEUE ${ queueObj ?. id } end: SIZE after: ${ runQueue . length } ` ) ;
6769 }
@@ -89,12 +91,9 @@ const stopQueue = () => {
8991 }
9092} ;
9193
92- const debugLogging = ( id : string , useTime ?: boolean ) => {
93- if ( useTime === true ) {
94- logger . debug ( `${ id } : ${ Date . now ( ) } ` ) ;
95- } else {
96- logger . debug ( id ) ;
97- }
94+ const debugLogging = ( id : string ) => {
95+ const now = new Date ( Date . now ( ) )
96+ logger . debug ( `[${ now . getMinutes ( ) } :${ now . getSeconds ( ) } :${ now . getMilliseconds ( ) } ]: ${ id } ` ) ;
9897} ;
9998
10099export const MonacoEditorReactComp : React . FC < MonacoEditorProps > = ( props ) => {
@@ -112,7 +111,9 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
112111 onError,
113112 onDisposeEditor,
114113 onDisposeLanguageClient,
115- logLevel
114+ logLevel,
115+ reprocessConfig,
116+ enforceLanguageClientDispose
116117 } = props ;
117118
118119 const editorAppRef = useRef < EditorApp > ( undefined ) ;
@@ -124,9 +125,11 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
124125 const onTextChangedRef = useRef ( onTextChanged ) ;
125126 const launchingRef = useRef < boolean > ( false ) ;
126127 const editorAppConfigRef = useRef < EditorAppConfig > ( undefined ) ;
128+ const flipReprocessConfigRef = useRef < boolean > ( false ) ;
129+ const flipLanguageClientDisposeRef = useRef < boolean > ( false ) ;
127130
128131 const performErrorHandling = ( error : Error ) => {
129- debugLogging ( `ERROR: ${ error . message } ` , true ) ;
132+ debugLogging ( `ERROR: ${ error . message } ` ) ;
130133 if ( onError ) {
131134 onError ( error ) ;
132135 } else {
@@ -152,15 +155,10 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
152155 apiWrapper = new MonacoVscodeApiWrapper ( vscodeApiConfig ! ) ;
153156 const globalInitFunc = async ( ) => {
154157 try {
155- debugLogging ( 'GLOBAL INIT' , true ) ;
156-
157158 if ( apiWrapper === undefined ) throw new Error ( 'Unexpected error occurred: apiWrapper is not available! Aborting...' ) ;
158159
159160 await apiWrapper . start ( ) ;
160161 onVscodeApiInitDone ?.( apiWrapper ) ;
161-
162- debugLogging ( 'GLOBAL INIT DONE' , true ) ;
163-
164162 runQueueLock = false ;
165163 } catch ( error ) {
166164 performErrorHandling ( error as Error ) ;
@@ -176,16 +174,14 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
176174
177175 const editorInit = async ( htmlContainer : HTMLElement | null ) => {
178176 try {
179- debugLogging ( 'INIT EDITOR' , true ) ;
180177 // it is possible to run without an editorApp, when the ViewsService or WorkbenchService
181178 if ( haveEditorService ( ) ) {
182179 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
183180 if ( htmlContainer === null || ( htmlContainer !== null && htmlContainer . parentElement === null ) ) {
184- debugLogging ( 'INIT EDITOR: Unable to create editor. HTML container or the parent is missing.' , true ) ;
181+ debugLogging ( 'INIT EDITOR: Unable to create editor. HTML container or the parent is missing.' ) ;
185182 } else {
186183 if ( editorAppRef . current === undefined && ! launchingRef . current ) {
187184 launchingRef . current = true ;
188- debugLogging ( 'INIT EDITOR: Creating editor' , true ) ;
189185
190186 editorAppRef . current = new EditorApp ( editorAppConfigRef . current ) ;
191187 if ( editorAppRef . current . isStarting ( ) === true || editorAppRef . current . isDisposing ( ) === true ) {
@@ -212,30 +208,30 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
212208
213209 onEditorStartDone ?.( editorAppRef . current ) ;
214210 launchingRef . current = false ;
211+ debugLogging ( 'INIT EDITOR: Editor start was successful.' ) ;
215212 } else {
216- debugLogging ( 'INIT EDITOR: Editor already created' , true ) ;
213+ debugLogging ( 'INIT EDITOR: Editor was already started.' ) ;
217214 }
218215 }
219216 } else {
220- debugLogging ( 'INIT EDITOR: Do nothing: Using ViewsService' , true ) ;
217+ debugLogging ( 'INIT EDITOR: Do nothing: Using ViewsService' ) ;
221218 }
222- debugLogging ( 'INIT EDITOR: Done' , true ) ;
223219 } catch ( error ) {
224220 performErrorHandling ( error as Error ) ;
225221 }
226222 } ;
227223
228224 const updateEditorModel = async ( ) => {
229225 try {
230- debugLogging ( 'UPDATE EDITOR MODEL' , true ) ;
231226 if ( ! launchingRef . current && editorAppRef . current ) {
232227 editorAppRef . current . updateCodeResources ( editorAppConfigRef . current ?. codeResources ) ;
233228 updateModelRelatedRefs ( ) ;
234- onConfigProcessed ?.( editorAppRef . current ) ;
229+ onConfigProcessed ?.( { modelUpdated : true , textUpdated : true } , editorAppRef . current ) ;
230+ debugLogging ( 'UPDATE EDITOR MODEL: Model was updated.' ) ;
235231 } else {
236- debugLogging ( 'UPDATE EDITOR MODEL: Not Possible: No editor' , true ) ;
232+ onConfigProcessed ?.( { modelUpdated : false , textUpdated : false } , editorAppRef . current ) ;
233+ debugLogging ( 'UPDATE EDITOR MODEL: No editor is avilable. Model update was not possible.' ) ;
237234 }
238- debugLogging ( 'UPDATE EDITOR MODEL: Done' , true ) ;
239235 } catch ( error ) {
240236 performErrorHandling ( error as Error ) ;
241237 }
@@ -251,25 +247,24 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
251247 const disposeEditor = async ( ) => {
252248 try {
253249 // dispose editor if used
254- debugLogging ( 'DISPOSE' , true ) ;
255-
256250 if ( editorAppRef . current !== undefined ) {
257251 await editorAppRef . current . dispose ( ) ;
258252 editorAppRef . current = undefined ;
259253 onDisposeEditor ?.( ) ;
254+ debugLogging ( 'DISPOSE: EditorApp was disposed' ) ;
260255 } else {
261- debugLogging ( 'DISPOSE: EditorApp is not disposed' , true ) ;
256+ debugLogging ( 'DISPOSE: EditorApp is not disposed' ) ;
262257 }
263- debugLogging ( 'DISPOSE DONE' , true ) ;
264- } catch ( error ) {
258+ } catch ( error ) {
265259 performErrorHandling ( error as Error ) ;
266260 }
267261 } ;
268262
269263 const processConfig = ( ) => {
270- let updateModel = false ;
264+ let modelUpdated = false ;
265+ let textUpdated = false ;
271266 try {
272- debugLogging ( 'CONFIG PROCESSED' , true ) ;
267+ debugLogging ( 'CONFIG PROCESSED: Started' ) ;
273268 if ( ! launchingRef . current && editorAppRef . current ) {
274269 if ( editorAppConfigRef . current ?. codeResources !== undefined ) {
275270 const newModifiedCodeUri = editorAppConfigRef . current . codeResources . modified ?. uri ;
@@ -279,13 +274,14 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
279274 const originalUri = originalCodeUriRef . current !== newOriginalCodeUri ? newOriginalCodeUri : undefined ;
280275 // re-create the editor if the URIs have changed
281276 if ( modifiedUri !== undefined || originalUri !== undefined ) {
282- updateModel = true ;
277+ modelUpdated = true ;
283278 } else {
284279 const newModifiedCode = editorAppConfigRef . current . codeResources . modified ?. text ;
285280 const newOriginalCode = editorAppConfigRef . current . codeResources . original ?. text ;
286281 const modified = modifiedCodeRef . current !== newModifiedCode ? newModifiedCode : undefined ;
287282 const original = originalCodeRef . current !== newOriginalCode ? newOriginalCode : undefined ;
288283 if ( modified !== undefined || original !== undefined ) {
284+ textUpdated = true ;
289285 editorAppRef . current . updateCode ( { modified, original } ) ;
290286 }
291287 }
@@ -301,14 +297,15 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
301297 }
302298 }
303299 }
304- if ( ! updateModel ) {
305- onConfigProcessed ?.( editorAppRef . current ) ;
300+ // notitfy now if no async model update was necessary
301+ if ( ! modelUpdated ) {
302+ onConfigProcessed ?.( { modelUpdated, textUpdated} , editorAppRef . current ) ;
306303 }
307- debugLogging ( 'CONFIG PROCESSED: Done' , true ) ;
304+ debugLogging ( 'CONFIG PROCESSED: Done' ) ;
308305 } catch ( error ) {
309306 performErrorHandling ( error as Error ) ;
310307 }
311- return updateModel ;
308+ return modelUpdated ;
312309 } ;
313310
314311 useEffect ( ( ) => {
@@ -321,66 +318,69 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
321318 editorAppConfigRef . current = editorAppConfig ;
322319 // it is possible to run without an editorApp, when the ViewsService or WorkbenchService
323320 if ( haveEditorService ( ) ) {
324- const updateModel = processConfig ( ) ;
325- if ( updateModel ) {
326- addQueue ( { id : 'model update' , func : updateEditorModel , currentContainer : containerRef . current } ) ;
321+ if ( editorAppRef . current === undefined ) {
322+ addQueue ( { id : 'editorInit' , func : editorInit , currentContainer : containerRef . current } ) ;
327323 } else {
328- if ( editorAppRef . current === undefined ) {
329- addQueue ( { id : 'editorInit' , func : editorInit , currentContainer : containerRef . current } ) ;
330- } else {
331- debugLogging ( 'CHECK EDITOR: Editor already created' , true ) ;
332- }
324+ debugLogging ( 'CHECK EDITOR: Editor already created. No queueing necessary.' ) ;
333325 }
334326 } else {
335- debugLogging ( 'INIT EDITOR: Do nothing: Using ViewsService' , true ) ;
327+ debugLogging ( 'INIT EDITOR: Do nothing: Using ViewsService' ) ;
336328 }
337329 } , [ editorAppConfig ] ) ;
338330
331+ useEffect ( ( ) => {
332+ if ( flipReprocessConfigRef . current !== ( reprocessConfig === true ) ) {
333+ debugLogging ( 'REPROCESS CONFIG: Triggered' ) ;
334+ const updateModel = processConfig ( ) ;
335+ if ( updateModel ) {
336+ addQueue ( { id : 'modelUpdate' , func : updateEditorModel , currentContainer : containerRef . current } ) ;
337+ }
338+ flipReprocessConfigRef . current = ! flipReprocessConfigRef . current ;
339+ } else {
340+ debugLogging ( 'REPROCESS CONFIG: Denied' ) ;
341+ }
342+ } , [ reprocessConfig ] ) ;
343+
339344 useEffect ( ( ) => {
340345 // fast-fail
341346 if ( languageClientConfig === undefined ) return ;
342347
343348 // always try to perform global init. Reason: we cannot ensure order
344349 performGlobalInit ( ) ;
345350
346- if ( languageClientConfig . enforceDispose === true ) {
351+ const lcInitFunc = async ( ) => {
352+ try {
353+ await lcsManager . start ( ) ;
354+ onLanguageClientsStartDone ?.( lcsManager ) ;
355+ } catch ( error ) {
356+ performErrorHandling ( error as Error ) ;
357+ }
358+ } ;
359+ lcsManager . setLogLevel ( languageClientConfig . logLevel ) ;
360+ lcsManager . setConfig ( languageClientConfig ) ;
361+ if ( ! lcsManager . isStarted ( ) ) {
362+ addQueue ( { id :'lcInit' , func : lcInitFunc , currentContainer : containerRef . current } ) ;
363+ } else {
364+ debugLogging ( 'INIT LC: Language client is already running. No need to schedule async start.' ) ;
365+ }
366+ } , [ languageClientConfig ] ) ;
367+
368+ useEffect ( ( ) => {
369+ if ( flipLanguageClientDisposeRef . current !== ( enforceLanguageClientDispose === true ) ) {
347370 const disposeLCFunc = async ( ) => {
348- // dispose editor if used
349371 try {
350- debugLogging ( 'DISPOSE LC ENFORCED' , true ) ;
351-
352372 await lcsManager . dispose ( ) ;
353373 onDisposeLanguageClient ?.( ) ;
354-
355- debugLogging ( 'DISPOSE LC ENFORCED DONE' , true ) ;
356374 } catch ( error ) {
357375 // The language client may throw an error during disposal, but we want to continue anyway
358376 performErrorHandling ( new Error ( `Unexpected error occurred during disposal of the language client: ${ error } ` ) ) ;
359377 }
360378 } ;
361- addQueue ( { id :'dispose lc ' , func : disposeLCFunc , currentContainer : containerRef . current } ) ;
379+ addQueue ( { id :'lcDispose ' , func : disposeLCFunc , currentContainer : containerRef . current } ) ;
362380 } else {
363- const lcInitFunc = async ( ) => {
364- try {
365- debugLogging ( 'INIT LC' , true ) ;
366-
367- await lcsManager . start ( ) ;
368- onLanguageClientsStartDone ?.( lcsManager ) ;
369- debugLogging ( 'INIT LC: Language client started' , true ) ;
370- debugLogging ( 'INIT LC DONE' , true ) ;
371- } catch ( error ) {
372- performErrorHandling ( error as Error ) ;
373- }
374- } ;
375- lcsManager . setLogLevel ( languageClientConfig . logLevel ) ;
376- lcsManager . setConfig ( languageClientConfig ) ;
377- if ( ! lcsManager . isStarted ( ) ) {
378- addQueue ( { id :'lcInit' , func : lcInitFunc , currentContainer : containerRef . current } ) ;
379- } else {
380- debugLogging ( 'INIT LC: Language client is already running. No need to schedule async start.' , true ) ;
381- }
381+ debugLogging ( 'ENFORCE DISPOSE LC: Denied' ) ;
382382 }
383- } , [ languageClientConfig ] ) ;
383+ } , [ enforceLanguageClientDispose ] ) ;
384384
385385 useEffect ( ( ) => {
386386 // this part runs on mount (componentDidMount)
0 commit comments