IDEA-309322, EA-772915 Move image loading out of EDT for ImageEditorImpl

Create a service and use coroutines to freely jump between IO threads and EDT.
To avoid converting too much stuff to Kotlin at once, use dependency
inversion and provide an object for image file loading to ImageEditorImpl
through the new service.

Cancelling the image loader's job through dispose() looks a bit ugly,
but until we move the entire thing to coroutines, it seems to be the only reasonable
way.

GitOrigin-RevId: ce42805acfcb67bbdbb3de869fb7a3606adcf383
This commit is contained in:
Sergei Tachenov
2023-07-27 21:30:21 +03:00
committed by intellij-monorepo-bot
parent 0d9751ac2b
commit 2fd2b39fbf
3 changed files with 76 additions and 8 deletions

View File

@@ -26,8 +26,8 @@ import org.intellij.images.editor.ImageEditor;
import org.intellij.images.editor.ImageZoomModel;
import org.intellij.images.fileTypes.ImageFileTypeManager;
import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActions;
import org.intellij.images.vfs.IfsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
@@ -41,6 +41,7 @@ public final class ImageEditorImpl implements ImageEditor {
private final Project project;
private final VirtualFile file;
private final ImageEditorUI editorUI;
private final @NotNull ImageFileLoader imageFileLoader;
private boolean disposed;
public ImageEditorImpl(@NotNull Project project, @NotNull VirtualFile file) {
@@ -70,17 +71,18 @@ public final class ImageEditorImpl implements ImageEditor {
}
}, this);
imageFileLoader = project.getService(ImageFileService.class).createImageFileLoader(this);
Disposer.register(this, imageFileLoader);
setValue(file);
}
void setValue(VirtualFile file) {
try {
editorUI.setImageProvider(IfsUtil.getImageProvider(file), IfsUtil.getFormat(file));
}
catch (Exception e) {
// Error loading image file
editorUI.setImageProvider(null, null);
}
imageFileLoader.loadFile(file);
}
void setImageProvider(@Nullable ImageDocument.ScaledImageProvider imageProvider, @Nullable String format) {
editorUI.setImageProvider(imageProvider, format);
}
@Override

View File

@@ -0,0 +1,11 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.images.editor.impl
import com.intellij.openapi.Disposable
import com.intellij.openapi.vfs.VirtualFile
internal interface ImageFileLoader : Disposable {
fun loadFile(file: VirtualFile?)
}

View File

@@ -0,0 +1,55 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.images.editor.impl
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import org.intellij.images.vfs.IfsUtil
@Service(Service.Level.APP)
internal class ImageFileService(
private val coroutineScope: CoroutineScope,
) {
fun createImageFileLoader(target: ImageEditorImpl): ImageFileLoader =
ImageFileLoaderImpl(
target,
coroutineScope
)
class ImageFileLoaderImpl(private val target: ImageEditorImpl, childScope: CoroutineScope) : ImageFileLoader {
private val flow = MutableSharedFlow<VirtualFile>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
private val job = childScope.launch(CoroutineName("ImageFileLoader for $target")) {
flow.collectLatest { file ->
val imageProvider = withContext(Dispatchers.IO) { IfsUtil.getImageProvider(file) }
val format = withContext(Dispatchers.IO) { IfsUtil.getFormat(file) }
withContext(Dispatchers.EDT) {
target.setImageProvider(imageProvider, format)
}
}
}
override fun loadFile(file: VirtualFile?) {
if (file == null) {
target.setImageProvider(null, null)
return
}
flow.tryEmit(file)
}
override fun dispose() {
job.cancel()
}
}
}