[command-completion] IDEA-370458 make command completion faster

GitOrigin-RevId: dcc5c8c5fde38d30ce962d59cfacdc26b06b3f22
This commit is contained in:
Mikhail Pyltsin
2025-04-09 21:45:47 +00:00
committed by intellij-monorepo-bot
parent 4ff346caa2
commit 4e2fe7893e
8 changed files with 305 additions and 189 deletions
@@ -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<ParameterClassMember> members = ContainerUtil.map(parameters, ParameterClassMember::new);
int idx = getSelectedIndex(element);
@@ -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<String> y = new ArrayList<caret><String>;
}
}
""".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 = """
@@ -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<LazyFixDescription> newFixes = ContainerUtil.map(myLazyQuickFixes,
d -> d.psiModificationStamp() == 0
? new LazyFixDescription(d.fixesComputer(), psiTimeStamp, d.future())
@@ -262,35 +262,7 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
TextRange(hostOffset, originalDocument.textLength))
var file =
if (isNonWritten) {
val createdFile = originalFile.copy() as PsiFile
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
createdFile
}
else {
if (topFile == originalFile) {
var createdFile = commandCompletionFactory.createFile(originalFile, adjustedText)
if (createdFile == null) {
createdFile = PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
}
createdFile
}
else {
val createdFile = PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
createdFile
}
}
var file = createFile(isNonWritten, originalFile, topFile, commandCompletionFactory, adjustedText, topEditor)
if (file is PsiFileImpl) {
file.setOriginalFile(topFile)
@@ -302,6 +274,41 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
}
return AdjustedCompletionParameters(file, adjustedOffset, hostAdjustedOffset)
}
private fun createFile(
isNonWritten: Boolean,
originalFile: PsiFile,
topFile: PsiFile,
commandCompletionFactory: CommandCompletionFactory,
adjustedText: String,
topEditor: Editor,
): PsiFile {
if (isNonWritten) {
val createdFile = originalFile.copy() as PsiFile
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
return createdFile
}
if (topFile == originalFile) {
var createdFile = commandCompletionFactory.createFile(originalFile, adjustedText)
if (createdFile == null) {
createdFile = PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
}
return createdFile
}
val createdFile = PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
return createdFile
}
}
@ApiStatus.Internal
@@ -12,15 +12,18 @@ import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.addAvailableFixes
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInsight.quickfix.LazyQuickFixUpdater
import com.intellij.concurrency.currentThreadContext
import com.intellij.injected.editor.DocumentWindow
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.application.readAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.jobToIndicator
import com.intellij.openapi.util.ProperTextRange
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil
import com.intellij.psi.util.PsiModificationTracker
import kotlinx.coroutines.job
import org.jetbrains.annotations.Nls
import javax.swing.Icon
@@ -54,15 +57,35 @@ internal class DirectErrorFixCompletionCommand(
val indicator = DaemonProgressIndicator()
readAction {
jobToIndicator(currentThreadContext().job, indicator) {
val highlightings = runAnnotatorsInGeneralHighlighting(topLevelPsiFile, true, true, true)
//todo merge with error finder
val fileDocument = topLevelPsiFile.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 highlightings = runAnnotatorsInGeneralHighlighting(topLevelPsiFile,
startOffset,
endOffset,
ProperTextRange(startOffset, endOffset),
true, true, true)
val lazyQuickFixUpdater = LazyQuickFixUpdater.getInstance(psiFile.project)
for (info in highlightings) {
if (info.hasLazyQuickFixes()) {
info.updateLazyFixesPsiTimeStamp(PsiModificationTracker.getInstance(psiFile.project).modificationCount);
lazyQuickFixUpdater.waitQuickFixesSynchronously(psiFile, editor, info)
}
if (!(info.startOffset == highlightInfo.range.startOffset && info.endOffset == highlightInfo.range.endOffset)) {
continue
}
val fixes: MutableList<IntentionActionDescriptor> = 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("<html>") && currentName.endsWith("</html>")) {
currentName = currentName.substring(6, currentName.length - 7)
}
if (currentName == name) {
return@jobToIndicator fix.action
}
}
@@ -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)
@@ -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<CompletionCommand> = 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<IntentionCache> = Key.create("completion.command.error.cache")
private val INSPECTION_CACHE: Key<IntentionCache> = Key.create("completion.command.inspection.cache")
private val ERROR_CACHE: Key<SoftReference<IntentionCache>> = Key.create("completion.command.error.cache")
private val INSPECTION_CACHE: Key<SoftReference<IntentionCache>> = Key.create("completion.command.inspection.cache")
private data class IntentionCache(
private val commands: List<CompletionCommand>,
@@ -183,147 +175,200 @@ internal class DirectIntentionCommandProvider : CommandProvider {
val offsetProvider = IntentionCommandOffsetProvider.EP_NAME.forLanguage(language)
val result: MutableMap<String, CompletionCommand> = 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<Int, Int>()
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<Int, Int>()
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<LocalInspectionToolWrapper?, List<ProblemDescriptor?>?> in inspectionResult.entries) {
val inspectionToolWrapper: LocalInspectionToolWrapper = entry.key ?: continue
val toolId = inspectionToolWrapper.shortName
val descriptors: List<ProblemDescriptor?> = 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<LocalInspectionToolWrapper?, List<ProblemDescriptor?>?> in inspectionResult.entries) {
val inspectionToolWrapper: LocalInspectionToolWrapper = entry.key ?: continue
val toolId = inspectionToolWrapper.shortName
val descriptors: List<ProblemDescriptor?> = 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<List<CompletionCommand>> {
return async {
val result: MutableList<CompletionCommand> = 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<HighlightInfo?>? = jobToIndicator(coroutineContext.job, indicator) {
ApplicationManager.getApplication().runReadAction<List<HighlightInfo?>?> {
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<HighlightInfo?>? = jobToIndicator(coroutineContext.job, indicator) {
ApplicationManager.getApplication().runReadAction<List<HighlightInfo?>?> {
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<IntentionActionDescriptor> = 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("<html>")) {
name = descriptor.action.familyName
val fixes: MutableList<IntentionActionDescriptor> = 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 = "<html>"
val suffix = "</html>"
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
}
}
}
@@ -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<HighlightInfo> 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();