From 4e2fe7893e9e002b1e71a8d86952e3ff4a487b47 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Wed, 9 Apr 2025 21:30:28 +0200 Subject: [PATCH] [command-completion] IDEA-370458 make command completion faster GitOrigin-RevId: dcc5c8c5fde38d30ce962d59cfacdc26b06b3f22 --- .../DefineParamsDefaultValueAction.java | 6 +- .../commands/JavaCommandsCompletionTest.kt | 17 + .../daemon/impl/HighlightInfo.java | 9 +- .../command/CommandCompletionProvider.kt | 65 ++-- .../DirectErrorFixCompletionCommand.kt | 27 +- .../DirectInspectionFixCompletionCommand.kt | 1 + .../DirectIntentionCommandProvider.kt | 343 ++++++++++-------- .../impl/HighlightVisitorBasedInspection.java | 26 +- 8 files changed, 305 insertions(+), 189 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefineParamsDefaultValueAction.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefineParamsDefaultValueAction.java index 397a6c446fa3..e34317411496 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefineParamsDefaultValueAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefineParamsDefaultValueAction.java @@ -95,8 +95,10 @@ public final class DefineParamsDefaultValueAction extends PsiBasedModCommandActi PsiParameterList parameterList = method.getParameterList(); PsiParameter[] parameters = parameterList.getParameters(); if (parameters.length == 1) { - return ModCommand.psiUpdate(method, (m, updater) -> invoke(context.project(), m, updater, - updater.getWritable(m).getParameterList().getParameters())); + return ModCommand.psiUpdate(method, (m, updater) -> { + PsiMethod writableMethod = updater.getWritable(m); + invoke(context.project(), writableMethod, updater, writableMethod.getParameterList().getParameters()); + }); } List members = ContainerUtil.map(parameters, ParameterClassMember::new); int idx = getSelectedIndex(element); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt index a95c1210beb9..466186f70547 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt @@ -421,6 +421,23 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() { } } + fun testRedCodeImport() { + Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable()) + runBlocking { + myFixture.configureByText(JavaFileType.INSTANCE, """ + class A { + void foo() { + List y = new ArrayList; + } + } + """.trimIndent()) + myFixture.doHighlighting() + myFixture.type(".") + val elements = myFixture.completeBasic() + assertTrue(elements.any { element -> element.lookupString.contains("Import", ignoreCase = true) }) + } + } + fun testChangeSignature() { Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable()) var text = """ diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java index 163421746bac..758147b54d37 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java @@ -298,7 +298,7 @@ public class HighlightInfo implements Segment { } else { LOG.error(new IllegalStateException("fileLevelComponents=" + fileLevelComponentsStorage)); - } + } } @ApiStatus.Internal @@ -1117,7 +1117,12 @@ public class HighlightInfo implements Segment { myIntentionActionDescriptors = List.copyOf(result); updateFields(getIntentionActionDescriptors(), document); } - synchronized void updateLazyFixesPsiTimeStamp(long psiTimeStamp) { + + /** + * only for internal usages + */ + @ApiStatus.Internal + public synchronized void updateLazyFixesPsiTimeStamp(long psiTimeStamp) { List newFixes = ContainerUtil.map(myLazyQuickFixes, d -> d.psiModificationStamp() == 0 ? new LazyFixDescription(d.fixesComputer(), psiTimeStamp, d.future()) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt index b4c68730649c..8269ea8b5657 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt @@ -262,35 +262,7 @@ internal class CommandCompletionProvider : CompletionProvider = ArrayList() addAvailableFixesForGroups(info, topLevelEditor, topLevelPsiFile, fixes, -1, topLevelOffset, false) for (fix in fixes) { - if (fix.action.text == name) { + var currentName = fix.action.text + if (currentName.startsWith("") && currentName.endsWith("")) { + currentName = currentName.substring(6, currentName.length - 7) + } + if (currentName == name) { return@jobToIndicator fix.action } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt index 40f3e55dcead..682e7f8c474a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt @@ -47,6 +47,7 @@ internal class DirectInspectionFixCompletionCommand( override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { if (editor == null) return + //todo merge with error finder val injectedLanguageManager = InjectedLanguageManager.getInstance(psiFile.project) val topLevelFile = injectedLanguageManager.getTopLevelFile(psiFile) val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt index 79dc7b75bd82..d1452040a8b6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt @@ -1,32 +1,18 @@ // 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.codeInsight.completion.command.commands -import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext -import com.intellij.codeInsight.completion.command.CommandCompletionUnsupportedOperationException -import com.intellij.codeInsight.completion.command.CommandProvider -import com.intellij.codeInsight.completion.command.CompletionCommand -import com.intellij.codeInsight.completion.command.HighlightInfoLookup -import com.intellij.codeInsight.completion.command.MyEditor -import com.intellij.codeInsight.completion.command.commandCompletionEnabled +import com.intellij.codeInsight.completion.command.* import com.intellij.codeInsight.daemon.HighlightDisplayKey -import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator -import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.codeInsight.daemon.impl.* import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor -import com.intellij.codeInsight.daemon.impl.HighlightVisitorBasedInspection -import com.intellij.codeInsight.daemon.impl.IntentionActionFilter -import com.intellij.codeInsight.daemon.impl.IntentionMenuContributor -import com.intellij.codeInsight.daemon.impl.SeverityRegistrar -import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo -import com.intellij.codeInsight.intention.CommonIntentionAction -import com.intellij.codeInsight.intention.EmptyIntentionAction -import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.codeInsight.intention.IntentionActionDelegate -import com.intellij.codeInsight.intention.IntentionManager +import com.intellij.codeInsight.intention.* import com.intellij.codeInsight.intention.impl.CachedIntentions import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewComputable import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewUnsupportedOperationException +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo +import com.intellij.codeInsight.quickfix.LazyQuickFixUpdater import com.intellij.codeInspection.InspectionEngine import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptorBase @@ -43,6 +29,8 @@ import com.intellij.lang.annotation.HighlightSeverity.INFORMATION import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.readAction +import com.intellij.openapi.diagnostic.ControlFlowException +import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.colors.CodeInsightColors @@ -59,16 +47,20 @@ import com.intellij.openapi.project.ProjectTypeService import com.intellij.openapi.util.Iconable import com.intellij.openapi.util.Iconable.ICON_FLAG_VISIBILITY import com.intellij.openapi.util.Key +import com.intellij.openapi.util.ProperTextRange import com.intellij.openapi.util.TextRange import com.intellij.profile.codeInspection.ProjectInspectionProfileManager.Companion.getInstance import com.intellij.psi.PsiFile import com.intellij.psi.PsiFileFactory import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil +import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiTreeUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.job +import java.lang.ref.SoftReference +import java.util.concurrent.CancellationException import java.util.function.Predicate /** @@ -97,14 +89,14 @@ internal class DirectIntentionCommandProvider : CommandProvider { return runBlockingCancellable { val result: MutableList = ArrayList() - val errorCache = originalEditor.getUserData(ERROR_CACHE) + val errorCache = originalEditor.getUserData(ERROR_CACHE)?.get() val cachedErrorCommand = errorCache?.getCommands(psiFile.fileDocument, offset) cachedErrorCommand?.let { result.addAll(it) } val asyncErrorHighlighting = if (cachedErrorCommand == null) asyncErrorHighlighting(psiFile, editor, offset) else null - val inspectionCache = originalEditor.getUserData(INSPECTION_CACHE) + val inspectionCache = originalEditor.getUserData(INSPECTION_CACHE)?.get() val cachedInspectionCommand = inspectionCache?.getCommands(psiFile.fileDocument, offset) cachedInspectionCommand?.let { result.addAll(it) @@ -120,22 +112,22 @@ internal class DirectIntentionCommandProvider : CommandProvider { val errors = asyncErrorHighlighting?.await() errors?.let { result.addAll(errors) - originalEditor.putUserData(ERROR_CACHE, IntentionCache( + originalEditor.putUserData(ERROR_CACHE, SoftReference(IntentionCache( commands = errors, offset = offset, hashCode = psiFile.fileDocument.immutableCharSequence.hashCode() - )) + ))) } val inspections = asyncInspections?.await() inspections?.let { result.addAll(inspections) - originalEditor.putUserData(INSPECTION_CACHE, IntentionCache( + originalEditor.putUserData(INSPECTION_CACHE, SoftReference(IntentionCache( commands = inspections, offset = offset, hashCode = psiFile.fileDocument.immutableCharSequence.hashCode() - )) + ))) } val intentions = asyncIntentions.await() @@ -154,8 +146,8 @@ internal class DirectIntentionCommandProvider : CommandProvider { return intentionEditor } - private val ERROR_CACHE: Key = Key.create("completion.command.error.cache") - private val INSPECTION_CACHE: Key = Key.create("completion.command.inspection.cache") + private val ERROR_CACHE: Key> = Key.create("completion.command.error.cache") + private val INSPECTION_CACHE: Key> = Key.create("completion.command.inspection.cache") private data class IntentionCache( private val commands: List, @@ -183,147 +175,200 @@ internal class DirectIntentionCommandProvider : CommandProvider { val offsetProvider = IntentionCommandOffsetProvider.EP_NAME.forLanguage(language) val result: MutableMap = mutableMapOf() - val intentionCommandSkipper = IntentionCommandSkipper.EP_NAME.forLanguage(psiFile.language) - val injectedLanguageManager = InjectedLanguageManager.getInstance(psiFile.project) - val allOffsets = offsetProvider?.findOffsets(psiFile, offset) ?: mutableListOf(offset) - val fileDocument = psiFile.fileDocument - val lineByOffset = mutableMapOf() - for (offset in allOffsets) { - val lineNumber = fileDocument.getLineNumber(offset) - lineByOffset.compute(lineNumber) { k, v -> if (v == null) offset else maxOf(v, offset) } - } - for (currentOffset in lineByOffset.values) { - editor.caretModel.moveToOffset(currentOffset) - val topLevelFile = injectedLanguageManager.getTopLevelFile(psiFile) - val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) - val topLevelOffset = injectedLanguageManager.injectedToHost(psiFile, currentOffset) - val isInjected = topLevelFile != psiFile - val profileToUse = getInstance(psiFile.project).getCurrentProfile() - val inspectionWrapper = InspectionProfileWrapper(profileToUse) - val inspectionTools = getInspectionTools(inspectionWrapper, originalFile) - val lineRange = getLineRange(topLevelFile, topLevelOffset) - val indicator = EmptyProgressIndicator() - val inspectionResult = jobToIndicator(coroutineContext.job, indicator) { - if (!isInjected) { - InspectionEngine.inspectEx(inspectionTools, topLevelFile, lineRange, lineRange, true, false, true, indicator, fun(_: LocalInspectionToolWrapper, _: ProblemDescriptor): Boolean { - return true - }) - } - else { - val textRange = getLineRange(psiFile, currentOffset) - InspectionEngine.inspectElements(inspectionTools, psiFile, textRange, true, true, indicator, - PsiTreeUtil.collectElements(psiFile) { it.textRange.intersects(textRange) }.toList(), fun(_: LocalInspectionToolWrapper, _: ProblemDescriptor): Boolean { - return true - }) - } + try { + val intentionCommandSkipper = IntentionCommandSkipper.EP_NAME.forLanguage(psiFile.language) + val injectedLanguageManager = InjectedLanguageManager.getInstance(psiFile.project) + val allOffsets = offsetProvider?.findOffsets(psiFile, offset) ?: mutableListOf(offset) + val fileDocument = psiFile.fileDocument + val lineByOffset = mutableMapOf() + for (offset in allOffsets) { + val lineNumber = fileDocument.getLineNumber(offset) + lineByOffset.compute(lineNumber) { k, v -> if (v == null) offset else maxOf(v, offset) } } - for (entry: MutableMap.MutableEntry?> in inspectionResult.entries) { - val inspectionToolWrapper: LocalInspectionToolWrapper = entry.key ?: continue - val toolId = inspectionToolWrapper.shortName - val descriptors: List = entry.value ?: continue - for (descriptor in descriptors) { - if (descriptor == null) continue - val fixes = descriptor.fixes ?: continue - if (descriptor !is ProblemDescriptorBase) continue - var textRange = descriptor.textRange ?: continue - if (!lineRange.intersects(textRange)) continue - if (isInjected) { - textRange = injectedLanguageManager.injectedToHost(psiFile, textRange) + for (currentOffset in lineByOffset.values) { + editor.caretModel.moveToOffset(currentOffset) + val topLevelFile = injectedLanguageManager.getTopLevelFile(psiFile) + val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) + val topLevelOffset = injectedLanguageManager.injectedToHost(psiFile, currentOffset) + val isInjected = topLevelFile != psiFile + val profileToUse = getInstance(psiFile.project).getCurrentProfile() + val inspectionWrapper = InspectionProfileWrapper(profileToUse) + val inspectionTools = getInspectionTools(inspectionWrapper, originalFile) + val lineRange = getLineRange(topLevelFile, topLevelOffset) + val indicator = EmptyProgressIndicator() + val inspectionResult = jobToIndicator(coroutineContext.job, indicator) { + if (!isInjected) { + InspectionEngine.inspectEx(inspectionTools, topLevelFile, lineRange, lineRange, true, false, true, indicator, fun(_: LocalInspectionToolWrapper, _: ProblemDescriptor): Boolean { + return true + }) } - val displayKey: HighlightDisplayKey = inspectionToolWrapper.getDisplayKey() ?: continue - val severity: HighlightSeverity = inspectionWrapper.getErrorLevel(displayKey, topLevelFile).severity - val severityRegistrar: SeverityRegistrar = inspectionWrapper.profileManager.severityRegistrar - val level = ProblemDescriptorUtil.highlightTypeFromDescriptor(descriptor, severity, severityRegistrar) - //necessary to be compatible with call site - editor.caretModel.moveToOffset(offset) - topLevelEditor.caretModel.moveToOffset(offset) - for (i in 0..fixes.size - 1) { - val action = QuickFixWrapper.wrap(descriptor, i) - if (action is EmptyIntentionAction) continue - if (intentionCommandSkipper != null && intentionCommandSkipper.skip(action, psiFile, currentOffset)) continue - if (!isInjected && !ShowIntentionActionsHandler.availableFor(topLevelFile, topLevelEditor, topLevelOffset, action)) continue - if (isInjected && !ShowIntentionActionsHandler.availableFor(psiFile, editor, currentOffset, action)) continue - val priority = if (level.getSeverity(null) == INFORMATION) 70 else 80 - val icon = if (level.getSeverity(null) == INFORMATION) AllIcons.Actions.IntentionBulbGrey else AllIcons.Actions.IntentionBulb + else { + val textRange = getLineRange(psiFile, currentOffset) + InspectionEngine.inspectElements(inspectionTools, psiFile, textRange, true, true, indicator, + PsiTreeUtil.collectElements(psiFile) { it.textRange.intersects(textRange) }.toList(), fun(_: LocalInspectionToolWrapper, _: ProblemDescriptor): Boolean { + return true + }) + } + } + for (entry: MutableMap.MutableEntry?> in inspectionResult.entries) { + val inspectionToolWrapper: LocalInspectionToolWrapper = entry.key ?: continue + val toolId = inspectionToolWrapper.shortName + val descriptors: List = entry.value ?: continue + for (descriptor in descriptors) { + if (descriptor == null) continue + val fixes = descriptor.fixes ?: continue + if (descriptor !is ProblemDescriptorBase) continue + var textRange = descriptor.textRange ?: continue + if (!lineRange.intersects(textRange)) continue + if (isInjected) { + textRange = injectedLanguageManager.injectedToHost(psiFile, textRange) + } + val displayKey: HighlightDisplayKey = inspectionToolWrapper.getDisplayKey() ?: continue + val severity: HighlightSeverity = inspectionWrapper.getErrorLevel(displayKey, topLevelFile).severity + val severityRegistrar: SeverityRegistrar = inspectionWrapper.profileManager.severityRegistrar + val level = ProblemDescriptorUtil.highlightTypeFromDescriptor(descriptor, severity, severityRegistrar) + //necessary to be compatible with call site + editor.caretModel.moveToOffset(offset) + topLevelEditor.caretModel.moveToOffset(offset) + for (i in 0..fixes.size - 1) { + val action = QuickFixWrapper.wrap(descriptor, i) + if (action is EmptyIntentionAction) continue + if (intentionCommandSkipper != null && intentionCommandSkipper.skip(action, psiFile, currentOffset)) continue + if (!isInjected && !ShowIntentionActionsHandler.availableFor(topLevelFile, topLevelEditor, topLevelOffset, action)) continue + if (isInjected && !ShowIntentionActionsHandler.availableFor(psiFile, editor, currentOffset, action)) continue + val priority = if (level.getSeverity(null) == INFORMATION) 70 else 80 + val icon = if (level.getSeverity(null) == INFORMATION) AllIcons.Actions.IntentionBulbGrey else AllIcons.Actions.IntentionBulb - result[toolId + ":" + action.text] = (DirectInspectionFixCompletionCommand( - inspectionId = toolId, - name = action.text, - priority = priority, - icon = icon, - highlightInfo = HighlightInfoLookup(textRange, level.attributesKey, priority), - targetOffset = currentOffset, - previewProvider = { - IntentionPreviewComputable(psiFile.project, action, psiFile, editor, offset).call() - })) + result[toolId + ":" + action.text] = (DirectInspectionFixCompletionCommand( + inspectionId = toolId, + name = action.text, + priority = priority, + icon = icon, + highlightInfo = HighlightInfoLookup(textRange, level.attributesKey, priority), + targetOffset = currentOffset, + previewProvider = { + computePreview(psiFile, action, editor, offset) + })) + } } } } } + catch (e: Exception) { + if (e is ControlFlowException || e is CancellationException) { + throw e + } + thisLogger().error("Can't collect inspections", e) + } + + return@readAction result.values.toList() } } + private fun computePreview( + psiFile: PsiFile, + action: IntentionAction, + editor: Editor, + offset: Int, + ): IntentionPreviewInfo? = try { + IntentionPreviewComputable(psiFile.project, action, psiFile, editor, offset).call() + } + catch (e: Exception) { + if (e is ControlFlowException || e is CancellationException) { + throw e + } + thisLogger().error("Can't create preview", e) + null + } + private fun CoroutineScope.asyncErrorHighlighting(psiFile: PsiFile, editor: Editor, offset: Int): Deferred> { return async { val result: MutableList = mutableListOf() readAction { - val injectedLanguageManager = InjectedLanguageManager.getInstance(psiFile.project) - val topLevelFile = injectedLanguageManager.getTopLevelFile(psiFile) - val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) - val topLevelOffset = injectedLanguageManager.injectedToHost(psiFile, offset) - val isInjected = topLevelFile != psiFile - val indicator = DaemonProgressIndicator() - val errorHighlightings: List? = jobToIndicator(coroutineContext.job, indicator) { - ApplicationManager.getApplication().runReadAction?> { - HighlightVisitorBasedInspection.runAnnotatorsInGeneralHighlighting(topLevelFile, true, true, true) + try { + val injectedLanguageManager = InjectedLanguageManager.getInstance(psiFile.project) + val topLevelFile = injectedLanguageManager.getTopLevelFile(psiFile) + val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) + val topLevelOffset = injectedLanguageManager.injectedToHost(psiFile, offset) + + val fileDocument = topLevelFile.fileDocument + val lineNumber = fileDocument.getLineNumber(topLevelOffset) + val startLineNumber = maxOf(0, lineNumber - 2) + val endLineNumber = minOf(fileDocument.lineCount - 1, lineNumber + 2) + val startOffset = fileDocument.getLineStartOffset(startLineNumber) + val endOffset = fileDocument.getLineEndOffset(endLineNumber) + val isInjected = topLevelFile != psiFile + val indicator = DaemonProgressIndicator() + val errorHighlightings: List? = jobToIndicator(coroutineContext.job, indicator) { + ApplicationManager.getApplication().runReadAction?> { + HighlightVisitorBasedInspection.runAnnotatorsInGeneralHighlighting(topLevelFile, + startOffset, + endOffset, + ProperTextRange(startOffset, endOffset), + true, true, true) + } } - } - if (errorHighlightings == null) return@readAction - var insideRange = getLineRange(topLevelFile, topLevelOffset) - if (isInjected) { - insideRange = insideRange.intersection( - TextRange(injectedLanguageManager.injectedToHost(psiFile, 0), - injectedLanguageManager.injectedToHost(psiFile, psiFile.fileDocument.textLength))) - } - var attemptToAddAi = false - for (info: HighlightInfo? in errorHighlightings) { - if (info == null) continue - if (!insideRange.intersects(info.startOffset, info.endOffset) || - (isInjected && !insideRange.contains(TextRange(info.startOffset, info.endOffset)))) continue - if (!attemptToAddAi && info.severity == HighlightSeverity.ERROR) { - val intentionsInfo: IntentionsInfo? = tryToAddAI(info, psiFile, editor, offset) - if (intentionsInfo != null) { - val intentionsCache = CachedIntentions(psiFile.project, psiFile, editor) - intentionsCache.wrapAndUpdateActions(intentionsInfo, false) - for (descriptor in intentionsCache.errorFixes) { - val command = IntentionCompletionCommand(descriptor, 50, AllIcons.Actions.QuickfixBulb, - HighlightInfoLookup(TextRange(info.startOffset, info.endOffset), - CodeInsightColors.ERRORS_ATTRIBUTES, 100), offset) { null } - result.add(command) + if (errorHighlightings == null) return@readAction + var insideRange = getLineRange(topLevelFile, topLevelOffset) + if (isInjected) { + insideRange = insideRange.intersection( + TextRange(injectedLanguageManager.injectedToHost(psiFile, 0), + injectedLanguageManager.injectedToHost(psiFile, psiFile.fileDocument.textLength))) + } + var attemptToAddAi = false + val lazyQuickFixUpdater = LazyQuickFixUpdater.getInstance(psiFile.project) + for (info: HighlightInfo? in errorHighlightings) { + if (info == null) continue + if (!insideRange.intersects(info.startOffset, info.endOffset) || + (isInjected && !insideRange.contains(TextRange(info.startOffset, info.endOffset)))) continue + if (info.hasLazyQuickFixes()) { + info.updateLazyFixesPsiTimeStamp(PsiModificationTracker.getInstance(psiFile.project).modificationCount) + lazyQuickFixUpdater.waitQuickFixesSynchronously(psiFile, editor, info) + } + if (!attemptToAddAi && info.severity == HighlightSeverity.ERROR) { + val intentionsInfo: IntentionsInfo? = tryToAddAI(info, psiFile, editor, offset) + if (intentionsInfo != null) { + val intentionsCache = CachedIntentions(psiFile.project, psiFile, editor) + intentionsCache.wrapAndUpdateActions(intentionsInfo, false) + for (descriptor in intentionsCache.errorFixes) { + val command = IntentionCompletionCommand(descriptor, 50, AllIcons.Actions.QuickfixBulb, + HighlightInfoLookup(TextRange(info.startOffset, info.endOffset), + CodeInsightColors.ERRORS_ATTRIBUTES, 100), offset) { null } + result.add(command) + } } + attemptToAddAi = true } - attemptToAddAi = true - } - val fixes: MutableList = ArrayList() - ShowIntentionsPass.addAvailableFixesForGroups(info, topLevelEditor, topLevelFile, fixes, -1, offset, false) - for (descriptor in fixes) { - if (descriptor.action is EmptyIntentionAction) continue - var name = descriptor.action.text - if (name.startsWith("")) { - name = descriptor.action.familyName + val fixes: MutableList = ArrayList() + ShowIntentionsPass.addAvailableFixesForGroups(info, topLevelEditor, topLevelFile, fixes, -1, offset, false) + for (descriptor in fixes) { + if (descriptor.action is EmptyIntentionAction) continue + var name = descriptor.action.text + val prefix = "" + val suffix = "" + if (name.startsWith(prefix) && name.endsWith(suffix)) { + @Suppress("HardCodedStringLiteral") + name = name.substring(prefix.length, name.length - suffix.length) + } + val command = DirectErrorFixCompletionCommand(name = name, + priority = 100, + icon = AllIcons.Actions.QuickfixBulb, + highlightInfo = HighlightInfoLookup(TextRange(info.startOffset, info.endOffset), + CodeInsightColors.ERRORS_ATTRIBUTES, 100), + previewProvider = { + computePreview(psiFile, descriptor.action, editor, offset) + }) + result.add(command) } - val command = DirectErrorFixCompletionCommand(name = name, - priority = 100, - icon = AllIcons.Actions.QuickfixBulb, - highlightInfo = HighlightInfoLookup(TextRange(info.startOffset, info.endOffset), - CodeInsightColors.ERRORS_ATTRIBUTES, 100), - previewProvider = { - IntentionPreviewComputable(psiFile.project, descriptor.action, psiFile, editor, offset).call() - }) - result.add(command) } } + catch (e: Exception) { + if (e is ControlFlowException || e is CancellationException) { + throw e + } + thisLogger().error("Can't collect errors", e) + } } return@async result } @@ -428,9 +473,9 @@ internal class DirectIntentionCommandProvider : CommandProvider { val intentionCommand = IntentionCompletionCommand(intention, 50, intention.icon ?: AllIcons.Actions.IntentionBulbGrey, null, currentOffset) { editor.caretModel.moveToOffset(currentOffset) - IntentionPreviewComputable(psiFile.project, intention.action, psiFile, editor, currentOffset).call() + computePreview(psiFile, intention.action, editor, currentOffset) } - result.put(intention.text, intentionCommand) + result[intention.text] = intentionCommand } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java index 0a3b95fd95b5..79fe01705a78 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java @@ -20,6 +20,7 @@ import com.intellij.platform.diagnostic.telemetry.helpers.TraceKt; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -103,6 +104,21 @@ public final class HighlightVisitorBasedInspection extends GlobalSimpleInspectio boolean highlightErrorElements, boolean runAnnotators, boolean runVisitors) { + int startOffset = 0; + int endOffset = psiFile.getTextLength(); + ProperTextRange visibleRange = ProperTextRange.create(psiFile.getTextRange()); + return runAnnotatorsInGeneralHighlighting(psiFile, startOffset, endOffset, visibleRange, highlightErrorElements, runAnnotators, runVisitors); + } + + @ApiStatus.Experimental + @ApiStatus.Internal + public static @NotNull List runAnnotatorsInGeneralHighlighting(@NotNull PsiFile psiFile, + int startOffset, + int endOffset, + @NotNull ProperTextRange visibleRange, + boolean highlightErrorElements, + boolean runAnnotators, + boolean runVisitors) { ApplicationManager.getApplication().assertIsNonDispatchThread(); ApplicationManager.getApplication().assertReadAccessAllowed(); Project project = psiFile.getProject(); @@ -112,13 +128,13 @@ public final class HighlightVisitorBasedInspection extends GlobalSimpleInspectio // in case the inspection is running in batch mode // todo IJPL-339 figure out what is the correct context here CodeInsightContext context = FileViewProviderUtil.getCodeInsightContext(psiFile); - HighlightingSessionImpl.getOrCreateHighlightingSession(psiFile, context, daemonProgressIndicator, ProperTextRange.create(psiFile.getTextRange()), + HighlightingSessionImpl.getOrCreateHighlightingSession(psiFile, context, daemonProgressIndicator, visibleRange, TextRange.EMPTY_RANGE); GeneralHighlightingPass ghp = - new GeneralHighlightingPass(psiFile, document, 0, psiFile.getTextLength(), true, ProperTextRange.create(psiFile.getTextRange()), null, - runAnnotators, runVisitors, highlightErrorElements, HighlightInfoUpdater.EMPTY); - InjectedGeneralHighlightingPass ighp = new InjectedGeneralHighlightingPass(psiFile, document, null, 0, psiFile.getTextLength(), true, - ProperTextRange.create(psiFile.getTextRange()), null, + new GeneralHighlightingPass(psiFile, document, startOffset, endOffset, true, visibleRange, null, + runAnnotators, runVisitors, highlightErrorElements, HighlightInfoUpdater.EMPTY); + InjectedGeneralHighlightingPass ighp = new InjectedGeneralHighlightingPass(psiFile, document, null, startOffset, endOffset, true, + visibleRange, null, runAnnotators, runVisitors, highlightErrorElements, HighlightInfoUpdater.EMPTY); ighp.setContext(context); String fileName = psiFile.getName();