From 56b088233ab969094f2d42659c6d0c69dc27ffb2 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 18 Sep 2015 13:17:07 +0300 Subject: [PATCH] KT-8179 Settings / "Add unambiguous imports on the fly" could work in Kotlin files too #KT-8179 Fixed --- idea/src/META-INF/plugin.xml | 2 + .../kotlin/idea/quickfix/AutoImportFix.kt | 105 +++++++++--------- .../idea/quickfix/KotlinReferenceImporter.kt | 102 +++++++++++++++++ 3 files changed, 156 insertions(+), 53 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 78f3ec38c3a..cd3f4389b7a 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1187,6 +1187,8 @@ level="WARNING" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt index 1634d9ca5f1..92c814b48d0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.JetBundle import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper @@ -96,59 +97,6 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction { - if (!element.isValid()) return listOf() - - val file = element.getContainingFile() as? JetFile ?: return listOf() - - var referenceName = element.getReferencedName() - if (element.getIdentifier() == null) { - val conventionName = JetPsiUtil.getConventionName(element) - if (conventionName != null) { - referenceName = conventionName.asString() - } - } - if (referenceName.isEmpty()) return listOf() - - val resolutionFacade = element.getResolutionFacade() - - val searchScope = getResolveScope(file) - - val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL) - - val diagnostics = bindingContext.getDiagnostics().forElement(element) - if (!diagnostics.any { it.getFactory() in ERRORS }) return listOf() - - val resolutionScope = element.getResolutionScope(bindingContext, resolutionFacade) - val containingDescriptor = resolutionScope.ownerDescriptor - - fun isVisible(descriptor: DeclarationDescriptor): Boolean { - if (descriptor is DeclarationDescriptorWithVisibility) { - return descriptor.isVisible(containingDescriptor, bindingContext, element) - } - - return true - } - - val result = ArrayList() - - val indicesHelper = KotlinIndicesHelper(resolutionFacade, searchScope, ::isVisible, true) - - if (!element.isImportDirectiveExpression() && !JetPsiUtil.isSelectorInQualified(element)) { - if (ProjectStructureUtil.isJsKotlinModule(file)) { - result.addAll(indicesHelper.getKotlinClasses({ it == referenceName }, { true })) - } - else { - result.addAll(indicesHelper.getJvmClassesByName(referenceName)) - } - result.addAll(indicesHelper.getTopLevelCallablesByName(referenceName)) - } - - result.addAll(indicesHelper.getCallableTopLevelExtensions({ it == referenceName }, element, bindingContext)) - - return result - } - companion object { private val ERRORS = setOf(Errors.UNRESOLVED_REFERENCE, Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER) @@ -168,5 +116,56 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction { + if (!element.isValid()) return listOf() + + val file = element.getContainingFile() as? JetFile ?: return listOf() + + var referenceName = element.getReferencedName() + if (element.getIdentifier() == null) { + val conventionName = JetPsiUtil.getConventionName(element) + if (conventionName != null) { + referenceName = conventionName.asString() + } + } + if (referenceName.isEmpty()) return listOf() + + val searchScope = getResolveScope(file) + + val bindingContext = element.analyze(BodyResolveMode.PARTIAL) + + val diagnostics = bindingContext.getDiagnostics().forElement(element) + if (!diagnostics.any { it.getFactory() in ERRORS }) return listOf() + + val resolutionScope = element.getResolutionScope(bindingContext, file.getResolutionFacade()) + val containingDescriptor = resolutionScope.ownerDescriptor + + fun isVisible(descriptor: DeclarationDescriptor): Boolean { + if (descriptor is DeclarationDescriptorWithVisibility) { + return descriptor.isVisible(containingDescriptor, bindingContext, element) + } + + return true + } + + val result = ArrayList() + + val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible, true) + + if (!element.isImportDirectiveExpression() && !JetPsiUtil.isSelectorInQualified(element)) { + if (ProjectStructureUtil.isJsKotlinModule(file)) { + result.addAll(indicesHelper.getKotlinClasses({ it == referenceName }, { true })) + } + else { + result.addAll(indicesHelper.getJvmClassesByName(referenceName)) + } + result.addAll(indicesHelper.getTopLevelCallablesByName(referenceName)) + } + + result.addAll(indicesHelper.getCallableTopLevelExtensions({ it == referenceName }, element, bindingContext)) + + return result + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt new file mode 100644 index 00000000000..e6db83c776d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinReferenceImporter.kt @@ -0,0 +1,102 @@ +/* + * 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.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.idea.actions.KotlinAddImportAction +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.targetDescriptors +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.psi.JetSimpleNameExpression +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 + +public 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 JetFile) return false + + val nameExpression = file.findElementAt(offset)?.parent as? JetSimpleNameExpression ?: return false + + return nameExpression.autoImport(editor, file) + } + + companion object { + + // TODO: use in table cell + // TODO: DependencyValidationManager + // TODO: filter out non-top level + public fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile, allowCaretNearRef: Boolean): Boolean { + if (file !is JetFile) 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 + } + + protected fun hasUnresolvedImportWhichCanImport(file: JetFile, name: String): Boolean { + return file.importDirectives.any { + it.targetDescriptors().isEmpty() && (it.isAllUnder || it.importPath?.importedName?.asString() == name) + } + } + + private fun JetSimpleNameExpression.autoImport(editor: Editor, file: JetFile): 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 = AutoImportFix.computeSuggestions(this) + + if (suggestions.distinctBy { it.importableFqName!! }.size() != 1) return false + + var result = false + CommandProcessor.getInstance().runUndoTransparentAction { + result = KotlinAddImportAction(project, editor, this, suggestions).execute() + } + return result + } + } +} \ No newline at end of file