Problems with HTMLFileEditor

I’m trying to show some HTML in an editor, and I’d like to be able to replace the HTML in-place in the same editor. I started out by using HTMLEditorProvider.openEditor, but quickly found that there seems to be no way to load new HTML in the current editor. If anyone knows of a way to do this, it would be greatly appreciated since I could then work around all the problems below.

In order to support this, I vendored my own versions of HTMLEditorProvider and HTMLFileEditor, which aren’t very much code. It’s easy to avoid conflicts with the built-in ones while reusing the same FileType for the LightVirtualFile, because the provider relies on a key that it has placed on the HTMLFileEditor. So far so good. Now I can look to see if an editor for the same HTML is already open, and replace the HTML in it.

However, the original bug I found was that if I then drag that editor to another tab group, the reloading no longer works, and I have no idea why. I was creating a repro repository for that problem when I found another worse one: using the repro test plugin, whenever I drag the editor to another tab group, I get the exception below and the project is instantly closed, dumping me back to the welcome screen. I don’t seem to be able to do anything to control that, since there’s none of my code at all in that stack trace.

Can anyone help with any of the above? The repro project is at: GitHub - cmf/html-load-problem, you can test it by running the “Test HTML loading” action. The first time that is run it will open a new editor, and subsequent invocations will update that editor.

If these are genuine bugs, I can report them in YouTrack.

2025-04-02 17:39:43,409 [  23544] SEVERE - #c.i.o.a.TransactionGuardImpl - Write-unsafe context! Model changes are allowed from write-safe contexts only. Please ensure you're using invokeLater/invokeAndWait with a correct modality state (not "any"). See TransactionGuard documentation for details.
  current modality=ModalityState:{[com.intellij.openapi.progress.impl.JobProviderWithOwnerContext@3e8ad84]}
