Completion: better replacement range calculation for both basic and smart completions

This commit is contained in:
Valentin Kipyatkov
2013-12-19 16:01:49 +04:00
parent d28705a5ec
commit 24b68e7bdf
25 changed files with 145 additions and 11 deletions
@@ -20,13 +20,14 @@ import com.intellij.codeInsight.completion.*;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.JetNodeTypes;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.plugin.references.JetSimpleNameReference;
public class JetCompletionContributor extends CompletionContributor {
@@ -117,17 +118,22 @@ public class JetCompletionContributor extends CompletionContributor {
}
}
if (!context.getEditor().getSelectionModel().hasSelection()) {
PsiReference reference = context.getFile().findReferenceAt(offset);
if (reference != null) {
PsiElement atElement = context.getFile().findElementAt(offset);
assert atElement != null : String.format("Element is not expected to be null: %d in file %s",
offset, context.getFile().getText());
// this code will make replacement offset "modified" and prevents altering it by the code in CompletionProgressIndicator
context.setReplacementOffset(context.getReplacementOffset());
IElementType parentElementType = atElement.getParent().getNode().getElementType();
if (!(reference instanceof JetSimpleNameReference) || parentElementType == JetNodeTypes.OPERATION_REFERENCE) {
context.setReplacementOffset(offset);
if (context.getCompletionType() == CompletionType.SMART) {
PsiElement tokenAt = context.getFile().findElementAt(Math.max(0, offset));
if (tokenAt != null) {
PsiElement parent = tokenAt.getParent();
if (parent instanceof JetExpression) {
// search expression to be replaced - go up while we are the first child of parent expression
JetExpression expression = (JetExpression) parent;
parent = expression.getParent();
while (parent instanceof JetExpression && parent.getFirstChild() == expression) {
expression = (JetExpression) parent;
parent = expression.getParent();
}
context.setReplacementOffset(expression.getTextRange().getEndOffset());
}
}
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(<caret>xxx)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(<caret>x.y())
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(sss<caret>.y())
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(<caret>null)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(<caret>123)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(<caret>this)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(<caret>true)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun String.bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(<caret>x.y())
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(<caret>1 + x)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(<caret>x().y.z())
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(sss: String) {
foo(sss<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(ss: String) {
foo(<caret>xxx)
}
@@ -0,0 +1,5 @@
fun foo(s: String){ }
fun bar(ss: String) {
foo(ss<caret>)
}
@@ -0,0 +1,8 @@
var b1: Boolean = true
var b2: Boolean = true
var b3: Boolean = true
val c: String = ""
fun bar() {
if (b1 && <caret>b2 && b3) {}
}
@@ -0,0 +1,8 @@
var b1: Boolean = true
var b2: Boolean = true
var b3: Boolean = true
val c: String = ""
fun bar() {
if (b1 && b3 && b3) {}
}
@@ -66,6 +66,13 @@ public class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
fun testInsertFunctionWithSingleParameterWithBrace() = doTest(0, "some", null, '{')
fun testTabReplaceIdentifier() = doTest(1, "sss", null, '\t')
fun testTabReplaceIdentifier2() = doTest(1, "sss", null, '\t')
fun testTabReplaceThis() = doTest(1, "sss", null, '\t')
fun testTabReplaceNull() = doTest(1, "sss", null, '\t')
fun testTabReplaceTrue() = doTest(1, "sss", null, '\t')
fun testTabReplaceNumber() = doTest(1, "sss", null, '\t')
fun testSingleBrackets() {
fixture.configureByFile(fileName())
fixture.`type`('(')
@@ -46,4 +46,9 @@ public class SmartCompletionHandlerTest() : CompletionHandlerTestBase() {
fun testClassObjectMethod1() = doTest(1, "K.bar", null, '\n')
fun testClassObjectMethod2() = doTest(1, "K.bar", null, '\n')
//fun testJavaStaticFieldInsertImport() = doTest(1, "Locale.ENGLISH", null, '\n') //TODO
fun testTabReplaceIdentifier() = doTest(1, "ss", null, '\t')
fun testTabReplaceExpression() = doTest(1, "sss", null, '\t')
fun testTabReplaceExpression2() = doTest(1, "sss", null, '\t')
fun testTabReplaceExpression3() = doTest(1, "sss", null, '\t')
fun testTabReplaceOperand() = doTest(1, "b3", null, '\t')
}