KT-759 Filter keywords auto completion according to context
This commit is contained in:
+129
-32
@@ -1,65 +1,162 @@
|
||||
package org.jetbrains.jet.plugin.completion;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.patterns.ElementPattern;
|
||||
import com.intellij.patterns.PlatformPatterns;
|
||||
import com.intellij.patterns.PsiElementPattern;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.filters.AndFilter;
|
||||
import com.intellij.psi.filters.ElementFilter;
|
||||
import com.intellij.psi.filters.NotFilter;
|
||||
import com.intellij.psi.filters.TextFilter;
|
||||
import com.intellij.psi.filters.position.FilterPattern;
|
||||
import com.intellij.psi.filters.position.LeftNeighbour;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.lang.psi.*;
|
||||
import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler;
|
||||
import org.jetbrains.jet.plugin.completion.handlers.JetKeywordInsertHandler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A keyword contributor for Kotlin
|
||||
* TODO: add different context for different keywords
|
||||
*
|
||||
* @author Nikolay Krasko
|
||||
*/
|
||||
public class JetKeywordCompletionContributor extends CompletionContributor {
|
||||
|
||||
private static class JetTopKeywordCompletionProvider extends CompletionProvider<CompletionParameters> {
|
||||
private final static InsertHandler<LookupElement> KEYWORDS_INSERT_HANDLER = new JetKeywordInsertHandler();
|
||||
private final static InsertHandler<LookupElement> FUNCTION_INSERT_HANDLER = new JetFunctionInsertHandler(
|
||||
JetFunctionInsertHandler.CaretPosition.AFTER_BRACKETS);
|
||||
|
||||
private final static List<String> FUNCTION_KEYWORDS = Lists.newArrayList("get", "set");
|
||||
|
||||
private final static InsertHandler<LookupElement> KEYWORDS_INSERT_HANDLER = new JetKeywordInsertHandler();
|
||||
|
||||
private final static String[] COMPLETE_KEYWORD = new String[] {
|
||||
"namespace", "as", "type", "class", "this", "super", "val", "var", "fun", "for", "null", "true",
|
||||
"false", "is", "in", "throw", "return", "break", "continue", "object", "if", "try", "else", "while",
|
||||
"do", "when", "trait", "This"
|
||||
};
|
||||
|
||||
private final static String[] COMPLETE_SOFT_KEYWORDS = new String[] {
|
||||
"import", "where", "by", "get", "set", "abstract", "enum", "open", "annotation", "override", "private",
|
||||
"public", "internal", "protected", "catch", "out", "vararg", "inline", "finally", "final", "ref"
|
||||
};
|
||||
|
||||
private static class InTopFilter implements ElementFilter {
|
||||
@Override
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
ProcessingContext context,
|
||||
@NotNull CompletionResultSet result) {
|
||||
public boolean isAcceptable(Object element, PsiElement context) {
|
||||
return PsiTreeUtil.getParentOfType(context, JetFile.class, false, JetClass.class, JetFunction.class) != null;
|
||||
}
|
||||
|
||||
for (String keyword : COMPLETE_KEYWORD) {
|
||||
|
||||
result.addElement(LookupElementBuilder.create(keyword).setInsertHandler(KEYWORDS_INSERT_HANDLER).setBold());
|
||||
}
|
||||
|
||||
for (String softKeyword : COMPLETE_SOFT_KEYWORDS) {
|
||||
result.addElement(LookupElementBuilder.create(softKeyword).setInsertHandler(KEYWORDS_INSERT_HANDLER));
|
||||
}
|
||||
@Override
|
||||
public boolean isClassAcceptable(Class hintClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InNonClassBlockFilter implements ElementFilter {
|
||||
@Override
|
||||
public boolean isAcceptable(Object element, PsiElement context) {
|
||||
return PsiTreeUtil.getParentOfType(context, JetFunction.class, true, JetClass.class) != null &&
|
||||
PsiTreeUtil.getParentOfType(context, JetBlockExpression.class, true, JetFunction.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassAcceptable(Class hintClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InParametersFilter implements ElementFilter {
|
||||
@Override
|
||||
public boolean isAcceptable(Object element, PsiElement context) {
|
||||
return PsiTreeUtil.getParentOfType(context, JetParameterList.class, false) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassAcceptable(Class hintClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InClassBodyFilter implements ElementFilter {
|
||||
@Override
|
||||
public boolean isAcceptable(Object element, PsiElement context) {
|
||||
return PsiTreeUtil.getParentOfType(context, JetClassBody.class, true, JetFunction.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassAcceptable(Class hintClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InPropertyFilter implements ElementFilter {
|
||||
@Override
|
||||
public boolean isAcceptable(Object element, PsiElement context) {
|
||||
return PsiTreeUtil.getParentOfType(context, JetProperty.class, false) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassAcceptable(Class hintClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeywordsCompletionProvider extends CompletionProvider<CompletionParameters> {
|
||||
|
||||
private final Collection<LookupElement> elements;
|
||||
|
||||
public KeywordsCompletionProvider(String ...keywords) {
|
||||
List<String> elementsList = Lists.newArrayList(keywords);
|
||||
elements = Collections2.transform(elementsList, new Function<String, LookupElement>() {
|
||||
@Override
|
||||
public LookupElement apply(String keyword) {
|
||||
final LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(keyword).setBold();
|
||||
|
||||
if (!FUNCTION_KEYWORDS.contains(keyword)) {
|
||||
return lookupElementBuilder.setInsertHandler(KEYWORDS_INSERT_HANDLER);
|
||||
}
|
||||
|
||||
return lookupElementBuilder.setInsertHandler(FUNCTION_INSERT_HANDLER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context,
|
||||
@NotNull CompletionResultSet result) {
|
||||
result.addAllElements(elements);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public JetKeywordCompletionContributor() {
|
||||
registerScopeKeywordsCompletion(new InTopFilter(),
|
||||
"abstract", "class", "enum", "final", "fun", "get", "import", "inline",
|
||||
"internal", "open", "package", "private", "protected", "public", "set",
|
||||
"trait", "type", "val", "var");
|
||||
|
||||
PsiElementPattern.Capture<PsiElement> notDotPlace =
|
||||
PlatformPatterns.psiElement().and(new FilterPattern(new NotFilter(new LeftNeighbour(new TextFilter(".")))));
|
||||
registerScopeKeywordsCompletion(new InClassBodyFilter(),
|
||||
"abstract", "class", "enum", "final", "fun", "inline", "internal", "get",
|
||||
"open", "override", "private", "protected", "public", "set", "trait",
|
||||
"type", "val", "var");
|
||||
|
||||
extend(CompletionType.BASIC,
|
||||
notDotPlace,
|
||||
new JetTopKeywordCompletionProvider());
|
||||
registerScopeKeywordsCompletion(new InNonClassBlockFilter(),
|
||||
"as", "break", "by", "catch", "class", "continue", "default", "do", "else",
|
||||
"enum", "false", "finally", "for", "fun", "get", "if", "in", "inline",
|
||||
"internal", "is", "null", "object", "private", "protected", "public", "ref",
|
||||
"return", "set", "super", "This", "this", "throw", "trait", "true", "try",
|
||||
"type", "val", "var", "vararg", "when", "where", "while");
|
||||
|
||||
registerScopeKeywordsCompletion(new InPropertyFilter(), "else", "false", "if", "null", "this", "true");
|
||||
|
||||
registerScopeKeywordsCompletion(new InParametersFilter(), "ref", "out");
|
||||
}
|
||||
|
||||
private void registerScopeKeywordsCompletion(final ElementFilter placeFilter, String... keywords) {
|
||||
extend(CompletionType.BASIC, getPlacePattern(placeFilter), new KeywordsCompletionProvider(keywords));
|
||||
}
|
||||
|
||||
private static ElementPattern<PsiElement> getPlacePattern(final ElementFilter placeFilter) {
|
||||
return PlatformPatterns.psiElement().and(
|
||||
new FilterPattern(new AndFilter(new NotFilter(new LeftNeighbour(new TextFilter("."))), placeFilter)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
class TestClass {
|
||||
val a : Int
|
||||
ge<caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
class TestClass {
|
||||
val a : Int
|
||||
get()<caret>
|
||||
}
|
||||
@@ -3,4 +3,4 @@ fun foo() {
|
||||
str. <caret>
|
||||
}
|
||||
|
||||
// ABSENT: public, val, in
|
||||
// ABSENT: public, val, in, if, when, this
|
||||
@@ -0,0 +1,54 @@
|
||||
class TestClass {
|
||||
<caret>
|
||||
}
|
||||
|
||||
// EXIST: abstract
|
||||
// ABSENT: annotation
|
||||
// ABSENT: as
|
||||
// ABSENT: break
|
||||
// ABSENT: by
|
||||
// ABSENT: catch
|
||||
// EXIST: class
|
||||
// ABSENT: continue
|
||||
// ABSENT: default
|
||||
// ABSENT: do
|
||||
// ABSENT: else
|
||||
// EXIST: enum
|
||||
// ABSENT: false
|
||||
// EXIST: final
|
||||
// ABSENT: finally
|
||||
// ABSENT: for
|
||||
// EXIST: fun
|
||||
// EXIST: get
|
||||
// ABSENT: if
|
||||
// ABSENT: import
|
||||
// ABSENT: in
|
||||
// EXIST: inline
|
||||
// EXIST: internal
|
||||
// ABSENT: is
|
||||
// ABSENT: null
|
||||
// ABSENT: object
|
||||
// EXIST: open
|
||||
// ABSENT: out
|
||||
// EXIST: override
|
||||
// ABSENT: package
|
||||
// EXIST: private
|
||||
// EXIST: protected
|
||||
// EXIST: public
|
||||
// ABSENT: ref
|
||||
// ABSENT: return
|
||||
// EXIST: set
|
||||
// ABSENT: super
|
||||
// ABSENT: This
|
||||
// ABSENT: this
|
||||
// ABSENT: throw
|
||||
// EXIST: trait
|
||||
// ABSENT: true
|
||||
// ABSENT: try
|
||||
// EXIST: type
|
||||
// EXIST: val
|
||||
// EXIST: var
|
||||
// ABSENT: vararg
|
||||
// ABSENT: when
|
||||
// ABSENT: where
|
||||
// ABSENT: while
|
||||
@@ -2,4 +2,53 @@ fun foo() {
|
||||
<caret>
|
||||
}
|
||||
|
||||
// EXIST: val, var
|
||||
// ABSENT: abstract
|
||||
// ??ABSENT: annotation // java annotation namespace
|
||||
// EXIST: as
|
||||
// EXIST: break
|
||||
// EXIST: by
|
||||
// EXIST: catch
|
||||
// EXIST: class
|
||||
// EXIST: continue
|
||||
// EXIST: default
|
||||
// EXIST: do
|
||||
// EXIST: else
|
||||
// EXIST: enum
|
||||
// EXIST: false
|
||||
// ABSENT: final
|
||||
// EXIST: finally
|
||||
// EXIST: for
|
||||
// EXIST: fun
|
||||
// EXIST: get
|
||||
// EXIST: if
|
||||
// ABSENT: import
|
||||
// EXIST: in
|
||||
// EXIST: inline
|
||||
// EXIST: internal
|
||||
// EXIST: is
|
||||
// EXIST: null
|
||||
// EXIST: object
|
||||
// ABSENT: open
|
||||
// ABSENT: out
|
||||
// ABSENT: override
|
||||
// ABSENT: package
|
||||
// EXIST: private
|
||||
// EXIST: protected
|
||||
// EXIST: public
|
||||
// EXIST: ref
|
||||
// EXIST: return
|
||||
// EXIST: set
|
||||
// EXIST: super
|
||||
// EXIST: This
|
||||
// EXIST: this
|
||||
// EXIST: throw
|
||||
// EXIST: trait
|
||||
// EXIST: true
|
||||
// EXIST: try
|
||||
// EXIST: type
|
||||
// EXIST: val
|
||||
// EXIST: var
|
||||
// EXIST: vararg
|
||||
// EXIST: when
|
||||
// EXIST: where
|
||||
// EXIST: while
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
fun test(<caret>) {
|
||||
|
||||
}
|
||||
|
||||
// ABSENT: abstract
|
||||
// ABSENT: annotation
|
||||
// ABSENT: as
|
||||
// ABSENT: break
|
||||
// ABSENT: by
|
||||
// ABSENT: catch
|
||||
// ABSENT: class
|
||||
// ABSENT: continue
|
||||
// ABSENT: default
|
||||
// ABSENT: do
|
||||
// ABSENT: else
|
||||
// ABSENT: enum
|
||||
// ABSENT: false
|
||||
// ABSENT: final
|
||||
// ABSENT: finally
|
||||
// ABSENT: for
|
||||
// ABSENT: fun
|
||||
// ABSENT: get
|
||||
// ABSENT: if
|
||||
// ABSENT: import
|
||||
// ABSENT: in
|
||||
// ABSENT: inline
|
||||
// ABSENT: internal
|
||||
// ABSENT: is
|
||||
// ABSENT: null
|
||||
// ABSENT: object
|
||||
// ABSENT: open
|
||||
// EXIST: out
|
||||
// ABSENT: override
|
||||
// ABSENT: package
|
||||
// ABSENT: private
|
||||
// ABSENT: protected
|
||||
// ABSENT: public
|
||||
// EXIST: ref
|
||||
// ABSENT: return
|
||||
// ABSENT: set
|
||||
// ABSENT: super
|
||||
// ABSENT: This
|
||||
// ABSENT: this
|
||||
// ABSENT: throw
|
||||
// ABSENT: trait
|
||||
// ABSENT: true
|
||||
// ABSENT: try
|
||||
// ABSENT: type
|
||||
// ABSENT: val
|
||||
// ABSENT: var
|
||||
// ABSENT: vararg
|
||||
// ABSENT: when
|
||||
// ABSENT: where
|
||||
// ABSENT: while
|
||||
@@ -0,0 +1,8 @@
|
||||
var a : Int = <caret>
|
||||
|
||||
// EXIST: else
|
||||
// EXIST: false
|
||||
// EXIST: if
|
||||
// EXIST: null
|
||||
// EXIST: this
|
||||
// EXIST: true
|
||||
@@ -2,5 +2,53 @@ fun foo() {
|
||||
val test : <caret>
|
||||
}
|
||||
|
||||
// TODO: Move all keywords to absent
|
||||
// EXIST: fun, val, var, namespace
|
||||
// ABSENT: abstract
|
||||
// ??ABSENT: annotation // java annotation namespace
|
||||
// EXIST: as
|
||||
// EXIST: break
|
||||
// EXIST: by
|
||||
// EXIST: catch
|
||||
// EXIST: class
|
||||
// EXIST: continue
|
||||
// EXIST: default
|
||||
// EXIST: do
|
||||
// EXIST: else
|
||||
// EXIST: enum
|
||||
// EXIST: false
|
||||
// ABSENT: final
|
||||
// EXIST: finally
|
||||
// EXIST: for
|
||||
// EXIST: fun
|
||||
// EXIST: get
|
||||
// EXIST: if
|
||||
// ABSENT: import
|
||||
// EXIST: in
|
||||
// EXIST: inline
|
||||
// EXIST: internal
|
||||
// EXIST: is
|
||||
// EXIST: null
|
||||
// EXIST: object
|
||||
// ABSENT: open
|
||||
// ABSENT: out
|
||||
// ABSENT: override
|
||||
// ABSENT: package
|
||||
// EXIST: private
|
||||
// EXIST: protected
|
||||
// EXIST: public
|
||||
// EXIST: ref
|
||||
// EXIST: return
|
||||
// EXIST: set
|
||||
// EXIST: super
|
||||
// EXIST: This
|
||||
// EXIST: this
|
||||
// EXIST: throw
|
||||
// EXIST: trait
|
||||
// EXIST: true
|
||||
// EXIST: try
|
||||
// EXIST: type
|
||||
// EXIST: val
|
||||
// EXIST: var
|
||||
// EXIST: vararg
|
||||
// EXIST: when
|
||||
// EXIST: where
|
||||
// EXIST: while
|
||||
|
||||
@@ -1,8 +1,52 @@
|
||||
<caret>
|
||||
|
||||
// EXIST: namespace, as, type, class, this, super, val, var, fun, for, null, true
|
||||
// EXIST: false, is, in, throw, return, break, continue, object, if, try, else, while
|
||||
// EXIST: do, when, trait, This
|
||||
// EXIST: import, where, by, get, set, abstract, enum, open, annotation, override, private
|
||||
// EXIST: public, internal, protected, catch, out, vararg, inline, finally, final, ref
|
||||
// ABSENT: ?in, new, extends, implements
|
||||
// EXIST: abstract
|
||||
// ABSENT: annotation
|
||||
// ABSENT: as
|
||||
// ABSENT: break
|
||||
// ABSENT: by
|
||||
// ABSENT: catch
|
||||
// EXIST: class
|
||||
// ABSENT: continue
|
||||
// ABSENT: default
|
||||
// ABSENT: do
|
||||
// ABSENT: else
|
||||
// EXIST: enum
|
||||
// ABSENT: false
|
||||
// EXIST: final
|
||||
// ABSENT: finally
|
||||
// ABSENT: for
|
||||
// EXIST: fun
|
||||
// EXIST: get
|
||||
// ABSENT: if
|
||||
// EXIST: import
|
||||
// ABSENT: in
|
||||
// EXIST: inline
|
||||
// EXIST: internal
|
||||
// ABSENT: is
|
||||
// ABSENT: null
|
||||
// ABSENT: object
|
||||
// EXIST: open
|
||||
// ABSENT: out
|
||||
// ABSENT: override
|
||||
// EXIST: package
|
||||
// EXIST: private
|
||||
// EXIST: protected
|
||||
// EXIST: public
|
||||
// ABSENT: ref
|
||||
// ABSENT: return
|
||||
// EXIST: set
|
||||
// ABSENT: super
|
||||
// ABSENT: This
|
||||
// ABSENT: this
|
||||
// ABSENT: throw
|
||||
// EXIST: trait
|
||||
// ABSENT: true
|
||||
// ABSENT: try
|
||||
// EXIST: type
|
||||
// EXIST: val
|
||||
// EXIST: var
|
||||
// ABSENT: vararg
|
||||
// ABSENT: when
|
||||
// ABSENT: where
|
||||
// ABSENT: while
|
||||
@@ -12,15 +12,15 @@ import java.io.IOException;
|
||||
public class KeywordsHandlerTest extends LightCompletionTestCase {
|
||||
|
||||
public void testSpaceAfter() throws IOException {
|
||||
configureFromFileText("Test.kt", "protecte<caret>");
|
||||
configureFromFileText("Test.kt", "impor<caret>");
|
||||
complete();
|
||||
checkResultByText("protected <caret>");
|
||||
checkResultByText("import <caret>");
|
||||
}
|
||||
|
||||
public void testNoSpaceAfter() throws IOException {
|
||||
configureFromFileText("Test.kt", "nul<caret>");
|
||||
configureFromFileText("Test.kt", "fun test() { nul<caret> }");
|
||||
complete();
|
||||
checkResultByText("null<caret>");
|
||||
checkResultByText("fun test() { null<caret> }");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user