KT-5876 Code completion should never auto-insert item which requires adding import
#KT-5876 Fixed
This commit is contained in:
@@ -36,9 +36,21 @@ public final class InTextDirectivesUtils {
|
||||
|
||||
@Nullable
|
||||
public static Integer getPrefixedInt(String fileText, String prefix) {
|
||||
String[] numberStrings = findArrayWithPrefixes(fileText, prefix);
|
||||
if (numberStrings.length > 0) {
|
||||
return Integer.parseInt(numberStrings[0]);
|
||||
String[] strings = findArrayWithPrefixes(fileText, prefix);
|
||||
if (strings.length > 0) {
|
||||
assert strings.length == 1;
|
||||
return Integer.parseInt(strings[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Boolean getPrefixedBoolean(String fileText, String prefix) {
|
||||
String[] strings = findArrayWithPrefixes(fileText, prefix);
|
||||
if (strings.length > 0) {
|
||||
assert strings.length == 1;
|
||||
return Boolean.parseBoolean(strings[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -60,7 +60,6 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
|
||||
protected val prefixMatcher: PrefixMatcher = this.resultSet.getPrefixMatcher()
|
||||
|
||||
protected val collector: LookupElementsCollector = LookupElementsCollector(prefixMatcher, resolveSession, { isVisibleDescriptor(it) })
|
||||
protected var anythingAdded: Boolean = false
|
||||
|
||||
protected val project: Project = position.getProject()
|
||||
protected val indicesHelper: KotlinIndicesHelper = KotlinIndicesHelper(project)
|
||||
@@ -77,16 +76,13 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
|
||||
}
|
||||
|
||||
protected fun flushToResultSet() {
|
||||
if (!collector.isEmpty) {
|
||||
anythingAdded = true
|
||||
}
|
||||
collector.flushToResultSet(resultSet)
|
||||
}
|
||||
|
||||
public fun complete(): Boolean {
|
||||
doComplete()
|
||||
flushToResultSet()
|
||||
return anythingAdded
|
||||
return !collector.isResultEmpty
|
||||
}
|
||||
|
||||
protected abstract fun doComplete()
|
||||
@@ -157,11 +153,11 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
|
||||
private fun addNonImported() {
|
||||
if (shouldRunTopLevelCompletion()) {
|
||||
TypesCompletion(parameters, resolveSession, prefixMatcher).addAllTypes(collector)
|
||||
collector.addDescriptorElements(getKotlinTopLevelDeclarations())
|
||||
collector.addDescriptorElements(getKotlinTopLevelDeclarations(), suppressAutoInsertion = true)
|
||||
}
|
||||
|
||||
if (shouldRunExtensionsCompletion()) {
|
||||
collector.addDescriptorElements(getKotlinExtensions())
|
||||
collector.addDescriptorElements(getKotlinExtensions(), suppressAutoInsertion = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +192,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
|
||||
|
||||
private fun addReferenceVariants(filterCondition: (DeclarationDescriptor) -> Boolean = { true }) {
|
||||
val descriptors = TipsManager.getReferenceVariants(jetReference!!.expression, bindingContext!!)
|
||||
collector.addDescriptorElements(descriptors.filter { filterCondition(it) })
|
||||
collector.addDescriptorElements(descriptors.filter { filterCondition(it) }, suppressAutoInsertion = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,23 +35,35 @@ class LookupElementsCollector(private val prefixMatcher: PrefixMatcher,
|
||||
private val elements = ArrayList<LookupElement>()
|
||||
|
||||
public fun flushToResultSet(resultSet: CompletionResultSet) {
|
||||
resultSet.addAllElements(elements)
|
||||
elements.clear()
|
||||
}
|
||||
|
||||
public val isEmpty: Boolean
|
||||
get() = elements.isEmpty()
|
||||
|
||||
public fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>) {
|
||||
for (descriptor in descriptors) {
|
||||
addDescriptorElements(descriptor)
|
||||
if (!elements.isEmpty()) {
|
||||
resultSet.addAllElements(elements)
|
||||
elements.clear()
|
||||
isResultEmpty = false
|
||||
}
|
||||
}
|
||||
|
||||
public fun addDescriptorElements(descriptor: DeclarationDescriptor) {
|
||||
public var isResultEmpty: Boolean = true
|
||||
private set
|
||||
|
||||
public fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>,
|
||||
suppressAutoInsertion: Boolean // auto-insertion suppression is used for elements that require adding an import
|
||||
) {
|
||||
for (descriptor in descriptors) {
|
||||
addDescriptorElements(descriptor, suppressAutoInsertion)
|
||||
}
|
||||
}
|
||||
|
||||
public fun addDescriptorElements(descriptor: DeclarationDescriptor, suppressAutoInsertion: Boolean) {
|
||||
if (!descriptorFilter(descriptor)) return
|
||||
|
||||
addElement(DescriptorLookupConverter.createLookupElement(resolveSession, descriptor))
|
||||
run {
|
||||
var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, descriptor)
|
||||
if (suppressAutoInsertion &&
|
||||
elements.isEmpty() && isResultEmpty /* without these checks we would get duplicated items */) {
|
||||
lookupElement = lookupElement.suppressAutoInsertion()
|
||||
}
|
||||
addElement(lookupElement)
|
||||
}
|
||||
|
||||
// add special item for function with one argument of function type with more than one parameter
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
|
||||
@@ -37,11 +37,13 @@ import org.jetbrains.jet.plugin.search.searchScopeForSourceElementDependencies
|
||||
|
||||
class TypesCompletion(val parameters: CompletionParameters, val resolveSession: ResolveSessionForBodies, val prefixMatcher: PrefixMatcher) {
|
||||
fun addAllTypes(result: LookupElementsCollector) {
|
||||
result.addDescriptorElements(KotlinBuiltIns.getInstance().getNonPhysicalClasses())
|
||||
result.addDescriptorElements(KotlinBuiltIns.getInstance().getNonPhysicalClasses().filter { prefixMatcher.prefixMatches(it.getName().asString()) },
|
||||
suppressAutoInsertion = true)
|
||||
|
||||
val project = parameters.getOriginalFile().getProject()
|
||||
val searchScope = searchScopeForSourceElementDependencies(parameters.getOriginalFile()) ?: return
|
||||
result.addDescriptorElements(KotlinIndicesHelper(project).getClassDescriptors({ prefixMatcher.prefixMatches(it) }, resolveSession, searchScope))
|
||||
result.addDescriptorElements(KotlinIndicesHelper(project).getClassDescriptors({ prefixMatcher.prefixMatches(it) }, resolveSession, searchScope),
|
||||
suppressAutoInsertion = true)
|
||||
|
||||
if (!ProjectStructureUtil.isJsKotlinModule(parameters.getOriginalFile() as JetFile)) {
|
||||
addAdaptedJavaCompletion(result)
|
||||
@@ -63,7 +65,7 @@ class TypesCompletion(val parameters: CompletionParameters, val resolveSession:
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
JetJavaClassInsertHandler.handleInsert(context, lookupElement)
|
||||
}
|
||||
})
|
||||
}.suppressAutoInsertion())
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -79,7 +81,8 @@ class TypesCompletion(val parameters: CompletionParameters, val resolveSession:
|
||||
if (JetFromJavaDescriptorHelper.getCompiledClassKind(aClass) != ClassKind.CLASS_OBJECT) {
|
||||
val qualifiedName = aClass.getQualifiedName()
|
||||
if (qualifiedName != null) {
|
||||
result.addDescriptorElements(ResolveSessionUtils.getClassDescriptorsByFqName(resolveSession.getModuleDescriptor(), FqName(qualifiedName)))
|
||||
result.addDescriptorElements(ResolveSessionUtils.getClassDescriptorsByFqName(resolveSession.getModuleDescriptor(), FqName(qualifiedName)),
|
||||
suppressAutoInsertion = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fun foo() {
|
||||
PrintS<caret>
|
||||
}
|
||||
|
||||
// AUTOCOMPLETE_SETTING: true
|
||||
// EXIST_JAVA_ONLY: PrintStream
|
||||
// NUMBER_JAVA: 1
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package second
|
||||
|
||||
public class NotImportedClass
|
||||
|
||||
// ALLOW_AST_ACCESS
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package first
|
||||
|
||||
class A : NotImported<caret>
|
||||
|
||||
// AUTOCOMPLETE_SETTING: true
|
||||
// EXIST: NotImportedClass
|
||||
// NUMBER: 1
|
||||
@@ -20,23 +20,47 @@ import com.intellij.codeInsight.lookup.LookupElement
|
||||
import org.jetbrains.jet.plugin.project.TargetPlatform
|
||||
import org.junit.Assert
|
||||
import org.jetbrains.jet.completion.ExpectedCompletionUtils
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
|
||||
fun testCompletion(fileText: String, platform: TargetPlatform?, complete: (Int) -> Array<LookupElement>?, defaultInvocationCount: Int = 0) {
|
||||
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
|
||||
val items = complete(invocationCount) ?: array()
|
||||
testWithAutoCompleteSetting(fileText) {
|
||||
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
|
||||
val items = complete(invocationCount) ?: array()
|
||||
|
||||
ExpectedCompletionUtils.assertDirectivesValid(fileText)
|
||||
ExpectedCompletionUtils.assertDirectivesValid(fileText)
|
||||
|
||||
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
|
||||
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
|
||||
val itemsNumber = ExpectedCompletionUtils.getExpectedNumber(fileText, platform)
|
||||
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
|
||||
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
|
||||
val itemsNumber = ExpectedCompletionUtils.getExpectedNumber(fileText, platform)
|
||||
|
||||
Assert.assertTrue("Should be some assertions about completion", expected.size != 0 || unexpected.size != 0 || itemsNumber != null)
|
||||
ExpectedCompletionUtils.assertContainsRenderedItems(expected, items, ExpectedCompletionUtils.isWithOrder(fileText))
|
||||
ExpectedCompletionUtils.assertNotContainsRenderedItems(unexpected, items)
|
||||
Assert.assertTrue("Should be some assertions about completion", expected.size != 0 || unexpected.size != 0 || itemsNumber != null)
|
||||
ExpectedCompletionUtils.assertContainsRenderedItems(expected, items, ExpectedCompletionUtils.isWithOrder(fileText))
|
||||
ExpectedCompletionUtils.assertNotContainsRenderedItems(unexpected, items)
|
||||
|
||||
if (itemsNumber != null) {
|
||||
val expectedItems = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items))
|
||||
Assert.assertEquals("Invalid number of completion items: ${expectedItems}", itemsNumber, items.size)
|
||||
if (itemsNumber != null) {
|
||||
val expectedItems = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items))
|
||||
Assert.assertEquals("Invalid number of completion items: ${expectedItems}", itemsNumber, items.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun testWithAutoCompleteSetting(fileText: String, doTest: () -> Unit) {
|
||||
val autoComplete = ExpectedCompletionUtils.getAutocompleteSetting(fileText)
|
||||
if (autoComplete == null) {
|
||||
doTest()
|
||||
return
|
||||
}
|
||||
|
||||
val settings = CodeInsightSettings.getInstance()
|
||||
val oldValue1 = settings.AUTOCOMPLETE_ON_CODE_COMPLETION
|
||||
val oldValue2 = settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION
|
||||
try {
|
||||
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = autoComplete
|
||||
settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = autoComplete
|
||||
doTest()
|
||||
}
|
||||
finally {
|
||||
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = oldValue1
|
||||
settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = oldValue2
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,7 @@ public class ExpectedCompletionUtils {
|
||||
|
||||
private static final String INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:";
|
||||
private static final String WITH_ORDER_PREFIX = "WITH_ORDER:";
|
||||
private static final String AUTOCOMPLETE_SETTING_PREFIX = "AUTOCOMPLETE_SETTING:";
|
||||
|
||||
public static final List<String> KNOWN_PREFIXES = ImmutableList.of(
|
||||
EXIST_LINE_PREFIX,
|
||||
@@ -128,6 +129,7 @@ public class ExpectedCompletionUtils {
|
||||
NUMBER_JAVA_LINE_PREFIX,
|
||||
INVOCATION_COUNT_PREFIX,
|
||||
WITH_ORDER_PREFIX,
|
||||
AUTOCOMPLETE_SETTING_PREFIX,
|
||||
AstAccessControl.INSTANCE$.getALLOW_AST_ACCESS_DIRECTIVE());
|
||||
|
||||
@NotNull
|
||||
@@ -204,6 +206,11 @@ public class ExpectedCompletionUtils {
|
||||
return InTextDirectivesUtils.getPrefixedInt(fileText, INVOCATION_COUNT_PREFIX);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Boolean getAutocompleteSetting(String fileText) {
|
||||
return InTextDirectivesUtils.getPrefixedBoolean(fileText, AUTOCOMPLETE_SETTING_PREFIX);
|
||||
}
|
||||
|
||||
public static boolean isWithOrder(String fileText) {
|
||||
return InTextDirectivesUtils.getPrefixedInt(fileText, WITH_ORDER_PREFIX) != null;
|
||||
}
|
||||
|
||||
@@ -420,6 +420,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoAutoInsertionOfNotImported.kt")
|
||||
public void testNoAutoInsertionOfNotImported() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoInsertionOfNotImported.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoAutoPopupAfterNumberLiteral.kt")
|
||||
public void testNoAutoPopupAfterNumberLiteral() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoPopupAfterNumberLiteral.kt");
|
||||
|
||||
@@ -420,6 +420,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoAutoInsertionOfNotImported.kt")
|
||||
public void testNoAutoInsertionOfNotImported() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoInsertionOfNotImported.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoAutoPopupAfterNumberLiteral.kt")
|
||||
public void testNoAutoPopupAfterNumberLiteral() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoAutoPopupAfterNumberLiteral.kt");
|
||||
|
||||
@@ -112,6 +112,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoAutoInsertionOfNotImported")
|
||||
public void testNoAutoInsertionOfNotImported() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NoAutoInsertionOfNotImported/");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NotImportedExtensionForImplicitReceiver")
|
||||
public void testNotImportedExtensionForImplicitReceiver() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedExtensionForImplicitReceiver/");
|
||||
|
||||
@@ -67,6 +67,10 @@ public class CompletionMultifileHandlerTest extends KotlinCompletionTestCase {
|
||||
|
||||
configureByFiles(null, fileName + "-1.kt", fileName + "-2.kt");
|
||||
complete(2);
|
||||
if (myItems != null) {
|
||||
assertTrue("Multiple items in completion", myItems.length == 1);
|
||||
selectItem(myItems[0]);
|
||||
}
|
||||
checkResultByFile(fileName + ".kt.after");
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
package org.jetbrains.jet.plugin.quickfix;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.test.InnerTestClasses;
|
||||
import org.jetbrains.jet.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.jetbrains.jet.JUnit3RunnerWithInners;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
Reference in New Issue
Block a user