Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement "Add unambiguous imports on the fly" option #8266

Merged
merged 2 commits into from Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -11,8 +11,6 @@ import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel
import org.rust.cargo.project.workspace.PackageOrigin
import org.rust.ide.inspections.fixes.QualifyPathFix
import org.rust.ide.inspections.import.AutoImportFix
import org.rust.ide.inspections.import.AutoImportHintFix
import org.rust.ide.settings.RsCodeInsightSettings
import org.rust.ide.utils.import.ImportCandidateBase
import org.rust.lang.core.macros.proc.ProcMacroApplicationService
import org.rust.lang.core.psi.*
Expand Down Expand Up @@ -119,12 +117,7 @@ private fun createQuickFixes(

val fixes = mutableListOf<LocalQuickFix>()
if (candidates != null && candidates.isNotEmpty()) {
val importFix = if (RsCodeInsightSettings.getInstance().showImportPopup) {
AutoImportHintFix(element, context.type, candidates[0].info.usePath, candidates.size > 1)
} else {
AutoImportFix(element, context.type)
}
fixes.add(importFix)
fixes.add(AutoImportFix(element, context))

if (element is RsPath && context.type == AutoImportFix.Type.GENERAL_PATH && candidates.size == 1) {
fixes.add(QualifyPathFix(element, candidates[0].info))
Expand Down
48 changes: 40 additions & 8 deletions src/main/kotlin/org/rust/ide/inspections/import/AutoImportFix.kt
Expand Up @@ -5,14 +5,19 @@

package org.rust.ide.inspections.import

import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass
import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInsight.intention.PriorityAction
import com.intellij.codeInspection.HintAction
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.rust.ide.inspections.import.AutoImportFix.Type.*
import org.rust.ide.settings.RsCodeInsightSettings
import org.rust.ide.utils.import.*
import org.rust.lang.core.psi.*
import org.rust.lang.core.psi.ext.*
Expand All @@ -22,7 +27,8 @@ import org.rust.lang.core.types.inference
import org.rust.openapiext.Testmark
import org.rust.openapiext.runWriteCommandAction

class AutoImportFix(element: RsElement, private val type: Type) : LocalQuickFixOnPsiElement(element), PriorityAction {
class AutoImportFix(element: RsElement, private val context: Context) :
LocalQuickFixOnPsiElement(element), PriorityAction, HintAction {

private var isConsumed: Boolean = false

Expand All @@ -37,14 +43,9 @@ class AutoImportFix(element: RsElement, private val type: Type) : LocalQuickFixO
invoke(project)
}

fun invoke(project: Project) {
private fun invoke(project: Project) {
val element = startElement as? RsElement ?: return
val (_, candidates) = when (type) {
GENERAL_PATH -> findApplicableContext(project, element as RsPath)
ASSOC_ITEM_PATH -> findApplicableContextForAssocItemPath(project, element as RsPath)
METHOD -> findApplicableContext(project, element as RsMethodCall)
} ?: return

val candidates = context.candidates
if (candidates.size == 1) {
project.runWriteCommandAction {
candidates.first().import(element)
Expand All @@ -70,6 +71,37 @@ class AutoImportFix(element: RsElement, private val type: Type) : LocalQuickFixO
}
}

override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean = isAvailable

override fun invoke(project: Project, editor: Editor?, file: PsiFile?) = invoke(project)

override fun startInWriteAction(): Boolean = true

override fun showHint(editor: Editor): Boolean {
if (!RsCodeInsightSettings.getInstance().showImportPopup) return false
if (HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) return false

val candidates = context.candidates
val hint = candidates[0].info.usePath
val multiple = candidates.size > 1
val message = ShowAutoImportPass.getMessage(multiple, hint)
val element = startElement
HintManager.getInstance().showQuestionHint(editor, message, element.textOffset, element.endOffset) {
invoke(element.project)
true
}
return true
}

override fun fixSilently(editor: Editor): Boolean {
if (!RsCodeInsightSettings.getInstance().addUnambiguousImportsOnTheFly) return false
val candidates = context.candidates
if (candidates.size != 1) return false
val project = editor.project ?: return false
invoke(project)
return true
}

companion object {

const val NAME = "Import"
Expand Down

This file was deleted.

@@ -0,0 +1,19 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.ide.inspections.import

import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.rust.ide.settings.RsCodeInsightSettings
import org.rust.lang.core.psi.RsFile

class RsReferenceImporter : ReferenceImporter {
override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile): Boolean = false
Undin marked this conversation as resolved.
Show resolved Hide resolved

override fun isAddUnambiguousImportsOnTheFlyEnabled(file: PsiFile): Boolean =
file is RsFile && RsCodeInsightSettings.getInstance().addUnambiguousImportsOnTheFly
}
43 changes: 31 additions & 12 deletions src/main/kotlin/org/rust/ide/settings/RsAutoImportOptions.kt
Expand Up @@ -6,19 +6,38 @@
package org.rust.ide.settings

import com.intellij.application.options.editor.AutoImportOptionsProvider
import com.intellij.openapi.options.ConfigurableBuilder
import com.intellij.openapi.application.ApplicationBundle
import com.intellij.openapi.options.UiDslConfigurable
import com.intellij.ui.ContextHelpLabel
import com.intellij.ui.layout.RowBuilder
import org.rust.RsBundle

class RsAutoImportOptions : ConfigurableBuilder(RsBundle.message("settings.rust.auto.import.title")),
AutoImportOptionsProvider {
init {
checkBox(
RsBundle.message("settings.rust.auto.import.show.popup"),
RsCodeInsightSettings.getInstance()::showImportPopup
)
checkBox(
RsBundle.message("settings.rust.auto.import.on.completion"),
RsCodeInsightSettings.getInstance()::importOutOfScopeItems
)
class RsAutoImportOptions : UiDslConfigurable.Simple(), AutoImportOptionsProvider {

override fun RowBuilder.createComponentRow() {
val settings = RsCodeInsightSettings.getInstance()
titledRow(RsBundle.message("settings.rust.auto.import.title")) {
row {
checkBox(
RsBundle.message("settings.rust.auto.import.show.popup"),
settings::showImportPopup
)
}
row {
checkBox(
RsBundle.message("settings.rust.auto.import.on.completion"),
settings::importOutOfScopeItems
)
}
row {
cell {
checkBox(
ApplicationBundle.message("checkbox.add.unambiguous.imports.on.the.fly"),
settings::addUnambiguousImportsOnTheFly
)
ContextHelpLabel.create(ApplicationBundle.message("help.add.unambiguous.imports"))()
}
}
}
}
}
Expand Up @@ -17,6 +17,7 @@ class RsCodeInsightSettings : PersistentStateComponent<RsCodeInsightSettings> {
var showImportPopup: Boolean = false
var importOutOfScopeItems: Boolean = true
var suggestOutOfScopeItems: Boolean = true
var addUnambiguousImportsOnTheFly: Boolean = false

override fun getState(): RsCodeInsightSettings = this

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rust-core.xml
Expand Up @@ -139,6 +139,7 @@
<!-- Imports -->

<lang.importOptimizer language="Rust" implementationClass="org.rust.ide.refactoring.RsImportOptimizer"/>
<referenceImporter implementation="org.rust.ide.inspections.import.RsReferenceImporter"/>
<autoImportOptionsProvider instance="org.rust.ide.settings.RsAutoImportOptions"/>

<!-- Navigation -->
Expand Down
Expand Up @@ -185,7 +185,7 @@ abstract class AnnotationTestFixtureBase(
testmark?.checkHit(action) ?: action()
}

private fun checkByText(text: String) {
fun checkByText(text: String) {
codeInsightFixture.checkResult(replaceCaretMarker(text.trimIndent()))
}

Expand Down
@@ -0,0 +1,86 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.ide.inspections.import

import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
import org.intellij.lang.annotations.Language
import org.rust.ide.inspections.RsInspectionsTestBase
import org.rust.ide.inspections.RsUnresolvedReferenceInspection
import org.rust.ide.settings.RsCodeInsightSettings

class RsReferenceImporterTest : RsInspectionsTestBase(RsUnresolvedReferenceInspection::class) {

fun `test single item`() = doTest("""
fn main() {
func();/*caret*/
}
mod inner {
pub fn func() {}
}
""", """
use crate::inner::func;

fn main() {
func();
}
mod inner {
pub fn func() {}
}
""")

fun `test multiple items`() = doTestNotChanged("""
fn main() {
func();/*caret*/
}
mod mod1 {
pub fn func() {}
}
mod mod2 {
pub fn func() {}
}
""")

fun `test option is disabled`() = doTestNotChanged("""
fn main() {
func();/*caret*/
}
mod mod1 {
pub fn func() {}
}
mod mod2 {
pub fn func() {}
}
""", enabled = false)

private fun doTestNotChanged(@Language("Rust") code: String, enabled: Boolean = true) = doTest(code, code, enabled)

private fun doTest(@Language("Rust") before: String, @Language("Rust") after: String, enabled: Boolean = true) {
withAddUnambiguousImportsOnTheFly(enabled) {
configureByText(before)
// For some reason have to call highlighting twice
myFixture.doHighlightingAndAllowModifications()
myFixture.doHighlightingAndAllowModifications()
annotationFixture.checkByText(after)
}
}

private fun withAddUnambiguousImportsOnTheFly(value: Boolean, action: () -> Unit) {
val settings = RsCodeInsightSettings.getInstance()
val oldValue = settings.addUnambiguousImportsOnTheFly
settings.addUnambiguousImportsOnTheFly = value
try {
action()
} finally {
settings.addUnambiguousImportsOnTheFly = oldValue
}
}
}

/** Same as [CodeInsightTestFixture.doHighlighting] but allows changing PSI during highlighting */
private fun CodeInsightTestFixture.doHighlightingAndAllowModifications() {
CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, intArrayOf(), /* canChangeDocument */ true)
}