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