[markdown] IJPL-177111 make RPC for Markdown link opening

GitOrigin-RevId: 190ccbebb108dc279f8113551afd164325d4a02b
This commit is contained in:
Milan Doslic
2025-04-15 07:21:52 +00:00
committed by intellij-monorepo-bot
parent b33938c543
commit fbbd8a9b2f
7 changed files with 247 additions and 0 deletions
@@ -1,5 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="2.0" />
<stringArg name="languageVersion" arg="2.0" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>
<arg>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</arg>
<arg>$MAVEN_REPOSITORY$/jetbrains/fleet/rhizomedb-compiler-plugin/2.1.10-0.1/rhizomedb-compiler-plugin-2.1.10-0.1.jar</arg>
<arg>$MAVEN_REPOSITORY$/com/jetbrains/fleet/rpc-compiler-plugin/2.1.10-0.1/rpc-compiler-plugin-2.1.10-0.1.jar</arg>
</args>
</arrayArg>
<arrayArg name="pluginOptions" />
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@@ -16,5 +42,13 @@
<orderEntry type="module" module-name="intellij.platform.diagnostic" />
<orderEntry type="library" name="jetbrains.markdown" level="project" />
<orderEntry type="module" module-name="intellij.platform.backend" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.platform.kernel.backend" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.analysis" />
<orderEntry type="module" module-name="intellij.platform.ide.core" />
<orderEntry type="module" module-name="intellij.platform.project" />
<orderEntry type="module" module-name="intellij.driver.sdk" />
<orderEntry type="module" module-name="intellij.platform.lang" />
<orderEntry type="module" module-name="intellij.platform.ide.core.impl" />
</component>
</module>
@@ -1,5 +1,10 @@
<idea-plugin>
<dependencies>
<plugin id="com.intellij.platform.experimental.backend"/>
<module name="intellij.platform.kernel.backend"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<platform.rpc.backend.remoteApiProvider
implementation="org.intellij.plugins.markdown.backend.providers.LinkOpenerApiProvider"/>
</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.MarkdownLinkOpenerRemoteApiImpl
import org.intellij.plugins.markdown.service.MarkdownLinkOpenerRemoteApi
private class LinkOpenerApiProvider : RemoteApiProvider {
override fun RemoteApiProvider.Sink.remoteApis() {
remoteApi(remoteApiDescriptor<MarkdownLinkOpenerRemoteApi>()) {
MarkdownLinkOpenerRemoteApiImpl()
}
}
}
@@ -0,0 +1,114 @@
// 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.backend.services
import com.intellij.ide.actions.OpenFileAction
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.guessProjectForFile
import com.intellij.openapi.util.SystemInfo
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 com.intellij.platform.project.projectId
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.PsiNavigateUtil
import com.intellij.util.UriUtil
import org.intellij.plugins.markdown.dto.MarkdownHeaderInfo
import org.intellij.plugins.markdown.lang.index.HeaderAnchorIndex
import org.intellij.plugins.markdown.mapper.MarkdownHeaderMapper
import org.intellij.plugins.markdown.service.MarkdownLinkOpenerRemoteApi
import org.intellij.plugins.markdown.ui.preview.MarkdownEditorWithPreview
import java.net.URI
import java.net.URISyntaxException
import java.nio.file.Path
internal class MarkdownLinkOpenerRemoteApiImpl : MarkdownLinkOpenerRemoteApi {
companion object {
private val logger: Logger = Logger.getInstance(MarkdownLinkOpenerRemoteApiImpl::class.java)
}
override suspend fun openFile(projectId: ProjectId, uri: String) {
val project = projectId.findProject()
val parsedUri = parseUri(uri)?: return
OpenFileAction.openFile(parsedUri.path, project)
}
private fun parseUri(uri: String): URI? {
return try {
URI(uri)
} catch (e: URISyntaxException) {
logger.warn(e)
return null
}
}
override suspend fun collectHeaders(projectId: ProjectId?, uri: String): List<MarkdownHeaderInfo> {
val project = projectId?.findProject() ?: return emptyList()
val parsedUri = parseUri(uri)?: return emptyList()
val targetFile = parsedUri.findVirtualFile() ?: return emptyList()
val anchor = parsedUri.fragment
return runReadAction {
if (DumbService.isDumb(project)) {
return@runReadAction emptyList()
}
val scope = when (val file = PsiManager.getInstance(project).findFile(targetFile)) {
null -> GlobalSearchScope.EMPTY_SCOPE
else -> GlobalSearchScope.fileScope(file)
}
return@runReadAction HeaderAnchorIndex.collectHeaders(project, scope, anchor).map(MarkdownHeaderMapper::map)
}
}
override suspend fun guessProjectForUri(uri: String): ProjectId? {
val parsedUri = parseUri(uri) ?: return null
val file = parsedUri.findVirtualFile() ?: return null
val project = guessProjectForFile(file) ?: return null
return project.projectId()
}
override suspend fun navigateToHeader(projectId: ProjectId, headerInfo: MarkdownHeaderInfo) {
val uri = createFileUri(headerInfo.filePath) ?: return
val file = uri.findVirtualFile() ?: return
val project = projectId.findProject()
val manager = FileEditorManager.getInstance(project)
val openedEditors = manager.getEditorList(file).filterIsInstance<MarkdownEditorWithPreview>()
val psiFile = PsiUtilCore.getPsiFile(project, file)
val element = psiFile.findElementAt(headerInfo.textOffset) ?: return
if (openedEditors.isNotEmpty()) {
openedEditors.forEach {
// Ensures the element is located as in the original implementation
PsiUtilCore.getElementAtOffset(psiFile, element.textOffset)
PsiNavigateUtil.navigate(element, true)
}
return
}
val descriptor = OpenFileDescriptor(project, file, element.textOffset)
manager.openEditor(descriptor, true)
}
private fun URI.findVirtualFile(): VirtualFile? {
val actualPath = when {
SystemInfo.isWindows -> UriUtil.trimLeadingSlashes(path)
else -> path
}
val path = Path.of(actualPath)
return VfsUtil.findFile(path, true)
}
private fun createFileUri(link: String): URI? {
return try {
URI("file", null, link, null)
} catch (e: URISyntaxException) {
logger.warn(e)
null
}
}
}
@@ -1,5 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="2.0" />
<stringArg name="languageVersion" arg="2.0" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>
<arg>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</arg>
<arg>$MAVEN_REPOSITORY$/jetbrains/fleet/rhizomedb-compiler-plugin/2.1.10-0.1/rhizomedb-compiler-plugin-2.1.10-0.1.jar</arg>
<arg>$MAVEN_REPOSITORY$/com/jetbrains/fleet/rpc-compiler-plugin/2.1.10-0.1/rpc-compiler-plugin-2.1.10-0.1.jar</arg>
</args>
</arrayArg>
<arrayArg name="pluginOptions" />
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@@ -15,5 +41,6 @@
<orderEntry type="module" module-name="intellij.platform.project" />
<orderEntry type="module" module-name="intellij.platform.ide.util.netty" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="module" module-name="intellij.platform.kernel" />
</component>
</module>
@@ -1,5 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="2.0" />
<stringArg name="languageVersion" arg="2.0" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>
<arg>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</arg>
<arg>$MAVEN_REPOSITORY$/jetbrains/fleet/rhizomedb-compiler-plugin/2.1.10-0.1/rhizomedb-compiler-plugin-2.1.10-0.1.jar</arg>
<arg>$MAVEN_REPOSITORY$/com/jetbrains/fleet/rpc-compiler-plugin/2.1.10-0.1/rpc-compiler-plugin-2.1.10-0.1.jar</arg>
</args>
</arrayArg>
<arrayArg name="pluginOptions" />
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@@ -47,5 +73,6 @@
<orderEntry type="module" module-name="intellij.platform.project" />
<orderEntry type="library" name="kotlinx-serialization-core" level="project" />
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
<orderEntry type="module" module-name="intellij.platform.kernel" />
</component>
</module>
@@ -0,0 +1,26 @@
// 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.platform.project.ProjectId
import com.intellij.platform.rpc.RemoteApiProviderService
import fleet.rpc.RemoteApi
import fleet.rpc.Rpc
import fleet.rpc.remoteApiDescriptor
import org.intellij.plugins.markdown.dto.MarkdownHeaderInfo
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
@Rpc
interface MarkdownLinkOpenerRemoteApi: RemoteApi<Unit> {
suspend fun openFile(projectId: ProjectId, uri: String)
suspend fun collectHeaders(projectId: ProjectId?, uri: String): List<MarkdownHeaderInfo>
suspend fun guessProjectForUri(uri: String): ProjectId?
suspend fun navigateToHeader(projectId: ProjectId, headerInfo: MarkdownHeaderInfo)
companion object {
@JvmStatic
suspend fun getInstance(): MarkdownLinkOpenerRemoteApi {
return RemoteApiProviderService.resolve(remoteApiDescriptor<MarkdownLinkOpenerRemoteApi>())
}
}
}