[markdown] IJPL-177111 fix IJPL-167949 by adding ProcessImagesExtension

Introduced `ProcessImagesExtension` to handle image resources in the Markdown browser preview. Added a new remote API (`ProjectStructureRemoteApi`) to retrieve project-specific resources.

GitOrigin-RevId: 9a3e8f02e167c23e4954e9ebdaa58a9bfd2330fd
This commit is contained in:
Milan Doslic
2025-04-15 07:21:53 +00:00
committed by intellij-monorepo-bot
parent c896bc7e89
commit 4ba841da7b
6 changed files with 134 additions and 0 deletions
@@ -6,5 +6,7 @@
<extensions defaultExtensionNs="com.intellij">
<platform.rpc.backend.remoteApiProvider
implementation="org.intellij.plugins.markdown.backend.providers.LinkOpenerApiProvider"/>
<platform.rpc.backend.remoteApiProvider
implementation="org.intellij.plugins.markdown.backend.providers.ProjectStructureProvider"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,14 @@
package org.intellij.plugins.markdown.backend.providers
import com.intellij.platform.rpc.backend.RemoteApiProvider
import fleet.rpc.remoteApiDescriptor
import org.intellij.plugins.markdown.backend.services.ProjectStructureRemoteApiImpl
import org.intellij.plugins.markdown.service.ProjectStructureRemoteApi
private class ProjectStructureProvider : RemoteApiProvider {
override fun RemoteApiProvider.Sink.remoteApis() {
remoteApi(remoteApiDescriptor<ProjectStructureRemoteApi>()) {
ProjectStructureRemoteApiImpl()
}
}
}
@@ -0,0 +1,38 @@
package org.intellij.plugins.markdown.backend.services
import com.intellij.ide.vfs.VirtualFileId
import com.intellij.ide.vfs.rpcId
import com.intellij.ide.vfs.virtualFile
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.BaseProjectDirectories
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.project.ProjectId
import com.intellij.platform.project.findProject
import org.intellij.plugins.markdown.service.ProjectStructureRemoteApi
import java.io.File
import java.net.URL
class ProjectStructureRemoteApiImpl : ProjectStructureRemoteApi {
private fun getBaseDirectory(projectId: ProjectId, virtualFileId: VirtualFileId) : VirtualFile?{
val project = projectId.findProject()
val virtualFile = virtualFileId.virtualFile() ?: return null
val baseDirectory = BaseProjectDirectories.getInstance(project).getBaseDirectoryFor(virtualFile)
return baseDirectory
}
override suspend fun getFileByResourceName(resourceName: String, virtualFileId: VirtualFileId?, projectId: ProjectId?): VirtualFileId? {
if (projectId == null || virtualFileId == null) return null
val projectRoot = getBaseDirectory(projectId, virtualFileId)
val resource = if (resourceName.startsWith("file:/")) {
VfsUtil.findFileByIoFile(File(URL(resourceName).path), true)
} else {
projectRoot?.findFileByRelativePath(resourceName)
} ?: return null
return resource.rpcId()
}
companion object {
private val logger: Logger = Logger.getInstance(ProjectStructureRemoteApiImpl::class.java)
}
}
@@ -398,6 +398,7 @@
<browserPreviewExtensionProvider implementation="org.intellij.plugins.markdown.extensions.common.InlineStylesExtension$Provider"/>
<browserPreviewExtensionProvider implementation="org.intellij.plugins.markdown.extensions.jcef.ProcessLinksExtension$Provider"/>
<browserPreviewExtensionProvider implementation="org.intellij.plugins.markdown.extensions.jcef.ProcessImagesExtension$Provider"/>
<browserPreviewExtensionProvider implementation="org.intellij.plugins.markdown.extensions.jcef.commandRunner.CommandRunnerExtension$Provider"/>
<fenceLanguageProvider id="DefaultMermaidCodeFenceLanguageProvider"
@@ -0,0 +1,56 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.extensions.jcef
import com.intellij.ide.vfs.rpcId
import com.intellij.ide.vfs.virtualFile
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.project.projectId
import kotlinx.coroutines.runBlocking
import org.intellij.plugins.markdown.extensions.MarkdownBrowserPreviewExtension
import org.intellij.plugins.markdown.service.ProjectStructureRemoteApi
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel
import org.intellij.plugins.markdown.ui.preview.ResourceProvider
internal class ProcessImagesExtension(
private val baseFile: VirtualFile?,
private val project: Project?
): ResourceProvider, MarkdownBrowserPreviewExtension {
override val resourceProvider: ResourceProvider = this
override fun canProvide(resourceName: String): Boolean {
return hasImageExtension(resourceName)
}
override fun loadResource(resourceName: String): ResourceProvider.Resource? {
val baseFileId = baseFile?.rpcId()
val projectId = project?.projectId()
val resource = runBlocking {
ProjectStructureRemoteApi.getInstance().getFileByResourceName(resourceName, baseFileId, projectId)?.virtualFile()
} ?: return null
return ResourceProvider.loadExternalResource(resource)
}
override fun dispose() = Unit
companion object{
fun hasImageExtension(name: String): Boolean {
val lowerName = name.lowercase()
return lowerName.endsWith(".jpeg") ||
lowerName.endsWith(".jpg") ||
lowerName.endsWith(".png") ||
lowerName.endsWith(".gif") ||
lowerName.endsWith(".bmp") ||
lowerName.endsWith(".svg") ||
lowerName.endsWith(".webp") ||
lowerName.endsWith(".tiff") ||
lowerName.endsWith(".tif")
}
}
class Provider: MarkdownBrowserPreviewExtension.Provider {
override fun createBrowserExtension(panel: MarkdownHtmlPanel): MarkdownBrowserPreviewExtension {
return ProcessImagesExtension(panel.virtualFile, panel.project)
}
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.service
import com.intellij.ide.vfs.VirtualFileId
import com.intellij.platform.project.ProjectId
import com.intellij.platform.rpc.RemoteApiProviderService
import fleet.rpc.RemoteApi
import fleet.rpc.Rpc
import fleet.rpc.remoteApiDescriptor
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
@Rpc
interface ProjectStructureRemoteApi : RemoteApi<Unit> {
suspend fun getFileByResourceName(resourceName: String, virtualFileId: VirtualFileId?, projectId: ProjectId?): VirtualFileId?
companion object {
@JvmStatic
suspend fun getInstance(): ProjectStructureRemoteApi {
return RemoteApiProviderService.resolve(remoteApiDescriptor<ProjectStructureRemoteApi>())
}
}
}