KT-688: Show kotlin keywords as auto completion in IDEA

This commit is contained in:
Nikolay Krasko
2011-11-30 18:33:37 +04:00
parent ce1718e518
commit 1e3c45e65c
10 changed files with 256 additions and 1 deletions
@@ -8,10 +8,16 @@ import org.jetbrains.annotations.NotNull;
public class JetKeywordToken extends JetToken {
/**
* Generate soft keyword (identifier that has a keyword meaning only in some contexts)
*/
public static JetKeywordToken keyword(String value) {
return new JetKeywordToken(value, false);
}
/**
* Generate keyword (identifier that has a keyword meaning in all possible contexts)
*/
public static JetKeywordToken softKeyword(String value) {
return new JetKeywordToken(value, true);
}
@@ -29,6 +35,7 @@ public class JetKeywordToken extends JetToken {
return myValue;
}
public boolean isSoft() {
return myIsSoft;
}
+2
View File
@@ -42,6 +42,7 @@
<lang.formatter language="jet" implementationClass="org.jetbrains.jet.plugin.formatter.JetFormattingModelBuilder"/>
<lang.findUsagesProvider language="jet" implementationClass="org.jetbrains.jet.plugin.findUsages.JetFindUsagesProvider"/>
<psi.referenceContributor language="jet" implementation="org.jetbrains.jet.plugin.references.JetReferenceContributor"/>
<completion.contributor language="jet" implementationClass="org.jetbrains.jet.plugin.completion.JetKeywordCompletionContributor"/>
<annotator language="jet" implementationClass="org.jetbrains.jet.plugin.annotations.SoftKeywordsAnnotator"/>
<annotator language="jet" implementationClass="org.jetbrains.jet.plugin.annotations.LabelsAnnotator"/>
<annotator language="jet" implementationClass="org.jetbrains.jet.plugin.annotations.JetPsiChecker"/>
@@ -57,6 +58,7 @@
<codeInsight.implementMethod language="jet" implementationClass="org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler"/>
<codeInsight.overrideMethod language="jet" implementationClass="org.jetbrains.jet.plugin.codeInsight.OverrideMethodsHandler"/>
<java.elementFinder implementation="org.jetbrains.jet.plugin.java.JavaElementFinder"/>
<toolWindow id="CodeWindow"
factoryClass="org.jetbrains.jet.plugin.internal.codewindow.BytecodeToolwindow$Factory"
@@ -7,9 +7,11 @@ import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;
public class JetFileFactory extends FileTypeFactory {
@Override
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
// TODO: Remove unused extensions
consumer.consume(JetFileType.INSTANCE, "jet;jetl;jets;kt;kts;ktm");
}
}
@@ -16,7 +16,6 @@ import org.jetbrains.jet.lang.diagnostics.UnresolvedReferenceDiagnostic;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.calls.ResolvedCallImpl;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
import org.jetbrains.jet.lang.types.ErrorUtils;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetHighlighter;
@@ -29,6 +28,8 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.*;
import static org.jetbrains.jet.lexer.JetTokens.*;
/**
* Quick showing possible problems with jet internals in IDEA with a tooltips
*
* @author abreslav
*/
public class DebugInfoAnnotator implements Annotator {
@@ -0,0 +1,48 @@
package org.jetbrains.jet.plugin.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
/**
* A keyword contributor for Kotlin
* TODO: add different context for different keywords
*
* @author Nikolay Krasko
*/
public class JetKeywordCompletionContributor extends CompletionContributor {
private static class JetKeywordCompletionProvider extends CompletionProvider<CompletionParameters> {
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"
};
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
for (String keyword : COMPLETE_KEYWORD) {
result.addElement(LookupElementBuilder.create(keyword).setBold());
}
for (String softKeyword : COMPLETE_SOFT_KEYWORDS) {
result.addElement(LookupElementBuilder.create(softKeyword));
}
}
}
public JetKeywordCompletionContributor() {
extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new JetKeywordCompletionProvider());
}
}
@@ -0,0 +1,5 @@
fun foo() {
<caret>
}
// EXIST: val, var
@@ -0,0 +1,6 @@
fun foo() {
val test : <caret>
}
// TODO: Move all keywords to absent
// EXPECT: fun, val, var, namespace
@@ -0,0 +1,8 @@
<caret>
// EXPECT: namespace, as, type, class, this, super, val, var, fun, for, null, true
// EXPECT: false, is, in, throw, return, break, continue, object, if, try, else, while
// EXPECT: do, when, trait, This
// EXPECT: import, where, by, get, set, abstract, enum, open, annotation, override, private
// EXPECT: public, internal, protected, catch, out, vararg, inline, finally, final, ref
// ABSENT: ?in, new, extends, implements
@@ -0,0 +1,115 @@
package org.jetbrains.jet.completion;
import com.intellij.codeInsight.completion.CodeCompletionHandlerBase;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupEx;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.testFramework.LightCodeInsightTestCase;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* @author Nikolay.Krasko
*/
public abstract class JetCompletionTestBase extends LightCodeInsightTestCase {
protected void doTest() {
final String testName = getTestName(false);
configureByFile(testName + ".kt");
CompletionType completionType = (testName.startsWith("Smart")) ? CompletionType.SMART : CompletionType.BASIC;
new CodeCompletionHandlerBase(completionType, false, false, true).invokeCompletion(getProject(), getEditor());
LookupEx lookup = LookupManager.getActiveLookup(getEditor());
assert lookup != null;
HashSet<String> items = new HashSet<String>(resolveLookups(lookup.getItems()));
List<String> shouldExist = itemsShouldExist(getFile().getText());
for (String shouldExistItem : shouldExist) {
assertTrue(String.format("Should contain proposal '%s'.", shouldExistItem),
items.contains(shouldExistItem));
}
List<String> shouldAbsent = itemsShouldAbsent(getFile().getText());
for (String shouldAbsentItem : shouldAbsent) {
assertTrue(String.format("Shouldn't contain proposal '%s'.", shouldAbsentItem),
!items.contains(shouldAbsentItem));
}
}
private static List<String> resolveLookups(List<LookupElement> items) {
ArrayList<String> result = new ArrayList<String>(items.size());
for (LookupElement item : items) {
result.add(item.getLookupString());
}
return result;
}
@NotNull
private static List<String> itemsShouldExist(String fileText) {
return findListWithPrefix("// EXIST:", fileText);
}
@NotNull
private static List<String> itemsShouldAbsent(String fileText) {
return findListWithPrefix("// ABSENT:", fileText);
}
@NotNull
private static List<String> findListWithPrefix(String prefix, String fileText) {
ArrayList<String> result = new ArrayList<String>();
for (String line : findLinesWithPrefixRemoved(prefix, fileText)) {
String[] completions = line.split(",");
for (String completion : completions) {
result.add(completion.trim());
}
}
return result;
}
@NotNull
private static List<String> findLinesWithPrefixRemoved(String prefix, String fileText) {
ArrayList<String> result = new ArrayList<String>();
for (String line : fileNonEmptyLines(fileText)) {
if (line.startsWith(prefix)) {
result.add(line.substring(prefix.length()).trim());
}
}
return result;
}
@NotNull
private static List<String> fileNonEmptyLines(String fileText) {
ArrayList<String> result = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new StringReader(fileText));
try {
String line;
while ((line = reader.readLine()) != null) {
if (!line.isEmpty()) {
result.add(line.trim());
}
}
} catch(IOException e) {
assert false;
}
return result;
}
}
@@ -0,0 +1,61 @@
package org.jetbrains.jet.completion;
import junit.framework.TestSuite;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.JetTestCaseBuilder;
import org.jetbrains.jet.plugin.PluginTestCaseBase;
import java.io.File;
/**
* Test auto completion messages
*
* @author Nikolay.Krasko
*/
public class KeywordsCompletionTest extends JetCompletionTestBase {
private final String myPath;
private final String myName;
public KeywordsCompletionTest(@NotNull String path, @NotNull String name) {
myPath = path;
myName = name;
// Set name explicitly because otherwise there will be "TestCase.fName cannot be null"
setName("testComletionExecute");
}
public void testComletionExecute() {
doTest();
}
@Override
protected String getTestDataPath() {
return new File(PluginTestCaseBase.getTestDataPathBase(), myPath).getPath() +
File.separator;
}
@NotNull
@Override
public String getName() {
return "test" + myName;
}
@NotNull
public static TestSuite suite() {
TestSuite suite = new TestSuite();
JetTestCaseBuilder.appendTestsInDirectory(
PluginTestCaseBase.getTestDataPathBase(), "/completion/keywords/", false,
JetTestCaseBuilder.emptyFilter, new JetTestCaseBuilder.NamedTestFactory() {
@NotNull
@Override
public junit.framework.Test createTest(@NotNull String dataPath, @NotNull String name) {
return new KeywordsCompletionTest(dataPath, name);
}
}, suite);
return suite;
}
}