From fbbd8a9b2f5adb95106d63fd396840a808c14480 Mon Sep 17 00:00:00 2001 From: Milan Doslic Date: Wed, 12 Mar 2025 17:19:00 +0100 Subject: [PATCH] [markdown] IJPL-177111 make RPC for Markdown link opening GitOrigin-RevId: 190ccbebb108dc279f8113551afd164325d4a02b --- .../backend/intellij.markdown.backend.iml | 34 ++++++ .../resources/intellij.markdown.backend.xml | 5 + .../providers/LinkOpenerApiProvider.kt | 14 +++ .../MarkdownLinkOpenerRemoteApiImpl.kt | 114 ++++++++++++++++++ .../frontend/intellij.markdown.frontend.iml | 27 +++++ plugins/markdown/core/intellij.markdown.iml | 27 +++++ .../service/MarkdownLinkOpenerRemoteApi.kt | 26 ++++ 7 files changed, 247 insertions(+) create mode 100644 plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/providers/LinkOpenerApiProvider.kt create mode 100644 plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/services/MarkdownLinkOpenerRemoteApiImpl.kt create mode 100644 plugins/markdown/core/src/org/intellij/plugins/markdown/service/MarkdownLinkOpenerRemoteApi.kt diff --git a/plugins/markdown/core/backend/intellij.markdown.backend.iml b/plugins/markdown/core/backend/intellij.markdown.backend.iml index 544f6e410563..ff2081e18ae9 100644 --- a/plugins/markdown/core/backend/intellij.markdown.backend.iml +++ b/plugins/markdown/core/backend/intellij.markdown.backend.iml @@ -1,5 +1,31 @@ + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + $MAVEN_REPOSITORY$/jetbrains/fleet/rhizomedb-compiler-plugin/2.1.10-0.1/rhizomedb-compiler-plugin-2.1.10-0.1.jar + $MAVEN_REPOSITORY$/com/jetbrains/fleet/rpc-compiler-plugin/2.1.10-0.1/rpc-compiler-plugin-2.1.10-0.1.jar + + + + + + + + @@ -16,5 +42,13 @@ + + + + + + + + \ No newline at end of file diff --git a/plugins/markdown/core/backend/resources/intellij.markdown.backend.xml b/plugins/markdown/core/backend/resources/intellij.markdown.backend.xml index 0f2813495ee4..084ed35c13f4 100644 --- a/plugins/markdown/core/backend/resources/intellij.markdown.backend.xml +++ b/plugins/markdown/core/backend/resources/intellij.markdown.backend.xml @@ -1,5 +1,10 @@ + + + + \ No newline at end of file diff --git a/plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/providers/LinkOpenerApiProvider.kt b/plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/providers/LinkOpenerApiProvider.kt new file mode 100644 index 000000000000..9a45f78b8c98 --- /dev/null +++ b/plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/providers/LinkOpenerApiProvider.kt @@ -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()) { + MarkdownLinkOpenerRemoteApiImpl() + } + } +} \ No newline at end of file diff --git a/plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/services/MarkdownLinkOpenerRemoteApiImpl.kt b/plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/services/MarkdownLinkOpenerRemoteApiImpl.kt new file mode 100644 index 000000000000..eba4115f6a35 --- /dev/null +++ b/plugins/markdown/core/backend/src/org/intellij/plugins/markdown/backend/services/MarkdownLinkOpenerRemoteApiImpl.kt @@ -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 { + 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() + 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 + } + } +} \ No newline at end of file diff --git a/plugins/markdown/core/frontend/intellij.markdown.frontend.iml b/plugins/markdown/core/frontend/intellij.markdown.frontend.iml index e9e81b98116e..c8740bf78756 100644 --- a/plugins/markdown/core/frontend/intellij.markdown.frontend.iml +++ b/plugins/markdown/core/frontend/intellij.markdown.frontend.iml @@ -1,5 +1,31 @@ + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + $MAVEN_REPOSITORY$/jetbrains/fleet/rhizomedb-compiler-plugin/2.1.10-0.1/rhizomedb-compiler-plugin-2.1.10-0.1.jar + $MAVEN_REPOSITORY$/com/jetbrains/fleet/rpc-compiler-plugin/2.1.10-0.1/rpc-compiler-plugin-2.1.10-0.1.jar + + + + + + + + @@ -15,5 +41,6 @@ + \ No newline at end of file diff --git a/plugins/markdown/core/intellij.markdown.iml b/plugins/markdown/core/intellij.markdown.iml index 6ab959c1912f..7baa00073b8b 100644 --- a/plugins/markdown/core/intellij.markdown.iml +++ b/plugins/markdown/core/intellij.markdown.iml @@ -1,5 +1,31 @@ + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + $MAVEN_REPOSITORY$/jetbrains/fleet/rhizomedb-compiler-plugin/2.1.10-0.1/rhizomedb-compiler-plugin-2.1.10-0.1.jar + $MAVEN_REPOSITORY$/com/jetbrains/fleet/rpc-compiler-plugin/2.1.10-0.1/rpc-compiler-plugin-2.1.10-0.1.jar + + + + + + + + @@ -47,5 +73,6 @@ + diff --git a/plugins/markdown/core/src/org/intellij/plugins/markdown/service/MarkdownLinkOpenerRemoteApi.kt b/plugins/markdown/core/src/org/intellij/plugins/markdown/service/MarkdownLinkOpenerRemoteApi.kt new file mode 100644 index 000000000000..e29faf138898 --- /dev/null +++ b/plugins/markdown/core/src/org/intellij/plugins/markdown/service/MarkdownLinkOpenerRemoteApi.kt @@ -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 { + suspend fun openFile(projectId: ProjectId, uri: String) + suspend fun collectHeaders(projectId: ProjectId?, uri: String): List + suspend fun guessProjectForUri(uri: String): ProjectId? + suspend fun navigateToHeader(projectId: ProjectId, headerInfo: MarkdownHeaderInfo) + + companion object { + @JvmStatic + suspend fun getInstance(): MarkdownLinkOpenerRemoteApi { + return RemoteApiProviderService.resolve(remoteApiDescriptor()) + } + } +} \ No newline at end of file