From 7b8fbb5fea183fcb75a16c87ab378399931e9669 Mon Sep 17 00:00:00 2001 From: Alexander Podkhalyuzin Date: Tue, 17 Sep 2019 14:09:52 -0700 Subject: [PATCH] Fixed freezes with add unambiguous imports on the fly We still had backup way to recalculate things for the case when fix is not available. So if error is not importable, or we have second error, which will be outdated after adding first import, we could get freezes. 2019.2 and 2019.1 have different APIs, so it's implemented in different way. #KT-30863 Fixed --- .../{ImportFix.kt => AbstractImportFix.kt} | 8 +- .../kotlin/idea/quickfix/ActualImportFix.kt | 30 ++++ .../idea/quickfix/ActualImportFix.kt.191 | 16 +++ .../idea/quickfix/KotlinReferenceImporter.kt | 78 ++--------- .../quickfix/KotlinReferenceImporter.kt.191 | 131 ++++++++++++++++++ 5 files changed, 193 insertions(+), 70 deletions(-) rename idea/src/org/jetbrains/kotlin/idea/quickfix/{ImportFix.kt => AbstractImportFix.kt} (98%) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt.191 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt.191 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractImportFix.kt similarity index 98% rename from idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt rename to idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractImportFix.kt index 3bf2cdb6b70..33160c36e43 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractImportFix.kt @@ -261,7 +261,8 @@ internal abstract class OrdinaryImportFixBase(expression: T, f } } -internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFixBase(expression, MyFactory) { +// This is required to be abstract to reduce bunch file size +internal abstract class AbstractImportFix(expression: KtSimpleNameExpression, factory: Factory) : OrdinaryImportFixBase(expression, factory) { override fun getCallTypeAndReceiver() = element?.let { CallTypeAndReceiver.detect(it) } private fun importNamesForMembers(): Collection { @@ -390,11 +391,6 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix indicesHelper ) } - - companion object MyFactory : Factory() { - override fun createImportAction(diagnostic: Diagnostic) = - (diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportFix) - } } internal class ImportConstructorReferenceFix(expression: KtSimpleNameExpression) : diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt new file mode 100644 index 00000000000..9e77b7bf196 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.CodeInsightSettings +import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.psi.KtSimpleNameExpression + +internal class ImportFix(expression: KtSimpleNameExpression): AbstractImportFix(expression, MyFactory) { + override fun fixSilently(editor: Editor): Boolean { + if (!CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) return false + if (isOutdated()) return false + val element = element ?: return false + val addImportAction = createAction(element.project, editor, element) + if (addImportAction.isUnambiguous()) { + addImportAction.execute() + return true + } + return false + } + + companion object MyFactory : Factory() { + override fun createImportAction(diagnostic: Diagnostic) = + (diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportFix) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt.191 b/idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt.191 new file mode 100644 index 00000000000..ca895482b05 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ActualImportFix.kt.191 @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.psi.KtSimpleNameExpression + +internal class ImportFix(expression: KtSimpleNameExpression): AbstractImportFix(expression) { + companion object MyFactory : Factory() { + override fun createImportAction(diagnostic: Diagnostic) = + (diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportFix) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt index 74e4362c238..447ca133122 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.daemon.ReferenceImporter -import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx import com.intellij.codeInsight.daemon.impl.DaemonListeners import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.editor.Editor @@ -30,7 +29,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType @@ -39,77 +37,19 @@ import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class KotlinReferenceImporter : ReferenceImporter { - override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile) - = autoImportReferenceAtCursor(editor, file, allowCaretNearRef = false) - - override fun autoImportReferenceAt(editor: Editor, file: PsiFile, offset: Int): Boolean { + override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile): Boolean { if (file !is KtFile) return false - val nameExpression = file.findElementAt(offset)?.parent as? KtSimpleNameExpression ?: return false - - if (!KotlinCodeInsightWorkspaceSettings.getInstance(file.project).addUnambiguousImportsOnTheFly) return false - - val importFix: ImportFixBase? = findImportFixAt(editor, file, offset) - if (importFix != null && !importFix.isOutdated()) { - val addImportAction = importFix.createAction(file.project, editor, nameExpression) - if (addImportAction.isUnambiguous()) { - addImportAction.execute() - } - return true - } - - return nameExpression.autoImport(editor, file) - } - - private fun findImportFixAt( - editor: Editor, - file: KtFile, - offset: Int - ): ImportFixBase? { - var importFix: ImportFixBase? = null - DaemonCodeAnalyzerEx.processHighlights(editor.document, file.project, null, offset, offset) { info -> - importFix = info.quickFixActionRanges?.asSequence() - ?.map { it.first.action }?.filterIsInstance>()?.firstOrNull() - importFix == null - } - return importFix - } - - companion object { - - // TODO: use in table cell - fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile, allowCaretNearRef: Boolean): Boolean { - if (file !is KtFile) return false - - val caretOffset = editor.caretModel.offset - val document = editor.document - val lineNumber = document.getLineNumber(caretOffset) - val startOffset = document.getLineStartOffset(lineNumber) - val endOffset = document.getLineEndOffset(lineNumber) - - val elements = file.elementsInRange(TextRange(startOffset, endOffset)) - .flatMap { it.collectDescendantsOfType() } - for (element in elements) { - if (!allowCaretNearRef && element.endOffset == caretOffset) continue - - if (element.autoImport(editor, file)) { - return true - } - } - - return false - } - - private fun hasUnresolvedImportWhichCanImport(file: KtFile, name: String): Boolean { + fun hasUnresolvedImportWhichCanImport(name: String): Boolean { return file.importDirectives.any { it.targetDescriptors().isEmpty() && (it.isAllUnder || it.importPath?.importedName?.asString() == name) } } - private fun KtSimpleNameExpression.autoImport(editor: Editor, file: KtFile): Boolean { + fun KtSimpleNameExpression.autoImport(): Boolean { if (!KotlinCodeInsightWorkspaceSettings.getInstance(project).addUnambiguousImportsOnTheFly) return false if (!DaemonListeners.canChangeFileSilently(file)) return false - if (hasUnresolvedImportWhichCanImport(file, getReferencedName())) return false + if (hasUnresolvedImportWhichCanImport(getReferencedName())) return false val bindingContext = analyze(BodyResolveMode.PARTIAL) if (mainReference.resolveToDescriptors(bindingContext).isNotEmpty()) return false @@ -127,5 +67,15 @@ class KotlinReferenceImporter : ReferenceImporter { } return result } + + val caretOffset = editor.caretModel.offset + val document = editor.document + val lineNumber = document.getLineNumber(caretOffset) + val startOffset = document.getLineStartOffset(lineNumber) + val endOffset = document.getLineEndOffset(lineNumber) + + return file.elementsInRange(TextRange(startOffset, endOffset)) + .flatMap { it.collectDescendantsOfType() } + .any { it.endOffset != caretOffset && it.autoImport() } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt.191 b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt.191 new file mode 100644 index 00000000000..01d30c8d3b9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt.191 @@ -0,0 +1,131 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.CodeInsightSettings +import com.intellij.codeInsight.daemon.ReferenceImporter +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx +import com.intellij.codeInsight.daemon.impl.DaemonListeners +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.actions.createSingleImportAction +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference +import org.jetbrains.kotlin.idea.core.targetDescriptors +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.psi.psiUtil.elementsInRange +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class KotlinReferenceImporter : ReferenceImporter { + override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile) + = autoImportReferenceAtCursor(editor, file, allowCaretNearRef = false) + + override fun autoImportReferenceAt(editor: Editor, file: PsiFile, offset: Int): Boolean { + if (file !is KtFile) return false + + val nameExpression = file.findElementAt(offset)?.parent as? KtSimpleNameExpression ?: return false + + if (!CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) return false + + val importFix: ImportFixBase? = findImportFixAt(editor, file, offset) + if (importFix != null && !importFix.isOutdated()) { + val addImportAction = importFix.createAction(file.project, editor, nameExpression) + if (addImportAction.isUnambiguous()) { + addImportAction.execute() + } + return true + } + + return false + } + + private fun findImportFixAt( + editor: Editor, + file: KtFile, + offset: Int + ): ImportFixBase? { + var importFix: ImportFixBase? = null + DaemonCodeAnalyzerEx.processHighlights(editor.document, file.project, null, offset, offset) { info -> + importFix = info.quickFixActionRanges?.asSequence() + ?.map { it.first.action }?.filterIsInstance>()?.firstOrNull() + importFix == null + } + return importFix + } + + companion object { + + // TODO: use in table cell + fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile, allowCaretNearRef: Boolean): Boolean { + if (file !is KtFile) return false + + val caretOffset = editor.caretModel.offset + val document = editor.document + val lineNumber = document.getLineNumber(caretOffset) + val startOffset = document.getLineStartOffset(lineNumber) + val endOffset = document.getLineEndOffset(lineNumber) + + val elements = file.elementsInRange(TextRange(startOffset, endOffset)) + .flatMap { it.collectDescendantsOfType() } + for (element in elements) { + if (!allowCaretNearRef && element.endOffset == caretOffset) continue + + if (element.autoImport(editor, file)) { + return true + } + } + + return false + } + + private fun hasUnresolvedImportWhichCanImport(file: KtFile, name: String): Boolean { + return file.importDirectives.any { + it.targetDescriptors().isEmpty() && (it.isAllUnder || it.importPath?.importedName?.asString() == name) + } + } + + private fun KtSimpleNameExpression.autoImport(editor: Editor, file: KtFile): Boolean { + if (!CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) return false + if (!DaemonListeners.canChangeFileSilently(file)) return false + if (hasUnresolvedImportWhichCanImport(file, getReferencedName())) return false + + val bindingContext = analyze(BodyResolveMode.PARTIAL) + if (mainReference.resolveToDescriptors(bindingContext).isNotEmpty()) return false + + val suggestions = ImportFix(this).collectSuggestions() + if (suggestions.size != 1) return false + val descriptors = file.resolveImportReference(suggestions.single()) + + // we do not auto-import nested classes because this will probably add qualification into the text and this will confuse the user + if (descriptors.any { it is ClassDescriptor && it.containingDeclaration is ClassDescriptor }) return false + + var result = false + CommandProcessor.getInstance().runUndoTransparentAction { + result = createSingleImportAction(project, editor, this, suggestions).execute() + } + return result + } + } +} \ No newline at end of file