java.lang.Throwable: Write-unsafe context! Model changes are allowed from write-safe contexts only. Please ensure you're using invokeLater/invokeAndWait with a correct modality state (not "any"). See TransactionGuard documentation for details.
  current modality=ModalityState:{[com.intellij.openapi.progress.impl.JobProviderWithOwnerContext@3e8ad84]}
	at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376)
	at com.intellij.openapi.application.TransactionGuardImpl.assertWriteSafeContext(TransactionGuardImpl.java:152)
	at com.intellij.openapi.vfs.newvfs.RefreshSessionImpl.<init>(RefreshSessionImpl.java:62)
	at com.intellij.openapi.vfs.newvfs.RefreshSessionImpl.<init>(RefreshSessionImpl.java:76)
	at com.intellij.openapi.vfs.newvfs.RefreshQueueImpl.processEvents(RefreshQueueImpl.java:195)
	at com.intellij.configurationStore.SaveSessionProducerManager$saveSessions$3.invoke(SaveSessionProducerManager.kt:88)
	at com.intellij.configurationStore.SaveSessionProducerManager$saveSessions$3.invoke(SaveSessionProducerManager.kt:87)
	at com.intellij.openapi.progress.CoroutinesKt.blockingContextInner(coroutines.kt:339)
	at com.intellij.openapi.progress.CoroutinesKt$blockingContext$2.invokeSuspend(coroutines.kt:232)
	at com.intellij.openapi.progress.CoroutinesKt$blockingContext$2.invoke(coroutines.kt)
	at com.intellij.openapi.progress.CoroutinesKt$blockingContext$2.invoke(coroutines.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:62)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:261)
	at com.intellij.openapi.progress.CoroutinesKt.blockingContext(coroutines.kt:231)
	at com.intellij.configurationStore.SaveSessionProducerManager.saveSessions(SaveSessionProducerManager.kt:87)
	at com.intellij.configurationStore.ProjectSaveSessionProducerManager.saveAndValidate(ProjectSaveSessionProducerManager.kt:15)
	at com.intellij.configurationStore.ProjectStoreImpl$doSave$2$1.invokeSuspend(ProjectStoreImpl.kt:376)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith$$$capture(ContinuationImpl.kt:33)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
	at --- Async.Stack.Trace --- (captured by IntelliJ IDEA debugger)
	at kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner.<init>(DebugProbesImpl.kt:531)
	at kotlinx.coroutines.debug.internal.DebugProbesImpl.createOwner(DebugProbesImpl.kt:510)
	at kotlinx.coroutines.debug.internal.DebugProbesImpl.probeCoroutineCreated$kotlinx_coroutines_core(DebugProbesImpl.kt:497)
	at kotlin.coroutines.jvm.internal.DebugProbesKt.probeCoroutineCreated(DebugProbes.kt:7)
	at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:161)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:21)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:88)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:123)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:52)
	at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:43)
	at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
	at com.intellij.configurationStore.ProjectStoreImpl$doSave$2.invokeSuspend(ProjectStoreImpl.kt:368)
	at com.intellij.configurationStore.ProjectStoreImpl$doSave$2.invoke(ProjectStoreImpl.kt)
	at com.intellij.configurationStore.ProjectStoreImpl$doSave$2.invoke(ProjectStoreImpl.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:62)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:261)
	at com.intellij.configurationStore.ProjectStoreImpl.doSave$intellij_platform_configurationStore_impl(ProjectStoreImpl.kt:367)
	at com.intellij.configurationStore.ComponentStoreImpl.save$suspendImpl(ComponentStoreImpl.kt:255)
	at com.intellij.configurationStore.ComponentStoreImpl.save(ComponentStoreImpl.kt)
	at com.intellij.configurationStore.StoreUtilKt$saveSettings$2.invokeSuspend(storeUtil.kt:107)
	at com.intellij.configurationStore.StoreUtilKt$saveSettings$2.invoke(storeUtil.kt)
	at com.intellij.configurationStore.StoreUtilKt$saveSettings$2.invoke(storeUtil.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:62)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:163)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at com.intellij.configurationStore.StoreUtilKt.saveSettings(storeUtil.kt:106)
	at com.intellij.configurationStore.StoreUtilKt$saveSettings$1.invokeSuspend(storeUtil.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith$$$capture(ContinuationImpl.kt:33)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
	at --- Async.Stack.Trace --- (captured by IntelliJ IDEA debugger)
	at kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner.<init>(DebugProbesImpl.kt:531)
	at kotlinx.coroutines.debug.internal.DebugProbesImpl.createOwner(DebugProbesImpl.kt:510)
	at kotlinx.coroutines.debug.internal.DebugProbesImpl.probeCoroutineCreated$kotlinx_coroutines_core(DebugProbesImpl.kt:497)
	at kotlin.coroutines.jvm.internal.DebugProbesKt.probeCoroutineCreated(DebugProbes.kt:7)
	at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:161)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:21)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:88)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:123)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:87)
	at kotlinx.coroutines.BuildersKt.async(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:78)
	at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport$runWithModalProgressBlockingInternal$2.invoke(PlatformTaskSupport.kt:136)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport$runWithModalProgressBlockingInternal$2.invoke(PlatformTaskSupport.kt:131)
	at com.intellij.openapi.application.impl.ModalityKt.inModalContext(modality.kt:12)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport.runWithModalProgressBlockingInternal(PlatformTaskSupport.kt:131)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport.access$runWithModalProgressBlockingInternal(PlatformTaskSupport.kt:44)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport$runWithModalProgressBlockingInternal$1.invoke(PlatformTaskSupport.kt:116)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport$runWithModalProgressBlockingInternal$1.invoke(PlatformTaskSupport.kt:112)
	at com.intellij.openapi.progress.ContextKt.prepareThreadContext(context.kt:85)
	at com.intellij.openapi.progress.impl.PlatformTaskSupport.runWithModalProgressBlockingInternal(PlatformTaskSupport.kt:112)
	at com.intellij.platform.ide.progress.TasksKt.runWithModalProgressBlocking(tasks.kt:178)
	at com.intellij.configurationStore.SaveAndSyncHandlerImpl.saveSettingsUnderModalProgress(SaveAndSyncHandlerImpl.kt:282)
	at com.intellij.openapi.project.impl.ProjectManagerImpl.closeProject(ProjectManagerImpl.kt:384)
	at com.intellij.openapi.project.impl.ProjectManagerImpl.closeProject$default(ProjectManagerImpl.kt:328)
	at com.intellij.openapi.project.impl.ProjectManagerImpl.closeAndDispose(ProjectManagerImpl.kt:435)
	at com.intellij.openapi.wm.impl.CloseProjectWindowHelper.closeProjectAndShowWelcomeFrameIfNoProjectOpened(CloseProjectWindowHelper.kt:57)
	at com.intellij.openapi.wm.impl.CloseProjectWindowHelper.windowClosing(CloseProjectWindowHelper.kt:44)
	at com.intellij.openapi.wm.impl.ProjectFrameHelper.windowClosing(ProjectFrameHelper.kt:428)
	at com.intellij.openapi.wm.impl.WindowCloseListener.windowClosing(ProjectFrameHelper.kt:451)
	at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
	at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
	at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
	at java.desktop/java.awt.Window.processWindowEvent(Window.java:2115)
	at java.desktop/javax.swing.JFrame.processWindowEvent(JFrame.java:298)
	at java.desktop/java.awt.Window.processEvent(Window.java:2074)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5032)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2810)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4860)
	at jcef/org.cef.browser.CefBrowser_N$1.run(CefBrowser_N.java:156)
	at java.desktop/java.awt.event.InvocationEvent.dispatch$$$capture(InvocationEvent.java:318)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java)
	at --- Async.Stack.Trace --- (captured by IntelliJ IDEA debugger)
	at java.desktop/java.awt.event.InvocationEvent.<init>(InvocationEvent.java:291)
	at java.desktop/java.awt.event.InvocationEvent.<init>(InvocationEvent.java:177)
	at java.desktop/java.awt.EventQueue.invokeLater(EventQueue.java:1327)
	at java.desktop/javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1421)
	at jcef/org.cef.browser.CefBrowser_N.doClose(CefBrowser_N.java:148)
	at jcef/org.cef.browser.CefBrowserOsrWithHandler.doClose(CefBrowserOsrWithHandler.java:18)
	at jcef/org.cef.CefClient.doClose(CefClient.java:806)

