Code completion: space after comma on selecting item with comma key

This commit is contained in:
Valentin Kipyatkov
2014-11-14 12:34:43 +03:00
parent 3ebc681eec
commit 57692b1a37
13 changed files with 112 additions and 15 deletions
@@ -20,16 +20,18 @@ import com.intellij.codeInsight.lookup.CharFilter
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.CharFilter.Result
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler
import com.intellij.openapi.util.Key
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
public class KotlinCompletionCharFilter() : CharFilter() {
class object {
public val ACCEPT_OPENING_BRACE: Key<Boolean> = Key("KotlinCompletionCharFilter.ACCEPT_OPENNING_BRACE")
public val ACCEPT_EQ: Key<Boolean> = Key("KotlinCompletionCharFilter.ACCEPT_EQ")
public val SELECTED_ITEM_PREFIX: Key<String> = Key("KotlinCompletionCharFilter.SELECTED_ITEM_PREFIX")
}
public override fun acceptChar(c : Char, prefixLength : Int, lookup : Lookup) : Result? {
override fun acceptChar(c : Char, prefixLength : Int, lookup : Lookup) : Result? {
if (lookup.getPsiFile() !is JetFile) return null
if (!lookup.isCompletion()) return null
@@ -38,6 +40,8 @@ public class KotlinCompletionCharFilter() : CharFilter() {
}
val currentItem = lookup.getCurrentItem()
currentItem?.putUserData(SELECTED_ITEM_PREFIX, lookup.itemPattern(currentItem))
return when (c) {
'.' -> {
//TODO: this heuristics better to be only used for auto-popup completion but I see no way to check this
@@ -66,7 +70,7 @@ public class KotlinCompletionCharFilter() : CharFilter() {
',', ' ', '(' -> Result.SELECT_ITEM_AND_FINISH_LOOKUP
else -> return CharFilter.Result.HIDE_LOOKUP
else -> CharFilter.Result.HIDE_LOOKUP
}
}
}
@@ -28,6 +28,7 @@ import com.intellij.codeInsight.completion.PrefixMatcher
import java.util.ArrayList
import com.intellij.codeInsight.completion.CompletionResultSet
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import com.intellij.openapi.util.TextRange
class LookupElementsCollector(private val prefixMatcher: PrefixMatcher, private val resolveSession: ResolveSessionForBodies) {
private val elements = ArrayList<LookupElement>()
@@ -89,7 +90,18 @@ class LookupElementsCollector(private val prefixMatcher: PrefixMatcher, private
public fun addElement(element: LookupElement) {
if (prefixMatcher.prefixMatches(element)) {
elements.add(element)
elements.add(object: LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext) {
getDelegate().handleInsert(context)
if (context.getCompletionChar() == ',' && context.shouldAddCompletionChar()) {
val insertedText = context.getDocument().getText(TextRange(context.getStartOffset(), context.getTailOffset()))
if (insertedText != getUserData(KotlinCompletionCharFilter.SELECTED_ITEM_PREFIX)) { // avoid insertion of space after comma if we have typed the whole text to insert already
WithTailInsertHandler.commaTail().postHandleInsert(context, getDelegate())
}
}
}
})
}
}
@@ -31,10 +31,14 @@ class WithTailInsertHandler(val tailText: String,
val overwriteText: Boolean = true) : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
item.handleInsert(context)
postHandleInsert(context, item)
}
if (tailText == context.getCompletionChar().toString() && context.shouldAddCompletionChar()) {
return
fun postHandleInsert(context: InsertionContext, item: LookupElement) {
if (tailText == context.getCompletionChar().toString()) {
context.setAddCompletionChar(false)
}
//TODO: what if completion char is different?
val document = context.getDocument()
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(document)
@@ -47,10 +51,10 @@ class WithTailInsertHandler(val tailText: String,
val moveCaret = context.getEditor().getCaretModel().getOffset() == tailOffset
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == c
fun isTextAt(offset: Int, text: String) = offset + text.length <= document.getTextLength() && document.getText(TextRange(offset, offset + text.length)) == text
if (overwriteText) {
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == c
fun isTextAt(offset: Int, text: String) = offset + text.length <= document.getTextLength() && document.getText(TextRange(offset, offset + text.length)) == text
if (spaceBefore && isCharAt(tailOffset, ' ')) {
document.deleteString(tailOffset, tailOffset + 1)
}
@@ -79,4 +83,10 @@ class WithTailInsertHandler(val tailText: String,
}
}
}
class object {
fun commaTail() = WithTailInsertHandler(",", spaceBefore = false, spaceAfter = true /*TODO: use code style option*/)
fun rparenthTail() = WithTailInsertHandler(")", spaceBefore = false, spaceAfter = false)
fun elseTail() = WithTailInsertHandler("else", spaceBefore = true, spaceAfter = true)
}
}
@@ -71,19 +71,19 @@ fun LookupElement.addTail(tail: Tail?): LookupElement {
Tail.COMMA -> object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler(",", spaceBefore = false, spaceAfter = true /*TODO: use code style option*/).handleInsert(context, getDelegate())
WithTailInsertHandler.commaTail().handleInsert(context, getDelegate())
}
}
Tail.RPARENTH -> object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
handlers.WithTailInsertHandler(")", spaceBefore = false, spaceAfter = false).handleInsert(context, getDelegate())
WithTailInsertHandler.rparenthTail().handleInsert(context, getDelegate())
}
}
Tail.ELSE -> object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
handlers.WithTailInsertHandler("else", spaceBefore = true, spaceAfter = true).handleInsert(context, getDelegate())
WithTailInsertHandler.elseTail().handleInsert(context, getDelegate())
}
}
}
@@ -2,7 +2,7 @@ fun foo(p1: Int) { }
fun foo(p1: Int, p2: Int) { }
fun bar(ppp: Int, a: Int) {
foo(ppp,<caret>)
foo(ppp, <caret>)
}
// ELEMENT: ppp
@@ -1,7 +1,7 @@
fun foo(p1: Int, p2: Int) { }
fun bar(pp: Int, a: Int) {
foo(pp,<caret>)
foo(pp, <caret>)
}
// COMPLETION_TYPE: SMART
@@ -0,0 +1,11 @@
// this test tests that no space auto-inserted after comma if we are just typing
fun foo(p1: Int, p2: Int) { }
fun bar(ppp: Int, ppp1: Int) {
foo(ppp<caret>)
}
// INVOCATION_COUNT: 0
// ELEMENT: *
// CHAR: ','
@@ -0,0 +1,11 @@
// this test tests that no space auto-inserted after comma if we are just typing
fun foo(p1: Int, p2: Int) { }
fun bar(ppp: Int, ppp1: Int) {
foo(ppp,<caret>)
}
// INVOCATION_COUNT: 0
// ELEMENT: *
// CHAR: ','
@@ -0,0 +1,8 @@
fun foo(p1: Int?, p2: Int) { }
fun bar(p: Int) {
foo(<caret>)
}
// ELEMENT: null
// CHAR: ','
@@ -0,0 +1,8 @@
fun foo(p1: Int?, p2: Int) { }
fun bar(p: Int) {
foo(null, <caret>)
}
// ELEMENT: null
// CHAR: ','
@@ -0,0 +1,8 @@
fun foo(p1: Int?, p2: Int) { }
fun bar(nullable: Int?) {
foo(null<caret>)
}
// ELEMENT: *
// CHAR: ','
@@ -0,0 +1,8 @@
fun foo(p1: Int?, p2: Int) { }
fun bar(nullable: Int?) {
foo(null,<caret>)
}
// ELEMENT: *
// CHAR: ','
@@ -19,7 +19,6 @@ package org.jetbrains.jet.completion.handlers;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.junit.runner.RunWith;
@@ -54,6 +53,24 @@ public class CompletionCharFilterTestGenerated extends AbstractCompletionCharFil
doTest(fileName);
}
@TestMetadata("Comma3.kt")
public void testComma3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/charFilter/Comma3.kt");
doTest(fileName);
}
@TestMetadata("Comma4.kt")
public void testComma4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/charFilter/Comma4.kt");
doTest(fileName);
}
@TestMetadata("Comma5.kt")
public void testComma5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/charFilter/Comma5.kt");
doTest(fileName);
}
@TestMetadata("ConstructorWithLambdaArg1.kt")
public void testConstructorWithLambdaArg1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/charFilter/ConstructorWithLambdaArg1.kt");