From e2a6d9082df263b65bd92f5a4f24bc63a709307e Mon Sep 17 00:00:00 2001 From: Ilia Permiashkin Date: Mon, 3 Aug 2026 13:37:26 +0000 Subject: [PATCH] [markdown] IJPL-250785 Markdown editor: High CPU from run-configuration lookups per code span Merge-request: IJ-MR-214471 Merged-by: Ilia Permiashkin Merge-request: IJ-MR-215424 Merged-by: Ilia Permiashkin GitOrigin-RevId: 9b5366e0f6b3878cc5ee04953e659a7162e5a479 --- .../MarkdownRunLineMarkersProvider.kt | 43 ++++++++++++------- ...MarkdownCodeSpanConfigurationRunnerTest.kt | 3 +- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/jcef/commandRunner/MarkdownRunLineMarkersProvider.kt b/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/jcef/commandRunner/MarkdownRunLineMarkersProvider.kt index ba28ce620ec9..ebb230f716f1 100644 --- a/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/jcef/commandRunner/MarkdownRunLineMarkersProvider.kt +++ b/plugins/markdown/core/src/org/intellij/plugins/markdown/extensions/jcef/commandRunner/MarkdownRunLineMarkersProvider.kt @@ -14,6 +14,9 @@ import com.intellij.openapi.project.DumbAware import com.intellij.openapi.util.NlsSafe import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.nextLeaf import org.intellij.plugins.markdown.MarkdownBundle import org.intellij.plugins.markdown.MarkdownUsageCollector.RUNNER_EXECUTED @@ -23,6 +26,7 @@ import org.intellij.plugins.markdown.extensions.jcef.commandRunner.CommandRunner import org.intellij.plugins.markdown.extensions.jcef.commandRunner.CommandRunnerExtension.Companion.trimPrompt import org.intellij.plugins.markdown.injection.aliases.CodeFenceLanguageGuesser import org.intellij.plugins.markdown.lang.MarkdownElementTypes +import org.intellij.plugins.markdown.lang.MarkdownLanguage import org.intellij.plugins.markdown.lang.MarkdownTokenTypes import org.intellij.plugins.markdown.lang.psi.impl.MarkdownCodeFence import org.intellij.plugins.markdown.lang.psi.util.hasType @@ -92,21 +96,12 @@ internal class MarkdownRunLineMarkersProvider: RunLineMarkerContributor(), DumbA val text = elementText.trim() if (text.isBlank()) return null - val codeSpans = getAllCodeSpansOnLine(element) - if (codeSpans.firstOrNull() != element) return null - - val configurations = codeSpans - .asSequence() - .map { getText(it).trim() to it } - .filter { it.first.isNotBlank() } - .distinctBy { it.first } - .flatMap { (text, host) -> - MarkdownCodeSpanConfigurationContextSearcher - .findAllConfigurations(text, host) - .mapNotNull { it.findExisting() ?: it.getConfiguration() } - .distinctBy { it.uniqueID } - } - .toList() + val configurations = CachedValuesManager.getCachedValue(element) { + CachedValueProvider.Result.create( + getConfigurations(element), + PsiModificationTracker.getInstance(element.project).forLanguages { !it.isKindOf(MarkdownLanguage.INSTANCE) } + ) + } if (configurations.isEmpty()) return null val actions = configurations.map(::RunConfigurationAction).toTypedArray() @@ -116,6 +111,24 @@ internal class MarkdownRunLineMarkersProvider: RunLineMarkerContributor(), DumbA ) { MarkdownBundle.message("markdown.runner.launch.command", text) } } + private fun getConfigurations(element: PsiElement): List { + val codeSpans = getAllCodeSpansOnLine(element) + if (codeSpans.firstOrNull() != element) return emptyList() + + return codeSpans + .asSequence() + .map { getText(it).trim() to it } + .filter { it.first.isNotBlank() } + .distinctBy { it.first } + .flatMap { (text, host) -> + MarkdownCodeSpanConfigurationContextSearcher + .findAllConfigurations(text, host) + .mapNotNull { it.getConfiguration() } + .distinctBy { it.uniqueID } + } + .toList() + } + private fun getText(element: PsiElement): @NlsSafe String { if (element.hasType(MarkdownTokenTypes.CODE_FENCE_CONTENT)) return element.text.trim() if (element.hasType(MarkdownTokenTypes.BACKTICK)) { diff --git a/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/MarkdownCodeSpanConfigurationRunnerTest.kt b/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/MarkdownCodeSpanConfigurationRunnerTest.kt index 8d3f4f61c9e3..bb068ecf7c86 100644 --- a/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/MarkdownCodeSpanConfigurationRunnerTest.kt +++ b/plugins/markdown/test/src/org/intellij/plugins/markdown/extensions/MarkdownCodeSpanConfigurationRunnerTest.kt @@ -19,7 +19,8 @@ internal class MarkdownCodeSpanConfigurationRunnerTest : LightJavaCodeInsightFix assertEquals(1, getPopupActionCount(markers)) } - fun `test code span run marker is not shown for absent Java class`() { + fun `test code span run marker is not shown for non-runnable Java class`() { + myFixture.addClass("public class JavaClass {}") configureMarkdown("`JavaClass`") val markers = findRunMarkers()