These are the test IDE details:

=== About ===
Build version: IntelliJ IDEA 2024.2.5 Build: #IC-242.24807.4 November 26, 2024
Theme: Dark
JRE: 21.0.5+13-b509.30, JetBrains s.r.o.
JVM: 21.0.5+13-b509.30, OpenJDK 64-Bit Server VM, JetBrains s.r.o.
Operating System: Mac OS X 15.3.2 (aarch64)
idea.config.path=/Users/colin/dev/html-load-problem/build/idea-sandbox/IC-2024.2.5/config
idea.system.path=/Users/colin/dev/html-load-problem/build/idea-sandbox/IC-2024.2.5/system
idea.plugins.path=/Users/colin/dev/html-load-problem/build/idea-sandbox/IC-2024.2.5/plugins
idea.log.path=/Users/colin/dev/html-load-problem/build/idea-sandbox/IC-2024.2.5/log

=== System ===
Number of CPU: 10
Used memory: 377Mb 
Free memory: 180Mb 
Total memory: 558Mb 
Maximum available memory: 2048Mb

=== Displays ===
Display 0: 5120x2880; scale: 200%, bounds: 2560x1440 @ (0; 0), insets: (25; 0; 0; 0)

=== Plugins ===
Custom plugins: [Html-load-problem (1.0-SNAPSHOT)]
Disabled plugins:[]

=== Project ===
Project trusted: true

=== Garbage Collection ===
Collector G1 Young Generation: count 26, total time 108 ms
Collector G1 Concurrent GC: count 18, total time 44 ms
Collector G1 Old Generation: count 0, total time 0 ms

I just realised after my edit to add the troubleshooting details, that the repro was using 2024.2. I’ve updated it to 2025.1 EAP, and now I see the original bug - when the HTML pane is dragged from one tab group to another, the reload no longer works, and it looks like the editor scale is messed up - the HTML content appears much larger than in the original editor.