KT-688: Show kotlin keywords as auto completion in IDEA
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user