KT-4914 Smart completion should work after 'as' and 'as?'
#KT-4914 Fixed
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Smart Completion Tests" type="JUnit" factoryName="JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="org.jetbrains.jet.completion.handlers.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<module name="idea" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="" />
|
||||
<option name="PACKAGE_NAME" value="org.jetbrains.jet.completion.handlers" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="pattern" />
|
||||
<option name="VM_PARAMETERS" value="-ea -Xmx512m -XX:MaxPermSize=320m" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs>
|
||||
<env name="kotlin.tests.actually.compile" value="false" />
|
||||
</envs>
|
||||
<patterns>
|
||||
<pattern testClass="org.jetbrains.jet.completion.JvmSmartCompletionTestGenerated" />
|
||||
<pattern testClass="org.jetbrains.jet.completion.handlers.SmartCompletionHandlerTestGenerated" />
|
||||
</patterns>
|
||||
<RunnerSettings RunnerId="Debug">
|
||||
<option name="DEBUG_PORT" value="" />
|
||||
<option name="TRANSPORT" value="0" />
|
||||
<option name="LOCAL" value="true" />
|
||||
</RunnerSettings>
|
||||
<RunnerSettings RunnerId="Run" />
|
||||
<ConfigurationWrapper RunnerId="Debug" />
|
||||
<ConfigurationWrapper RunnerId="Run" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -136,13 +136,23 @@ public class JetCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
|
||||
int expressionEnd = expression.getTextRange().getEndOffset();
|
||||
String text = context.getFile().getText();
|
||||
while (expressionEnd > 0 && Character.isWhitespace(text.charAt(expressionEnd - 1))) {
|
||||
expressionEnd--;
|
||||
}
|
||||
|
||||
int suggestedReplacementOffset;
|
||||
if (expression instanceof JetCallExpression) {
|
||||
JetExpression calleeExpression = ((JetCallExpression) expression).getCalleeExpression();
|
||||
context.setReplacementOffset(calleeExpression != null ? calleeExpression.getTextRange().getEndOffset() : expressionEnd);
|
||||
suggestedReplacementOffset = calleeExpression != null ? calleeExpression.getTextRange().getEndOffset() : expressionEnd;
|
||||
}
|
||||
else{
|
||||
context.setReplacementOffset(expressionEnd);
|
||||
else {
|
||||
suggestedReplacementOffset = expressionEnd;
|
||||
}
|
||||
if (suggestedReplacementOffset > context.getReplacementOffset()) {
|
||||
context.setReplacementOffset(suggestedReplacementOffset);
|
||||
}
|
||||
|
||||
context.getOffsetMap().addOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET, expressionEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.jetbrains.jet.plugin.util.makeNotNullable
|
||||
import org.jetbrains.jet.plugin.util.makeNullable
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.jet.plugin.caches.resolve.getLazyResolveSession
|
||||
import org.jetbrains.jet.renderer.DescriptorRenderer
|
||||
|
||||
class SmartCompletion(val expression: JetSimpleNameExpression,
|
||||
val resolveSession: ResolveSessionForBodies,
|
||||
@@ -64,6 +65,9 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
|
||||
}
|
||||
|
||||
private fun buildLookupElementsInternal(referenceVariants: Iterable<DeclarationDescriptor>): Collection<LookupElement>? {
|
||||
val elements = buildForAsTypePosition()
|
||||
if (elements != null) return elements
|
||||
|
||||
val parent = expression.getParent()
|
||||
val expressionWithType: JetExpression
|
||||
val receiver: JetExpression?
|
||||
@@ -254,6 +258,61 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
|
||||
return null
|
||||
}
|
||||
|
||||
private fun buildForAsTypePosition(): Collection<LookupElement>? {
|
||||
val binaryExpression = ((expression.getParent() as? JetUserType)
|
||||
?.getParent() as? JetTypeReference)
|
||||
?.getParent() as? JetBinaryExpressionWithTypeRHS
|
||||
if (binaryExpression != null) {
|
||||
val elementType = binaryExpression.getOperationReference().getReferencedNameElementType()
|
||||
if (elementType == JetTokens.AS_KEYWORD || elementType == JetTokens.AS_SAFE) {
|
||||
val expectedInfos = calcExpectedInfos(binaryExpression) ?: return null
|
||||
|
||||
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.`type`.makeNotNullable() }
|
||||
|
||||
val result = ArrayList<LookupElement>()
|
||||
for ((jetType, infos) in expectedInfosGrouped) {
|
||||
val lookupElement = lookupElementForType(jetType) ?: continue
|
||||
result.add(lookupElement.addTail(infos))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun lookupElementForType(jetType: JetType): LookupElement? {
|
||||
if (jetType.isError()) return null
|
||||
val classifier = jetType.getConstructor().getDeclarationDescriptor() ?: return null
|
||||
|
||||
val lookupElement = createLookupElement(classifier, resolveSession)
|
||||
val lookupString = lookupElement.getLookupString()
|
||||
|
||||
val typeArgs = jetType.getArguments()
|
||||
var itemText = lookupString + DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderTypeArguments(typeArgs)
|
||||
val typeText = DescriptorUtils.getFqName(classifier).toString() + DescriptorRenderer.SOURCE_CODE.renderTypeArguments(typeArgs)
|
||||
|
||||
val insertHandler: InsertHandler<LookupElement> = object : InsertHandler<LookupElement> {
|
||||
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), typeText)
|
||||
context.setTailOffset(context.getStartOffset() + typeText.length)
|
||||
shortenReferences(context, context.getStartOffset(), context.getTailOffset())
|
||||
}
|
||||
}
|
||||
|
||||
return object: LookupElementDecorator<LookupElement>(lookupElement) {
|
||||
override fun getLookupString() = lookupString
|
||||
|
||||
override fun renderElement(presentation: LookupElementPresentation) {
|
||||
getDelegate().renderElement(presentation)
|
||||
presentation.setItemText(itemText)
|
||||
}
|
||||
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
insertHandler.handleInsert(context, getDelegate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class object {
|
||||
public val OLD_ARGUMENTS_REPLACEMENT_OFFSET: OffsetKey = OffsetKey.create("nonFunctionReplacementOffset")
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ import org.jetbrains.jet.plugin.util.makeNotNullable
|
||||
class TypeInstantiationItems(val resolveSession: ResolveSessionForBodies, val visibilityFilter: (DeclarationDescriptor) -> Boolean) {
|
||||
public fun addToCollection(collection: MutableCollection<LookupElement>, expectedInfos: Collection<ExpectedInfo>) {
|
||||
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.`type`.makeNotNullable() }
|
||||
for ((jetType, types) in expectedInfosGrouped) {
|
||||
val tail = mergeTails(types.map { it.tail })
|
||||
for ((jetType, infos) in expectedInfosGrouped) {
|
||||
val tail = mergeTails(infos.map { it.tail })
|
||||
addToCollection(collection, jetType, tail)
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class TypeInstantiationItems(val resolveSession: ResolveSessionForBodies, val vi
|
||||
if (KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(jetType)) return // do not show "object: ..." for function types
|
||||
|
||||
val classifier = jetType.getConstructor().getDeclarationDescriptor()
|
||||
if (!(classifier is ClassDescriptor)) return
|
||||
if (classifier !is ClassDescriptor) return
|
||||
|
||||
val isAbstract = classifier.getModality() == Modality.ABSTRACT
|
||||
val allConstructors = classifier.getConstructors()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p1: String, p2: Int){ }
|
||||
|
||||
fun bar(o: Any){
|
||||
foo(o as <caret>)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p1: String, p2: Int){ }
|
||||
|
||||
fun bar(o: Any){
|
||||
foo(o as String, <caret>)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: java.util.HashMap<String, java.io.File>){ }
|
||||
|
||||
fun bar(o: Any){
|
||||
foo(o as <caret>)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import java.io.File
|
||||
import java.util.HashMap
|
||||
|
||||
fun foo(p: java.util.HashMap<String, java.io.File>){ }
|
||||
|
||||
fun bar(o: Any){
|
||||
foo(o as HashMap<String, File>)<caret>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(l: List<String>){}
|
||||
fun foo(l: List<String>, p: Int){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as <caret>)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(l: List<String>){}
|
||||
fun foo(l: List<String>, p: Int){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as List<String><caret>)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: java.io.File?){ }
|
||||
|
||||
fun bar(o: Any){
|
||||
foo(o as? <caret>)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import java.io.File
|
||||
|
||||
fun foo(p: java.io.File?){ }
|
||||
|
||||
fun bar(o: Any){
|
||||
foo(o as? File)<caret>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(i: Int){}
|
||||
fun foo(i: Int, c: Char){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as <caret>)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(i: Int){}
|
||||
fun foo(i: Int, c: Char){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as Int<caret>)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun bar(o: Any): String {
|
||||
return o as <caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun bar(o: Any): String {
|
||||
return o as String<caret>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fun foo(s: String?){}
|
||||
|
||||
fun foo(i: Int){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as <caret>)
|
||||
}
|
||||
|
||||
// NUMBER: 2
|
||||
// EXIST: String
|
||||
// EXIST: Int
|
||||
@@ -0,0 +1,11 @@
|
||||
fun foo(s: List<String>){}
|
||||
|
||||
fun foo(i: Map<String, Int>){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as <caret>)
|
||||
}
|
||||
|
||||
// NUMBER: 2
|
||||
// EXIST: { lookupString:"List", itemText:"List<String>" }
|
||||
// EXIST: { lookupString:"Map", itemText:"Map<String, Int>" }
|
||||
@@ -0,0 +1,12 @@
|
||||
fun foo(l: List<String>){}
|
||||
fun foo(l: List<String>, p: Int){}
|
||||
|
||||
fun foo(s: String?){}
|
||||
|
||||
fun bar(o: Any) {
|
||||
foo(o as <caret>)
|
||||
}
|
||||
|
||||
// NUMBER: 2
|
||||
// EXIST: { lookupString:"List", itemText:"List<String>" }
|
||||
// EXIST: { lookupString:"String", itemText:"String" }
|
||||
@@ -32,6 +32,21 @@ import org.jetbrains.jet.completion.AbstractJvmSmartCompletionTest;
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/completion/smart")
|
||||
public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionTest {
|
||||
@TestMetadata("AfterAs.kt")
|
||||
public void testAfterAs() throws Exception {
|
||||
doTest("idea/testData/completion/smart/AfterAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AfterAs2.kt")
|
||||
public void testAfterAs2() throws Exception {
|
||||
doTest("idea/testData/completion/smart/AfterAs2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AfterAs3.kt")
|
||||
public void testAfterAs3() throws Exception {
|
||||
doTest("idea/testData/completion/smart/AfterAs3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AfterExclSign.kt")
|
||||
public void testAfterExclSign() throws Exception {
|
||||
doTest("idea/testData/completion/smart/AfterExclSign.kt");
|
||||
|
||||
@@ -18,8 +18,6 @@ package org.jetbrains.jet.completion.handlers
|
||||
|
||||
import org.jetbrains.jet.plugin.JetLightCodeInsightFixtureTestCase
|
||||
import com.intellij.codeInsight.completion.CompletionType
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettingsManager
|
||||
import org.jetbrains.jet.plugin.formatter.JetCodeStyleSettings
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import java.io.File
|
||||
import org.jetbrains.jet.plugin.PluginTestCaseBase
|
||||
@@ -31,8 +29,6 @@ import com.intellij.codeInsight.lookup.LookupElementPresentation
|
||||
import org.junit.Assert
|
||||
import com.intellij.openapi.application.Result
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
|
||||
import kotlin.properties.Delegates
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
|
||||
public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTestCase() {
|
||||
protected abstract val completionType : CompletionType
|
||||
@@ -41,28 +37,27 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
||||
protected val fixture: JavaCodeInsightTestFixture
|
||||
get() = myFixture
|
||||
|
||||
protected fun doTest() : Unit = doTest(2, null, null, null, '\n')
|
||||
protected fun doTest() : Unit = doTest(2, "*", null, null, '\n')
|
||||
|
||||
protected fun doTest(time : Int, lookupString : String?, tailText : String?, completionChar : Char) {
|
||||
protected fun doTest(time: Int, lookupString: String?, tailText: String?, completionChar: Char) {
|
||||
doTest(time, lookupString, null, tailText, completionChar)
|
||||
}
|
||||
|
||||
protected fun doTest(time : Int, lookupString : String?, itemText: String?, tailText : String?, completionChar : Char) {
|
||||
protected fun doTest(time: Int, lookupString: String?, itemText: String?, tailText: String?, completionChar: Char) {
|
||||
fixture.configureByFile(fileName())
|
||||
doTestWithTextLoaded(time, lookupString, itemText, tailText, completionChar)
|
||||
}
|
||||
|
||||
protected fun doTestWithTextLoaded(time : Int, lookupString : String?, itemText: String?, tailText : String?, completionChar : Char) {
|
||||
protected fun doTestWithTextLoaded(time: Int, lookupString: String?, itemText: String?, tailText: String?, completionChar: Char) {
|
||||
fixture.complete(completionType, time)
|
||||
|
||||
if (lookupString != null || itemText != null || tailText != null) {
|
||||
fixture.complete(completionType, time)
|
||||
val item = getExistentLookupElement(lookupString, itemText, tailText)
|
||||
if (item != null) {
|
||||
selectItem(item, completionChar)
|
||||
}
|
||||
}
|
||||
else {
|
||||
forceCompleteFirst(completionType, time)
|
||||
}
|
||||
|
||||
checkResult()
|
||||
}
|
||||
|
||||
@@ -70,13 +65,20 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
||||
fixture.checkResultByFile(afterFileName())
|
||||
}
|
||||
|
||||
private fun getExistentLookupElement(lookupString : String?, itemText: String?, tailText : String?) : LookupElement? {
|
||||
private fun getExistentLookupElement(lookupString: String?, itemText: String?, tailText: String?): LookupElement? {
|
||||
val lookup = LookupManager.getInstance(getProject())?.getActiveLookup() as LookupImpl?
|
||||
if (lookup == null) return null
|
||||
val items = lookup.getItems()
|
||||
|
||||
if (lookupString == "*") {
|
||||
assert(itemText == null)
|
||||
assert(tailText == null)
|
||||
return items.firstOrNull()
|
||||
}
|
||||
|
||||
var foundElement : LookupElement? = null
|
||||
val presentation = LookupElementPresentation()
|
||||
for (lookupElement in lookup.getItems()) {
|
||||
for (lookupElement in items) {
|
||||
val lookupOk = if (lookupString != null) lookupElement.getLookupString().contains(lookupString) else true
|
||||
|
||||
if (lookupOk) {
|
||||
@@ -116,21 +118,13 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
||||
|
||||
protected fun afterFileName(): String = getTestName(false) + ".kt.after"
|
||||
|
||||
private fun forceCompleteFirst(`type` : CompletionType, time : Int) {
|
||||
fixture.complete(`type`, time)
|
||||
val items : Array<LookupElement>? = fixture.getLookupElements()
|
||||
if (items != null && items.isNotEmpty()) {
|
||||
selectItem(items[0])
|
||||
}
|
||||
}
|
||||
|
||||
protected override fun getTestDataPath() : String = File(PluginTestCaseBase.getTestDataPathBase(), testDataRelativePath).getPath() + File.separator
|
||||
|
||||
protected fun selectItem(item : LookupElement?) {
|
||||
protected fun selectItem(item: LookupElement?) {
|
||||
selectItem(item, 0.toChar())
|
||||
}
|
||||
|
||||
protected fun selectItem(item : LookupElement?, completionChar : Char) {
|
||||
protected fun selectItem(item: LookupElement?, completionChar: Char) {
|
||||
val lookup = (fixture.getLookup() as LookupImpl)
|
||||
lookup.setCurrentItem(item)
|
||||
if (LookupEvent.isSpecialCompletionChar(completionChar)) {
|
||||
|
||||
+30
@@ -32,6 +32,26 @@ import org.jetbrains.jet.completion.handlers.AbstractSmartCompletionHandlerTest;
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/completion/handlers/smart")
|
||||
public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletionHandlerTest {
|
||||
@TestMetadata("AfterAs.kt")
|
||||
public void testAfterAs() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/AfterAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AfterAs2.kt")
|
||||
public void testAfterAs2() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/AfterAs2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AfterAs3.kt")
|
||||
public void testAfterAs3() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/AfterAs3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AfterSafeAs.kt")
|
||||
public void testAfterSafeAs() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/AfterSafeAs.kt");
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSmart() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/completion/handlers/smart"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
@@ -56,6 +76,16 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
|
||||
doTest("idea/testData/completion/handlers/smart/AnonymousObjectInsertsImport.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AutoCompleteAfterAs1.kt")
|
||||
public void testAutoCompleteAfterAs1() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/AutoCompleteAfterAs1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AutoCompleteAfterAs2.kt")
|
||||
public void testAutoCompleteAfterAs2() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/AutoCompleteAfterAs2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassObjectMethod1.kt")
|
||||
public void testClassObjectMethod1() throws Exception {
|
||||
doTest("idea/testData/completion/handlers/smart/ClassObjectMethod1.kt");
|
||||
|
||||
Reference in New Issue
Block a user