From 6e2fa02aa7774913aa00da4569c7a7cafda0ad87 Mon Sep 17 00:00:00 2001 From: "Ilia.Shulgin" Date: Mon, 14 Jul 2025 12:41:24 +0200 Subject: [PATCH] [diff] IJPL-190026 Don't call LanguageUtil.getLanguageForPsi on EDT `DiffLangSpecificProvider.findApplicable` is called from EDT, producing a slow operation error in `LanguageUtil.getLanguageForPsi -> ... -> findModuleForFile`. Now `DiffUserDataKeys.LANGUAGE` is set on BGT and `DiffLangSpecificProvider.findApplicable` relies on it. GitOrigin-RevId: dfe87819de3f3782ffcfee1174b0ba3d7dc69ea0 --- platform/diff-impl/api-dump.txt | 9 ++- .../diff/impl/CacheDiffRequestProcessor.java | 4 ++ .../diff/lang/DiffLangSpecificProvider.kt | 8 +-- .../com/intellij/diff/lang/DiffLanguage.kt | 58 +++++++++++++++++++ .../lang/LangDiffIgnoredRangeProvider.java | 22 +------ .../LangSpecificMergeConflictResolver.kt | 9 ++- ...angSpecificMergeConflictResolverWrapper.kt | 2 +- .../util/text/SmartTextDiffProvider.java | 4 +- 8 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 platform/diff-impl/src/com/intellij/diff/lang/DiffLanguage.kt diff --git a/platform/diff-impl/api-dump.txt b/platform/diff-impl/api-dump.txt index 78afd0352cb3..bc35a7c2a53f 100644 --- a/platform/diff-impl/api-dump.txt +++ b/platform/diff-impl/api-dump.txt @@ -545,14 +545,14 @@ com.intellij.diff.lang.DiffIgnoredRangeProvider com.intellij.diff.lang.DiffLangSpecificProvider - sf:Companion:com.intellij.diff.lang.DiffLangSpecificProvider$Companion - sf:EP_NAME:com.intellij.openapi.extensions.ExtensionPointName -- s:findApplicable(com.intellij.openapi.project.Project,com.intellij.diff.contents.DiffContent,com.intellij.diff.contents.DiffContent):com.intellij.diff.lang.DiffLangSpecificProvider +- s:findApplicable(com.intellij.diff.contents.DiffContent,com.intellij.diff.contents.DiffContent):com.intellij.diff.lang.DiffLangSpecificProvider - a:getDescription():java.lang.String - getLineFragments(com.intellij.openapi.project.Project,java.lang.CharSequence,java.lang.CharSequence,com.intellij.diff.tools.util.base.IgnorePolicy,com.intellij.diff.tools.util.base.HighlightPolicy,com.intellij.openapi.progress.ProgressIndicator):java.util.List - getPatchedLineFragments(com.intellij.openapi.project.Project,java.util.List,java.lang.CharSequence,java.lang.CharSequence,com.intellij.diff.tools.util.base.IgnorePolicy,com.intellij.diff.tools.util.base.HighlightPolicy,com.intellij.openapi.progress.ProgressIndicator):java.util.List - a:getShouldPrecalculateLineFragments():Z - a:isApplicable(com.intellij.lang.Language):Z f:com.intellij.diff.lang.DiffLangSpecificProvider$Companion -- f:findApplicable(com.intellij.openapi.project.Project,com.intellij.diff.contents.DiffContent,com.intellij.diff.contents.DiffContent):com.intellij.diff.lang.DiffLangSpecificProvider +- f:findApplicable(com.intellij.diff.contents.DiffContent,com.intellij.diff.contents.DiffContent):com.intellij.diff.lang.DiffLangSpecificProvider a:com.intellij.diff.lang.LangDiffIgnoredRangeProvider - com.intellij.diff.lang.DiffIgnoredRangeProvider - ():V @@ -560,15 +560,14 @@ a:com.intellij.diff.lang.LangDiffIgnoredRangeProvider - pa:accepts(com.intellij.openapi.project.Project,com.intellij.lang.Language):Z - pa:computeIgnoredRanges(com.intellij.openapi.project.Project,java.lang.CharSequence,com.intellij.lang.Language):java.util.List - getIgnoredRanges(com.intellij.openapi.project.Project,java.lang.CharSequence,com.intellij.diff.contents.DiffContent):java.util.List -- s:getLanguage(com.intellij.openapi.project.Project,com.intellij.diff.contents.DiffContent):com.intellij.lang.Language com.intellij.diff.merge.LangSpecificMergeConflictResolver - sf:Companion:com.intellij.diff.merge.LangSpecificMergeConflictResolver$Companion - sf:EP_NAME:com.intellij.openapi.extensions.ExtensionPointName -- s:findApplicable(com.intellij.openapi.project.Project,java.util.List):com.intellij.diff.merge.LangSpecificMergeConflictResolver +- s:findApplicable(java.util.List):com.intellij.diff.merge.LangSpecificMergeConflictResolver - a:isApplicable(com.intellij.lang.Language):Z - a:tryResolveMergeConflicts(com.intellij.diff.merge.LangSpecificMergeContext,kotlin.coroutines.Continuation):java.lang.Object f:com.intellij.diff.merge.LangSpecificMergeConflictResolver$Companion -- f:findApplicable(com.intellij.openapi.project.Project,java.util.List):com.intellij.diff.merge.LangSpecificMergeConflictResolver +- f:findApplicable(java.util.List):com.intellij.diff.merge.LangSpecificMergeConflictResolver f:com.intellij.diff.merge.LangSpecificMergeContext - (com.intellij.openapi.project.Project,java.util.List,java.util.List,java.util.List):V - f:file(com.intellij.diff.util.ThreeSide):com.intellij.psi.PsiFile diff --git a/platform/diff-impl/src/com/intellij/diff/impl/CacheDiffRequestProcessor.java b/platform/diff-impl/src/com/intellij/diff/impl/CacheDiffRequestProcessor.java index 8b852895220a..b18735404a65 100644 --- a/platform/diff-impl/src/com/intellij/diff/impl/CacheDiffRequestProcessor.java +++ b/platform/diff-impl/src/com/intellij/diff/impl/CacheDiffRequestProcessor.java @@ -4,6 +4,7 @@ package com.intellij.diff.impl; import com.intellij.CommonBundle; import com.intellij.diff.chains.DiffRequestProducer; import com.intellij.diff.chains.DiffRequestProducerException; +import com.intellij.diff.lang.DiffLanguage; import com.intellij.diff.requests.*; import com.intellij.diff.tools.util.SoftHardCacheMap; import com.intellij.diff.util.DiffTaskQueue; @@ -110,6 +111,9 @@ public abstract class CacheDiffRequestProcessor extends DiffRequestProcessor myQueue.executeAndTryWait( indicator -> { final DiffRequest request = doLoadRequest(requestProvider, indicator); + if (request instanceof ContentDiffRequest contentDiffRequest) { + contentDiffRequest.getContents().forEach(content -> DiffLanguage.computeAndCacheLanguage(content, getProject())); + } return () -> finishRequestLoading(request, force, scrollToChangePolicy, requestProvider); }, () -> applyRequest(new LoadingDiffRequest(getRequestName(requestProvider)), force, scrollToChangePolicy), diff --git a/platform/diff-impl/src/com/intellij/diff/lang/DiffLangSpecificProvider.kt b/platform/diff-impl/src/com/intellij/diff/lang/DiffLangSpecificProvider.kt index df70c08db02f..92ccc1473b31 100644 --- a/platform/diff-impl/src/com/intellij/diff/lang/DiffLangSpecificProvider.kt +++ b/platform/diff-impl/src/com/intellij/diff/lang/DiffLangSpecificProvider.kt @@ -61,11 +61,9 @@ interface DiffLangSpecificProvider { val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.intellij.diff.lang.DiffLangSpecificAdjuster") @JvmStatic - fun findApplicable(project: Project?, leftContent: DiffContent, rightContent: DiffContent): DiffLangSpecificProvider? { - if (project == null) return null - - val leftLanguage = LangDiffIgnoredRangeProvider.getLanguage(project, leftContent) - val rightLanguage = LangDiffIgnoredRangeProvider.getLanguage(project, rightContent) + fun findApplicable(leftContent: DiffContent, rightContent: DiffContent): DiffLangSpecificProvider? { + val leftLanguage = DiffLanguage.getLanguage(leftContent) + val rightLanguage = DiffLanguage.getLanguage(rightContent) if (leftLanguage == null || rightLanguage == null || leftLanguage != rightLanguage) return null diff --git a/platform/diff-impl/src/com/intellij/diff/lang/DiffLanguage.kt b/platform/diff-impl/src/com/intellij/diff/lang/DiffLanguage.kt new file mode 100644 index 000000000000..da9c8611bd40 --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/lang/DiffLanguage.kt @@ -0,0 +1,58 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.diff.lang + +import com.intellij.diff.contents.DiffContent +import com.intellij.diff.contents.DocumentContent +import com.intellij.diff.lang.DiffLanguage.getLanguageOrCompute +import com.intellij.diff.util.DiffUserDataKeys +import com.intellij.lang.Language +import com.intellij.lang.LanguageUtil +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.project.Project +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import org.jetbrains.annotations.CalledInAny + +internal object DiffLanguage { + /** + * Note that unlike [getLanguageOrCompute] this method ignores [com.intellij.psi.LanguageSubstitutors] as their + * calculation might require a background thread. + * + * @return language saved as user data in [content] or language guessed by [content] file type + */ + @CalledInAny + fun getLanguage(content: DiffContent): Language? = + getLanguageByDataKey(content) ?: getLanguageByFileType(content) + + @JvmStatic + @RequiresBackgroundThread + fun getLanguageOrCompute(project: Project, content: DiffContent): Language? { + val language = getLanguageByDataKey(content) + if (language != null) return language + + return computeLanguage(content, project) + } + + @JvmStatic + @RequiresBackgroundThread + fun computeAndCacheLanguage(content: DiffContent, project: Project?) { + val language = computeLanguage(content, project) + if (language != null) { + content.putUserData(DiffUserDataKeys.LANGUAGE, language) + } + } + + private fun computeLanguage(content: DiffContent, project: Project?): Language? { + if (project == null) return getLanguageByFileType(content) + + val fileType = content.getContentType() + val languageForPsi = (content as? DocumentContent)?.highlightFile?.let { highlightingFile -> + runReadAction { LanguageUtil.getLanguageForPsi(project, highlightingFile, fileType) } + } + + return languageForPsi ?: getLanguageByFileType(content) + } + + private fun getLanguageByDataKey(content: DiffContent): Language? = content.getUserData(DiffUserDataKeys.LANGUAGE) + + private fun getLanguageByFileType(content: DiffContent): Language? = content.getContentType()?.let(LanguageUtil::getFileTypeLanguage) +} diff --git a/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java b/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java index e731a5897e91..78d9903fe1ca 100644 --- a/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java +++ b/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java @@ -2,15 +2,9 @@ package com.intellij.diff.lang; import com.intellij.diff.contents.DiffContent; -import com.intellij.diff.contents.DocumentContent; -import com.intellij.diff.util.DiffUserDataKeys; import com.intellij.lang.Language; -import com.intellij.lang.LanguageUtil; -import com.intellij.openapi.application.ReadAction; -import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +18,7 @@ public abstract class LangDiffIgnoredRangeProvider implements DiffIgnoredRangePr @Override public final boolean accepts(@Nullable Project project, @NotNull DiffContent content) { if (project == null) return false; - Language language = getLanguage(project, content); + Language language = DiffLanguage.getLanguageOrCompute(project, content); if (language == null) return false; return accepts(project, language); } @@ -32,21 +26,9 @@ public abstract class LangDiffIgnoredRangeProvider implements DiffIgnoredRangePr @Override public @NotNull List getIgnoredRanges(@Nullable Project project, @NotNull CharSequence text, @NotNull DiffContent content) { assert project != null; - Language language = getLanguage(project, content); + Language language = DiffLanguage.getLanguageOrCompute(project, content); assert language != null; return computeIgnoredRanges(project, text, language); } - - public static @Nullable Language getLanguage(@NotNull Project project, @NotNull DiffContent content) { - Language language = content.getUserData(DiffUserDataKeys.LANGUAGE); - if (language != null) return language; - - FileType fileType = content.getContentType(); - VirtualFile file = content instanceof DocumentContent ? ((DocumentContent)content).getHighlightFile() : null; - if (file != null) { - return ReadAction.compute(() -> LanguageUtil.getLanguageForPsi(project, file, fileType)); - } - return fileType == null ? null : LanguageUtil.getFileTypeLanguage(fileType); - } } diff --git a/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolver.kt b/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolver.kt index 51a0552e0e1b..ac60accb7855 100644 --- a/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolver.kt +++ b/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolver.kt @@ -2,11 +2,10 @@ package com.intellij.diff.merge import com.intellij.diff.contents.DocumentContent -import com.intellij.diff.lang.LangDiffIgnoredRangeProvider +import com.intellij.diff.lang.DiffLanguage import com.intellij.diff.util.ThreeSide import com.intellij.lang.Language import com.intellij.openapi.extensions.ExtensionPointName -import com.intellij.openapi.project.Project /** * An interface for resolving merge conflicts in a way that requires semantic of some language. @@ -31,10 +30,10 @@ interface LangSpecificMergeConflictResolver { val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.intellij.diff.merge.conflict.semantic.resolver") @JvmStatic - fun findApplicable(project: Project?, contentList: List): LangSpecificMergeConflictResolver? { - if (project == null || contentList.size != ThreeSide.entries.size) return null + fun findApplicable(contentList: List): LangSpecificMergeConflictResolver? { + if (contentList.size != ThreeSide.entries.size) return null - val languageList = contentList.mapNotNull { LangDiffIgnoredRangeProvider.getLanguage(project, it) } + val languageList = contentList.mapNotNull { DiffLanguage.getLanguage(it) } if (languageList.size != ThreeSide.entries.size || languageList.distinct().size != 1) return null diff --git a/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolverWrapper.kt b/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolverWrapper.kt index 9bb5cce0cb0e..0c814722f45e 100644 --- a/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolverWrapper.kt +++ b/platform/diff-impl/src/com/intellij/diff/merge/LangSpecificMergeConflictResolverWrapper.kt @@ -28,7 +28,7 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal class LangSpecificMergeConflictResolverWrapper(private val project: Project?, contentList: List) { private val resolver: LangSpecificMergeConflictResolver? = if (`is`("semantic.merge.conflict.resolution", false)) { - LangSpecificMergeConflictResolver.findApplicable(project, contentList) + LangSpecificMergeConflictResolver.findApplicable(contentList) } else { null diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java index db9f75c41424..b63fef838bc5 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java @@ -49,7 +49,7 @@ public class SmartTextDiffProvider extends TwosideTextDiffProviderBase implement DiffContent content1 = Side.LEFT.select(request.getContents()); DiffContent content2 = Side.RIGHT.select(request.getContents()); DiffIgnoredRangeProvider ignoredRangeProvider = getIgnoredRangeProvider(project, content1, content2); - DiffLangSpecificProvider diffProvider = DiffLangSpecificProvider.findApplicable(project, content1, content2); + DiffLangSpecificProvider diffProvider = DiffLangSpecificProvider.findApplicable(content1, content2); IgnorePolicy[] ignorePolicies = getIgnorePolicies(); return new SmartTextDiffProvider(project, content1, content2, settings, rediff, disposable, ignoredRangeProvider, diffProvider, ignorePolicies); @@ -63,7 +63,7 @@ public class SmartTextDiffProvider extends TwosideTextDiffProviderBase implement DiffContent content1 = Side.LEFT.select(request.getContents()); DiffContent content2 = Side.RIGHT.select(request.getContents()); DiffIgnoredRangeProvider ignoredRangeProvider = getIgnoredRangeProvider(project, content1, content2); - DiffLangSpecificProvider diffAdjuster = DiffLangSpecificProvider.findApplicable(project, content1, content2); + DiffLangSpecificProvider diffAdjuster = DiffLangSpecificProvider.findApplicable(content1, content2); return new SmartTextDiffProvider.NoIgnore(project, content1, content2, settings, rediff, disposable, ignoredRangeProvider, diffAdjuster); }