From 35ffb10dcf29fb62772a6bec0fcf356559698ef2 Mon Sep 17 00:00:00 2001 From: Kostiantyn Shchepanovskyi Date: Sat, 1 Oct 2016 14:36:02 +0300 Subject: [PATCH] Navigating to message, enum or service by name (Ctrl+N) (#26) --- README.md | 3 +- build.gradle | 6 +- gradle.properties | 2 +- .../plugin/GoToClassContributor.java | 130 ++++++++++++ .../plugin/ProtoFindUsagesProvider.java | 16 +- .../psi/{UserType.java => DataType.java} | 10 +- ...eContainer.java => DataTypeContainer.java} | 5 +- .../jetbrains/plugin/psi/EnumNode.java | 9 +- .../plugin/psi/FileReferenceNode.java | 2 +- .../jetbrains/plugin/psi/MessageNode.java | 12 +- .../plugin/psi/ProtoPsiFileRoot.java | 49 ++++- .../jetbrains/plugin/psi/ProtoRootNode.java | 30 +-- .../jetbrains/plugin/psi/ProtoType.java | 25 +++ .../jetbrains/plugin/psi/ServiceNode.java | 31 ++- .../AbstractPresentationProvider.java | 44 ++++ .../EnumPresentationProvider.java | 24 +++ .../MessagePresentationProvider.java | 23 +++ .../psi/presentation/PresentationUtil.java | 43 ++++ .../presentation}/ProtoItemPresentation.java | 12 +- .../ServicePresentationProvider.java | 24 +++ .../plugin/reference/TypeReference.java | 12 +- .../file/FilePathReferenceProvider.java | 24 +-- .../plugin/settings/ProtobufSettings.java | 16 ++ .../structure/EnumConstantTreeElement.java | 1 + .../view/structure/EnumTreeElement.java | 1 + .../structure/MessageFieldTreeElement.java | 1 + .../view/structure/MessageTreeElement.java | 1 + .../view/structure/RootTreeElement.java | 1 + .../structure/ServiceMethodTreeElement.java | 1 + .../view/structure/ServiceTreeElement.java | 1 + src/main/resources/META-INF/plugin.xml | 27 ++- .../messages/ProtostuffBundle.properties | 1 + .../plugin/navigation/GoToClassTest.java | 195 ++++++++++++++++++ .../plugin/reference/ReferenceTest.java | 8 +- src/test/resources/parsing/Enum.txt | 2 +- .../parsing/HeaderAndFooterComments.txt | 2 +- src/test/resources/parsing/Message.txt | 2 +- src/test/resources/parsing/Service.txt | 2 +- 38 files changed, 709 insertions(+), 89 deletions(-) create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/GoToClassContributor.java rename src/main/java/io/protostuff/jetbrains/plugin/psi/{UserType.java => DataType.java} (87%) rename src/main/java/io/protostuff/jetbrains/plugin/psi/{UserTypeContainer.java => DataTypeContainer.java} (77%) create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoType.java create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/AbstractPresentationProvider.java create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/EnumPresentationProvider.java create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/MessagePresentationProvider.java create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/PresentationUtil.java rename src/main/java/io/protostuff/jetbrains/plugin/{view/structure => psi/presentation}/ProtoItemPresentation.java (68%) create mode 100644 src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ServicePresentationProvider.java create mode 100644 src/test/java/io/protostuff/jetbrains/plugin/navigation/GoToClassTest.java diff --git a/README.md b/README.md index 4ef47e2..a1df6f0 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ [Protobuf Support Plugin](https://plugins.jetbrains.com/plugin/8277) for IntelliJ IDEA & other JetBrains products. -Based on [antlr4-jetbrains-adapter](https://github.com/antlr/jetbrains/) and ANTLR 4 grammar from [protostuff-compiler](https://github.com/protostuff/protostuff-compiler/tree/master/protostuff-parser/src/main/antlr4/io/protostuff/compiler/parser). - Plugin is compatible with IntelliJ IDEA 2016.1. Other JetBrains IDEs of the same or higher version should be supported as well. ### Roadmap @@ -36,3 +34,4 @@ Requirements: ### Links https://github.com/protostuff/protobuf-jetbrains-plugin/wiki/Links + diff --git a/build.gradle b/build.gradle index 1fa2d94..e6eda8b 100644 --- a/build.gradle +++ b/build.gradle @@ -41,11 +41,7 @@ apply plugin: 'org.jetbrains.intellij' intellij { version = ideaVersion updateSinceUntilBuild = false - // TODO: we do not need dependency on this plugin in runtime - // TODO: should be removed, but then tests are failing with - // TODO: ERROR: java.lang.ClassNotFoundException: com.intellij.lang.properties.PropertiesFileTypeFactory -// plugins 'properties' - downloadSources = false + downloadSources = true publish { username = project.hasProperty('jetbrainsUser') \ ? project.property('jetbrainsUser') \ diff --git a/gradle.properties b/gradle.properties index 85a753d..c69cc0a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Available idea versions: # https://www.jetbrains.com/intellij-repository/releases # https://www.jetbrains.com/intellij-repository/snapshots -version=0.5.0 +version=0.7.0 ideaVersion=145.258.11 # https://intellij-support.jetbrains.com/hc/en-us/articles/206544879-Selecting-the-JDK-version-the-IDE-will-run-under # Java 8 is required to run IntelliJ IDEA starting from version 16 diff --git a/src/main/java/io/protostuff/jetbrains/plugin/GoToClassContributor.java b/src/main/java/io/protostuff/jetbrains/plugin/GoToClassContributor.java new file mode 100644 index 0000000..1d9ef48 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/GoToClassContributor.java @@ -0,0 +1,130 @@ +package io.protostuff.jetbrains.plugin; + +import com.intellij.navigation.GotoClassContributor; +import com.intellij.navigation.NavigationItem; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.search.FileTypeIndex; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.indexing.FileBasedIndex; +import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot; +import io.protostuff.jetbrains.plugin.psi.ProtoType; +import io.protostuff.jetbrains.plugin.settings.ProtobufSettings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class GoToClassContributor implements GotoClassContributor { + + public static String getQualifiedNameUserType(ProtoType protoType) { + return protoType.getFullName(); + } + + @Nullable + @Override + public String getQualifiedName(final NavigationItem item) { + if (item instanceof ProtoType) { + return getQualifiedNameUserType((ProtoType) item); + } + return null; + } + + @Nullable + @Override + public String getQualifiedNameSeparator() { + return "."; + } + + @NotNull + @Override + public String[] getNames(Project project, boolean includeNonProjectItems) { + List types = findUserTypes(project, includeNonProjectItems); + List names = new ArrayList<>(types.size()); + for (ProtoType type : types) { + if (type.getName().length() > 0) { + names.add(type.getName()); + } + } + return names.toArray(new String[names.size()]); + } + + @NotNull + @Override + public NavigationItem[] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) { + List types = findUserTypes(project, includeNonProjectItems, name); + return types.toArray(new NavigationItem[types.size()]); + } + + private List findUserTypes(Project project, boolean includeNonProjectItems) { + return getProtoTypes(project, includeNonProjectItems, + protoType -> true); + } + + private List findUserTypes(Project project, boolean includeNonProjectItems, String key) { + return getProtoTypes(project, includeNonProjectItems, + protoType -> key.equals(protoType.getName())); + } + + @NotNull + private List getProtoTypes(Project project, boolean includeNonProjectItems, Predicate filter) { + List result = new ArrayList<>(); + List files = new ArrayList<>(); + addProjectAndLibraryFiles(project, includeNonProjectItems, files); + if (includeNonProjectItems) { + addFilesFromCustomIncludePath(project, files); + } + for (VirtualFile virtualFile : files) { + ProtoPsiFileRoot file = (ProtoPsiFileRoot) PsiManager.getInstance(project).findFile(virtualFile); + if (file != null) { + Collection types = file.getAllTypes(); + for (ProtoType type : types) { + if (filter.test(type)) { + result.add(type); + } + } + } + } + return result; + } + + private void addProjectAndLibraryFiles(Project project, boolean includeNonProjectItems, List files) { + FileBasedIndex index = FileBasedIndex.getInstance(); + GlobalSearchScope scope = getSearchScope(project, includeNonProjectItems); + files.addAll(index.getContainingFiles(FileTypeIndex.NAME, ProtoFileType.INSTANCE, scope)); + } + + private void addFilesFromCustomIncludePath(Project project, List files) { + ProtobufSettings settings = project.getComponent(ProtobufSettings.class); + List includePaths = settings.getIncludePathsVf(); + for (VirtualFile includePath : includePaths) { + FileBasedIndex.iterateRecursively(includePath, file -> { + if (!file.isDirectory() && isProtoFile(file)) { + files.add(file); + } + return true; + }, null, null, null); + } + } + + private boolean isProtoFile(VirtualFile file) { + return file.getName().toLowerCase().endsWith(ProtoFileType.FILE_EXTENSION); + } + + @NotNull + private GlobalSearchScope getSearchScope(Project project, boolean includeNonProjectItems) { + if (includeNonProjectItems) { + return GlobalSearchScope.allScope(project); + } else { + return GlobalSearchScope.projectScope(project); + } + } + +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/ProtoFindUsagesProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/ProtoFindUsagesProvider.java index 4d2fdb2..4c45b6c 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/ProtoFindUsagesProvider.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/ProtoFindUsagesProvider.java @@ -4,13 +4,9 @@ import com.intellij.lang.cacheBuilder.WordsScanner; import com.intellij.lang.findUsages.FindUsagesProvider; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiNamedElement; -import com.intellij.psi.tree.TokenSet; -import io.protostuff.compiler.parser.ProtoLexer; import io.protostuff.jetbrains.plugin.psi.EnumNode; import io.protostuff.jetbrains.plugin.psi.MessageNode; -import io.protostuff.jetbrains.plugin.psi.UserType; -import org.antlr.jetbrains.adapter.lexer.PSIElementTypeFactory; +import io.protostuff.jetbrains.plugin.psi.DataType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,7 +27,7 @@ public WordsScanner getWordsScanner() { @Override public boolean canFindUsagesFor(@NotNull PsiElement psiElement) { - return psiElement instanceof UserType; + return psiElement instanceof DataType; } @Nullable @@ -55,8 +51,8 @@ public String getType(@NotNull PsiElement element) { @NotNull @Override public String getDescriptiveName(@NotNull PsiElement element) { - if (element instanceof UserType) { - UserType type = (UserType) element; + if (element instanceof DataType) { + DataType type = (DataType) element; return type.getFullName(); } return ""; @@ -65,8 +61,8 @@ public String getDescriptiveName(@NotNull PsiElement element) { @NotNull @Override public String getNodeText(@NotNull PsiElement element, boolean useFullName) { - if (element instanceof UserType) { - UserType type = (UserType) element; + if (element instanceof DataType) { + DataType type = (DataType) element; if (useFullName) { return type.getFullName(); } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/UserType.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/DataType.java similarity index 87% rename from src/main/java/io/protostuff/jetbrains/plugin/psi/UserType.java rename to src/main/java/io/protostuff/jetbrains/plugin/psi/DataType.java index 590f0da..64414b3 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/UserType.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/DataType.java @@ -11,19 +11,22 @@ import org.jetbrains.annotations.Nullable; /** + * User-defined proto type that can be used as a field - message or enum. + * * @author Kostiantyn Shchepanovskyi */ -public class UserType +public class DataType extends IdentifierDefSubtree - implements ScopeNode, KeywordsContainer { + implements ScopeNode, KeywordsContainer, ProtoType { - public UserType(@NotNull ASTNode node, IElementType idElementType) { + public DataType(@NotNull ASTNode node, IElementType idElementType) { super(node, idElementType); } /** * Returns fully qualified name of this message (starting with dot). */ + @NotNull public String getQualifiedName() { PsiElement parent = getParent(); if (parent instanceof ProtoRootNode) { @@ -46,6 +49,7 @@ public String getQualifiedName() { /** * Returns full name of this type without leading dot. */ + @NotNull public String getFullName() { return getQualifiedName().substring(1); } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/UserTypeContainer.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/DataTypeContainer.java similarity index 77% rename from src/main/java/io/protostuff/jetbrains/plugin/psi/UserTypeContainer.java rename to src/main/java/io/protostuff/jetbrains/plugin/psi/DataTypeContainer.java index 815801a..ecc6d59 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/UserTypeContainer.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/DataTypeContainer.java @@ -5,7 +5,7 @@ /** * @author Kostiantyn Shchepanovskyi */ -public interface UserTypeContainer { +public interface DataTypeContainer { /** * Returns string prefix that is common for all children full names. @@ -13,5 +13,6 @@ public interface UserTypeContainer { */ String getNamespace(); - Collection getChildrenTypes(); + Collection getDeclaredDataTypes(); + } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/EnumNode.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/EnumNode.java index 7add04d..7e2444a 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/EnumNode.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/EnumNode.java @@ -2,11 +2,9 @@ import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; -import com.intellij.psi.PsiElement; +import com.intellij.navigation.ItemPresentationProviders; import io.protostuff.compiler.parser.ProtoParser; -import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.ProtoParserDefinition; -import io.protostuff.jetbrains.plugin.view.structure.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; import java.util.Arrays; @@ -16,7 +14,7 @@ * @author Kostiantyn Shchepanovskyi */ public class EnumNode - extends UserType { + extends DataType { public static final String ALLOW_ALIAS = "allow_alias"; public static final String TRUE = "true"; @@ -27,8 +25,7 @@ public EnumNode(@NotNull ASTNode node) { @Override public ItemPresentation getPresentation() { - String fullName = getFullName(); - return new ProtoItemPresentation(fullName, Icons.ENUM); + return ItemPresentationProviders.getItemPresentation(this); } public List getConstants() { diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/FileReferenceNode.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/FileReferenceNode.java index 515d824..d4a3704 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/FileReferenceNode.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/FileReferenceNode.java @@ -73,7 +73,7 @@ public ProtoPsiFileRoot getTarget() { } public ProtoPsiFileRoot getTarget(@NotNull String filename, @NotNull Module module) { - Collection roots = FilePathReferenceProvider.getRoots(module, true); + Collection roots = FilePathReferenceProvider.getRoots(module); for (PsiFileSystemItem root : roots) { VirtualFile file = root.getVirtualFile().findFileByRelativePath(getFilename()); if (file != null) { diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/MessageNode.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/MessageNode.java index 9c3aee8..05f1b52 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/MessageNode.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/MessageNode.java @@ -2,10 +2,9 @@ import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; +import com.intellij.navigation.ItemPresentationProviders; import io.protostuff.compiler.parser.ProtoParser; -import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.ProtoParserDefinition; -import io.protostuff.jetbrains.plugin.view.structure.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -16,7 +15,7 @@ /** * @author Kostiantyn Shchepanovskyi */ -public class MessageNode extends UserType implements AntlrParserRuleNode, UserTypeContainer { +public class MessageNode extends DataType implements AntlrParserRuleNode, DataTypeContainer { public static final int RULE_INDEX = ProtoParser.RULE_messageBlock; @@ -43,8 +42,8 @@ public String getNamespace() { } @Override - public Collection getChildrenTypes() { - return Arrays.asList(findChildrenByClass(UserType.class)); + public Collection getDeclaredDataTypes() { + return Arrays.asList(findChildrenByClass(DataType.class)); } public Collection getFields() { @@ -62,8 +61,7 @@ public Collection getFields() { @Override public ItemPresentation getPresentation() { - String fullName = getFullName(); - return new ProtoItemPresentation(fullName, Icons.MESSAGE); + return ItemPresentationProviders.getItemPresentation(this); } @NotNull diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java index caac3ec..a3c1200 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.*; /** * @author Kostiantyn Shchepanovskyi @@ -32,7 +33,7 @@ public FileType getFileType() { @Override public String toString() { final VirtualFile virtualFile = getVirtualFile(); - return "ProtobufFile: " + (virtualFile != null ? virtualFile.getName() : ""); + return "Protobuf File: " + (virtualFile != null ? virtualFile.getName() : ""); } @Override @@ -54,4 +55,50 @@ public ScopeNode getContext() { public PsiElement resolve(PsiNamedElement element) { return null; } + + /** + * Returns all messages, enums and services defined in this proto file. + */ + public Collection getAllTypes() { + ProtoRootNode root = findChildByClass(ProtoRootNode.class); + if (root != null) { + List result = new ArrayList<>(); + Collection declaredTypes = root.getDeclaredTypes(); + result.addAll(declaredTypes); + addChildrenRecursively(declaredTypes, result); + return result; + } + return Collections.emptyList(); + } + + private void addChildrenRecursively(Collection elements, List result) { + for (ProtoType element : elements) { + if (element instanceof DataTypeContainer) { + DataTypeContainer container = (DataTypeContainer) element; + Collection declaredDataTypes = container.getDeclaredDataTypes(); + result.addAll(declaredDataTypes); + addChildrenRecursively(declaredDataTypes, result); + } + } + } + + @NotNull + public String getPackageName() { + ProtoRootNode root = findChildByClass(ProtoRootNode.class); + if (root != null) { + return root.getPackageName(); + } + return ""; + } + + @Nullable + public ProtoType findType(String fullName) { + Collection all = getAllTypes(); + for (ProtoType type : all) { + if (Objects.equals(fullName, type.getFullName())) { + return type; + } + } + return null; + } } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootNode.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootNode.java index 21ed8b8..6a5fb5b 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootNode.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootNode.java @@ -13,7 +13,7 @@ /** * @author Kostiantyn Shchepanovskyi */ -public class ProtoRootNode extends ANTLRPsiNode implements KeywordsContainer, UserTypeContainer { +public class ProtoRootNode extends ANTLRPsiNode implements KeywordsContainer, DataTypeContainer { public ProtoRootNode(@NotNull ASTNode node) { super(node); @@ -33,19 +33,19 @@ public String getPackageName() { } - public UserType resolve(String typeName, Deque scopeLookupList) { + public DataType resolve(String typeName, Deque scopeLookupList) { return resolve(typeName, scopeLookupList, true); } - public UserType resolve(String typeName, Deque scopeLookupList, boolean resolveInImports) { - UserType result = null; + public DataType resolve(String typeName, Deque scopeLookupList, boolean resolveInImports) { + DataType result = null; // A leading '.' (for example, .foo.bar.Baz) means to start from the outermost scope if (typeName.startsWith(".")) { result = resolveByQualifiedName(typeName); } else { for (String scope : scopeLookupList) { String fullTypeName = scope + typeName; - UserType type = resolveByQualifiedName(fullTypeName); + DataType type = resolveByQualifiedName(fullTypeName); if (type != null) { result = type; break; @@ -73,7 +73,7 @@ public UserType resolve(String typeName, Deque scopeLookupList, boolean return null; } - public UserType resolveByQualifiedName(String qualifiedName) { + public DataType resolveByQualifiedName(String qualifiedName) { String prefix = getCurrentProtoPrefix(); if (qualifiedName.startsWith(prefix)) { return resolveRecursive(this, qualifiedName); @@ -90,15 +90,15 @@ private String getCurrentProtoPrefix() { return "." + getPackageName() + "."; } - public UserType resolveRecursive(UserTypeContainer container, String targetName) { - Collection childrenTypes = container.getChildrenTypes(); - for (UserType type : childrenTypes) { + public DataType resolveRecursive(DataTypeContainer container, String targetName) { + Collection childrenTypes = container.getDeclaredDataTypes(); + for (DataType type : childrenTypes) { String qualifiedName = type.getQualifiedName(); if (Objects.equals(targetName, qualifiedName)) { return type; } - if (type instanceof UserTypeContainer && targetName.startsWith(qualifiedName + ".")) { - return resolveRecursive((UserTypeContainer) type, targetName); + if (type instanceof DataTypeContainer && targetName.startsWith(qualifiedName + ".")) { + return resolveRecursive((DataTypeContainer) type, targetName); } } return null; @@ -119,7 +119,11 @@ public String getNamespace() { } @Override - public Collection getChildrenTypes() { - return Arrays.asList(findChildrenByClass(UserType.class)); + public Collection getDeclaredDataTypes() { + return Arrays.asList(findChildrenByClass(DataType.class)); + } + + public Collection getDeclaredTypes() { + return Arrays.asList(findChildrenByClass(ProtoType.class)); } } \ No newline at end of file diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoType.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoType.java new file mode 100644 index 0000000..ff3c703 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoType.java @@ -0,0 +1,25 @@ +package io.protostuff.jetbrains.plugin.psi; + +import com.intellij.navigation.NavigationItem; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +/** + * User-defined proto type: message, enum or service + * + * @author Kostiantyn Shchepanovskyi + */ +public interface ProtoType extends PsiElement, NavigationItem { + + /** + * Returns short name of the message/enum/service. + */ + @NotNull + String getName(); + + /** + * Returns full class name: [package.] + name (without leading dot). + */ + @NotNull + String getFullName(); +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/ServiceNode.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/ServiceNode.java index 944a570..129d177 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/ServiceNode.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/ServiceNode.java @@ -1,6 +1,9 @@ package io.protostuff.jetbrains.plugin.psi; +import com.google.common.base.MoreObjects; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; +import com.intellij.navigation.ItemPresentationProviders; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import io.protostuff.compiler.parser.ProtoParser; @@ -18,7 +21,7 @@ */ public class ServiceNode extends IdentifierDefSubtree - implements ScopeNode, KeywordsContainer { + implements ScopeNode, KeywordsContainer, ProtoType { public ServiceNode(@NotNull ASTNode node) { super(node, ProtoParserDefinition.rule(ProtoParser.RULE_serviceName)); @@ -35,4 +38,30 @@ public List getRpcMethods() { return Arrays.asList(nodes); } + @Override + public ItemPresentation getPresentation() { + return ItemPresentationProviders.getItemPresentation(this); + } + + @NotNull + @Override + public String getName() { + String name = super.getName(); + return MoreObjects.firstNonNull(name, ""); + } + + @NotNull + @Override + public String getFullName() { + PsiElement parent = getParent(); + if (parent instanceof ProtoRootNode) { + ProtoRootNode proto = (ProtoRootNode) parent; + String packageName = proto.getPackageName(); + if (packageName.isEmpty()) { + return getName(); + } + return packageName + "." + getName(); + } + return ""; + } } \ No newline at end of file diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/AbstractPresentationProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/AbstractPresentationProvider.java new file mode 100644 index 0000000..192a231 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/AbstractPresentationProvider.java @@ -0,0 +1,44 @@ +package io.protostuff.jetbrains.plugin.psi.presentation; + +import com.intellij.navigation.ItemPresentation; +import com.intellij.navigation.ItemPresentationProvider; +import com.intellij.navigation.NavigationItem; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import io.protostuff.jetbrains.plugin.Icons; +import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public abstract class AbstractPresentationProvider + implements ItemPresentationProvider { + + @Override + public ItemPresentation getPresentation(@NotNull T item) { + String name = getName(item); + String location = getLocationString(item); + Icon icon = getIcon(); + return new ProtoItemPresentation(name, location, icon); + } + + protected abstract Icon getIcon(); + + protected abstract String getName(@NotNull T item); + + @Nullable + private String getLocationString(@NotNull T item) { + PsiFile file = item.getContainingFile(); + if (file instanceof ProtoPsiFileRoot) { + ProtoPsiFileRoot classOwner = (ProtoPsiFileRoot)file; + String packageName = classOwner.getPackageName(); + if (packageName.isEmpty()) return null; + return "(" + packageName + ")"; + } + return null; + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/EnumPresentationProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/EnumPresentationProvider.java new file mode 100644 index 0000000..ba8c4c9 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/EnumPresentationProvider.java @@ -0,0 +1,24 @@ +package io.protostuff.jetbrains.plugin.psi.presentation; + +import io.protostuff.jetbrains.plugin.Icons; +import io.protostuff.jetbrains.plugin.psi.EnumNode; +import io.protostuff.jetbrains.plugin.psi.MessageNode; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class EnumPresentationProvider extends AbstractPresentationProvider { + + @Override + protected Icon getIcon() { + return Icons.ENUM; + } + + @Override + protected String getName(@NotNull EnumNode item) { + return PresentationUtil.getNameForElement(item); + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/MessagePresentationProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/MessagePresentationProvider.java new file mode 100644 index 0000000..0dac1eb --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/MessagePresentationProvider.java @@ -0,0 +1,23 @@ +package io.protostuff.jetbrains.plugin.psi.presentation; + +import io.protostuff.jetbrains.plugin.Icons; +import io.protostuff.jetbrains.plugin.psi.MessageNode; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class MessagePresentationProvider extends AbstractPresentationProvider { + + @Override + protected Icon getIcon() { + return Icons.MESSAGE; + } + + @Override + protected String getName(@NotNull MessageNode item) { + return PresentationUtil.getNameForElement(item); + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/PresentationUtil.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/PresentationUtil.java new file mode 100644 index 0000000..fa451d1 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/PresentationUtil.java @@ -0,0 +1,43 @@ +package io.protostuff.jetbrains.plugin.psi.presentation; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.util.PsiTreeUtil; +import io.protostuff.jetbrains.plugin.ProtostuffBundle; +import io.protostuff.jetbrains.plugin.psi.MessageNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class PresentationUtil { + + @Nullable + public static String getNameForElement(PsiElement element) { + if (element instanceof PsiNamedElement) { + PsiNamedElement message = (PsiNamedElement) element; + String name = message.getName(); + String context = getContextName(message); + if (context != null) { + return ProtostuffBundle.message("element.context.display", name, context); + } else { + return name; + } + } + return null; + } + + private static String getContextName(@NotNull PsiElement element) { + PsiElement parent = PsiTreeUtil.getParentOfType(element, MessageNode.class); + if (parent == null) parent = element.getContainingFile(); + while (true) { + if (parent == null) return null; + if (parent instanceof PsiFile) return null; + String name = getNameForElement(parent); + if (name != null) return name; + parent = parent.getParent(); + } + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ProtoItemPresentation.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ProtoItemPresentation.java similarity index 68% rename from src/main/java/io/protostuff/jetbrains/plugin/view/structure/ProtoItemPresentation.java rename to src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ProtoItemPresentation.java index 1f7cdf6..1ff3592 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ProtoItemPresentation.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ProtoItemPresentation.java @@ -1,4 +1,4 @@ -package io.protostuff.jetbrains.plugin.view.structure; +package io.protostuff.jetbrains.plugin.psi.presentation; import com.intellij.navigation.ItemPresentation; import org.jetbrains.annotations.Nullable; @@ -11,11 +11,19 @@ public final class ProtoItemPresentation implements ItemPresentation { private final String name; + private final String location; private final Icon icon; public ProtoItemPresentation(String name, Icon icon) { this.name = name; this.icon = icon; + this.location = null; + } + + public ProtoItemPresentation(String name, String location, Icon icon) { + this.name = name; + this.location = location; + this.icon = icon; } @Nullable @@ -27,7 +35,7 @@ public String getPresentableText() { @Nullable @Override public String getLocationString() { - return null; + return location; } @Nullable diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ServicePresentationProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ServicePresentationProvider.java new file mode 100644 index 0000000..952e79a --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/presentation/ServicePresentationProvider.java @@ -0,0 +1,24 @@ +package io.protostuff.jetbrains.plugin.psi.presentation; + +import io.protostuff.jetbrains.plugin.Icons; +import io.protostuff.jetbrains.plugin.psi.MessageNode; +import io.protostuff.jetbrains.plugin.psi.ServiceNode; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class ServicePresentationProvider extends AbstractPresentationProvider { + + @Override + protected Icon getIcon() { + return Icons.SERVICE; + } + + @Override + protected String getName(@NotNull ServiceNode item) { + return PresentationUtil.getNameForElement(item); + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/reference/TypeReference.java b/src/main/java/io/protostuff/jetbrains/plugin/reference/TypeReference.java index 3dd1392..bfc393e 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/reference/TypeReference.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/reference/TypeReference.java @@ -4,8 +4,8 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReferenceBase; import io.protostuff.jetbrains.plugin.psi.ProtoRootNode; -import io.protostuff.jetbrains.plugin.psi.UserType; -import io.protostuff.jetbrains.plugin.psi.UserTypeContainer; +import io.protostuff.jetbrains.plugin.psi.DataType; +import io.protostuff.jetbrains.plugin.psi.DataTypeContainer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,9 +30,9 @@ public PsiElement resolve() { return resolveInScope(getElement(), this); } - public static UserType resolveInScope(PsiElement scopeElement, TypeReference ref) { + public static DataType resolveInScope(PsiElement scopeElement, TypeReference ref) { PsiElement element = scopeElement; - while (element != null && !(element instanceof UserTypeContainer)) { + while (element != null && !(element instanceof DataTypeContainer)) { element = element.getParent(); } PsiElement protoElement = element; @@ -42,7 +42,7 @@ public static UserType resolveInScope(PsiElement scopeElement, TypeReference ref if (element == null || protoElement == null) { return null; } - UserTypeContainer scope = (UserTypeContainer) element; + DataTypeContainer scope = (DataTypeContainer) element; ProtoRootNode proto = (ProtoRootNode) protoElement; Deque scopeLookupList = createScopeLookupList(scope); return proto.resolve(ref.key, scopeLookupList); @@ -51,7 +51,7 @@ public static UserType resolveInScope(PsiElement scopeElement, TypeReference ref // Type name resolution in the protocol buffer language works like C++: first // the innermost scope is searched, then the next-innermost, and so on, with // each package considered to be "inner" to its parent package. - public static Deque createScopeLookupList(UserTypeContainer container) { + public static Deque createScopeLookupList(DataTypeContainer container) { String namespace = container.getNamespace(); Deque scopeLookupList = new ArrayDeque<>(); int end = 0; diff --git a/src/main/java/io/protostuff/jetbrains/plugin/reference/file/FilePathReferenceProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/reference/file/FilePathReferenceProvider.java index de89cc6..1e34526 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/reference/file/FilePathReferenceProvider.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/reference/file/FilePathReferenceProvider.java @@ -93,12 +93,12 @@ public Collection computeDefaultContexts() { if (forModules.length > 0) { Set rootsForModules = ContainerUtil.newLinkedHashSet(); for (Module forModule : forModules) { - rootsForModules.addAll(getRoots(forModule, true)); + rootsForModules.addAll(getRoots(forModule)); } return rootsForModules; } - return getRoots(ModuleUtilCore.findModuleForPsiElement(getElement()), true); + return getRoots(ModuleUtilCore.findModuleForPsiElement(getElement())); } @Override @@ -149,25 +149,22 @@ public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNu } @NotNull - public static Collection getRoots(@Nullable final Module thisModule, boolean includingClasses) { + public static Collection getRoots(@Nullable final Module thisModule) { if (thisModule == null) return Collections.emptyList(); ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(thisModule); Set result = ContainerUtil.newLinkedHashSet(); final PsiManager psiManager = PsiManager.getInstance(thisModule.getProject()); - if (includingClasses) { - VirtualFile[] libraryUrls = moduleRootManager.orderEntries().getAllLibrariesAndSdkClassesRoots(); - for (VirtualFile file : libraryUrls) { - PsiDirectory directory = psiManager.findDirectory(file); - if (directory != null) { - result.add(directory); - } + + VirtualFile[] libraryUrls = moduleRootManager.orderEntries().getAllLibrariesAndSdkClassesRoots(); + for (VirtualFile file : libraryUrls) { + PsiDirectory directory = psiManager.findDirectory(file); + if (directory != null) { + result.add(directory); } } - VirtualFile[] sourceRoots = moduleRootManager.orderEntries().recursively() - .withoutSdk().withoutLibraries() - .sources().usingCache().getRoots(); + VirtualFile[] sourceRoots = moduleRootManager.orderEntries().getAllSourceRoots(); for (VirtualFile root : sourceRoots) { final PsiDirectory directory = psiManager.findDirectory(root); if (directory != null) { @@ -185,7 +182,6 @@ public static Collection getRoots(@Nullable final Module this result.add(psiDirectory); } } - return result; } } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/settings/ProtobufSettings.java b/src/main/java/io/protostuff/jetbrains/plugin/settings/ProtobufSettings.java index a98ab72..f0ba834 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/settings/ProtobufSettings.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/settings/ProtobufSettings.java @@ -5,6 +5,9 @@ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; import com.intellij.util.xmlb.XmlSerializerUtil; import org.jetbrains.annotations.NotNull; @@ -27,6 +30,19 @@ public List getIncludePaths() { return includePaths; } + @NotNull + public List getIncludePathsVf() { + List result = new ArrayList<>(); + List includePaths = getIncludePaths(); + for (String includePath : includePaths) { + VirtualFile path = LocalFileSystem.getInstance().findFileByPath(includePath); + if (path != null && path.isDirectory()) { + result.add(path); + } + } + return result; + } + public void setIncludePaths(@NotNull List includePaths) { this.includePaths = new ArrayList<>(includePaths); } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumConstantTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumConstantTreeElement.java index 3ac3d93..ee0db9a 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumConstantTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumConstantTreeElement.java @@ -4,6 +4,7 @@ import com.intellij.navigation.ItemPresentation; import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.EnumConstantNode; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; final class EnumConstantTreeElement extends AbstractTreeElement { diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumTreeElement.java index 128e162..5ee54d0 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/EnumTreeElement.java @@ -4,6 +4,7 @@ import com.intellij.navigation.ItemPresentation; import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.EnumNode; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; final class EnumTreeElement extends AbstractTreeElement { diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageFieldTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageFieldTreeElement.java index ca1344b..515141d 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageFieldTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageFieldTreeElement.java @@ -4,6 +4,7 @@ import com.intellij.navigation.ItemPresentation; import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.FieldNode; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; final class MessageFieldTreeElement extends AbstractTreeElement { diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageTreeElement.java index 086d303..fc6684d 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/MessageTreeElement.java @@ -6,6 +6,7 @@ import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.EnumNode; import io.protostuff.jetbrains.plugin.psi.MessageNode; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java index 139dd51..7e501ee 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java @@ -5,6 +5,7 @@ import com.intellij.psi.PsiElement; import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.*; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceMethodTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceMethodTreeElement.java index 9df9de7..b37face 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceMethodTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceMethodTreeElement.java @@ -4,6 +4,7 @@ import com.intellij.navigation.ItemPresentation; import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.RpcMethodNode; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; final class ServiceMethodTreeElement extends AbstractTreeElement { diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceTreeElement.java index 8f24a8f..43f5230 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/ServiceTreeElement.java @@ -4,6 +4,7 @@ import com.intellij.navigation.ItemPresentation; import io.protostuff.jetbrains.plugin.Icons; import io.protostuff.jetbrains.plugin.psi.ServiceNode; +import io.protostuff.jetbrains.plugin.psi.presentation.ProtoItemPresentation; import org.jetbrains.annotations.NotNull; final class ServiceTreeElement extends AbstractTreeElement { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 0e1fc2c..7f2df3c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ io.protostuff.protostuff-jetbrains-plugin Protobuf Support - 0.5.0 + 0.7.0 Kostiantyn Shchepanovskyi Brace matching.
  • Line and block commenting.
  • Code formatting.
  • +
  • Navigating to message, enum or service by name (Ctrl+N)

  • GitHub | @@ -32,13 +33,12 @@ - v0.5.0 - (2016-09-04) + + v0.7.0 + (2016-10-01)
      -
    • Add support for custom include path.
    • -
    • Add validation for message fields, enum constants and service rpc methods.
    • -
    • Add startup check for conflicting plugins.
    • +
    • Add navigation to message, enum or service by name (Ctrl+N).
    • +
    • Fix syntax error highlighting for missing tokens.

    @@ -117,6 +117,19 @@ language="PROTO" implementationClass="io.protostuff.jetbrains.plugin.annotator.ProtoErrorsAnnotator"/> + + + + + + diff --git a/src/main/resources/io/protostuff/protobuf-jetbrains-plugin/messages/ProtostuffBundle.properties b/src/main/resources/io/protostuff/protobuf-jetbrains-plugin/messages/ProtostuffBundle.properties index d3d7e88..3696430 100644 --- a/src/main/resources/io/protostuff/protobuf-jetbrains-plugin/messages/ProtostuffBundle.properties +++ b/src/main/resources/io/protostuff/protobuf-jetbrains-plugin/messages/ProtostuffBundle.properties @@ -18,3 +18,4 @@ error.reserved.field.name=Reserved field name: {0} error.duplicate.constant.name=Duplicate constant name: {0} error.duplicate.constant.value=Duplicate constant value: {0} error.duplicate.method.name=Duplicate RPC method name: {0} +element.context.display={0} in {1} diff --git a/src/test/java/io/protostuff/jetbrains/plugin/navigation/GoToClassTest.java b/src/test/java/io/protostuff/jetbrains/plugin/navigation/GoToClassTest.java new file mode 100644 index 0000000..92c96b4 --- /dev/null +++ b/src/test/java/io/protostuff/jetbrains/plugin/navigation/GoToClassTest.java @@ -0,0 +1,195 @@ +package io.protostuff.jetbrains.plugin.navigation; + +import com.google.common.io.Files; +import com.intellij.ide.util.gotoByName.ChooseByNameModel; +import com.intellij.ide.util.gotoByName.ChooseByNamePopup; +import com.intellij.ide.util.gotoByName.GotoClassModel2; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Disposer; +import com.intellij.psi.PsiElement; +import com.intellij.testFramework.EdtTestUtil; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot; +import io.protostuff.jetbrains.plugin.psi.ProtoType; +import io.protostuff.jetbrains.plugin.settings.ProtobufSettings; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; + +import javax.swing.*; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * "Go To Class" tests topic is not covered in plugin development documentation. + *

    + * This test is created from the test in intellij-community: + * https://github.com/JetBrains/intellij-community/blob/master/java/java-tests/testSrc/com/intellij/navigation/ChooseByNameTest.groovy + * + * @author Kostiantyn Shchepanovskyi + */ +public class GoToClassTest extends LightCodeInsightFixtureTestCase { + + private ChooseByNamePopup popup = null; + private File tempDir; + + @Override + protected void setUp() throws Exception { + super.setUp(); + tempDir = Files.createTempDir(); + tempDir.deleteOnExit(); + } + + @Override + protected void tearDown() throws Exception { + popup = null; + super.tearDown(); + } + + public void testGoToMessage() throws Exception { + ProtoPsiFileRoot file = addProto("test.proto", String.join("\n", + "package navigation;", + "message MyProtobufMessage {}")); + ProtoType message = findType(file, "navigation.MyProtobufMessage"); + List elements = getPopupElements(new GotoClassModel2(getProject()), "MyProto", false); + Assert.assertEquals(1, elements.size()); + Assert.assertSame(message, elements.get(0)); + } + + public void testGoToNestedMessage() throws Exception { + ProtoPsiFileRoot file = addProto("test.proto", String.join("\n", + "package navigation;", + "message MyProtobufMessage {", + "enum MyProtobufNestedMessage {}", + "}")); + ProtoType message = findType(file, "navigation.MyProtobufMessage.MyProtobufNestedMessage"); + List elements = getPopupElements(new GotoClassModel2(getProject()), "MyProtoNes", false); + Assert.assertEquals(1, elements.size()); + Assert.assertSame(message, elements.get(0)); + } + + public void testGoToEnum() throws Exception { + ProtoPsiFileRoot file = addProto("test.proto", String.join("\n", + "package navigation;", + "enum MyProtobufEnum {}")); + ProtoType anEnum = findType(file, "navigation.MyProtobufEnum"); + List elements = getPopupElements(new GotoClassModel2(getProject()), "MyProto", false); + Assert.assertEquals(1, elements.size()); + Assert.assertSame(anEnum, elements.get(0)); + } + + public void testGoToNestedEnum() throws Exception { + ProtoPsiFileRoot file = addProto("test.proto", String.join("\n", + "package navigation;", + "message MyProtobufMessage {", + "enum MyProtobufNestedEnum {}", + "}")); + ProtoType anEnum = findType(file, "navigation.MyProtobufMessage.MyProtobufNestedEnum"); + List elements = getPopupElements(new GotoClassModel2(getProject()), "MyProtoNes", false); + Assert.assertEquals(1, elements.size()); + Assert.assertSame(anEnum, elements.get(0)); + } + + public void testGoToService() throws Exception { + ProtoPsiFileRoot file = addProto("test.proto", String.join("\n", + "package navigation;", + "service MyProtobufService {}")); + ProtoType service = findType(file, "navigation.MyProtobufService"); + List elements = getPopupElements(new GotoClassModel2(getProject()), "MyProto", false); + Assert.assertEquals(1, elements.size()); + Assert.assertSame(service, elements.get(0)); + } + + public void testMultipleElements() throws Exception { + addProto("test.proto", String.join("\n", + "package navigation;", + "message MyProtobufMessage {}", + "enum MyProtobufEnum {}", + "service MyProtobufService {}" + )); + List elements = getPopupElements(new GotoClassModel2(getProject()), "MyProto", false); + Assert.assertEquals(3, elements.size()); + } + + public void testGoToMessage_inCustomIncludePath() throws Exception { + File includePathRoot = tempDir; + ProtobufSettings settings = getProject().getComponent(ProtobufSettings.class); + settings.setIncludePaths(Collections.singletonList(includePathRoot.getAbsolutePath())); + File proto = new File(tempDir.getPath() + "/test.proto"); + Writer writer = new BufferedWriter(new FileWriter(proto)); + writer.write("message MessageInCustomPath {}"); + writer.close(); + + List elements = getPopupElements(new GotoClassModel2(getProject()), "MeInCus", false); + Assert.assertEquals(1, elements.size()); + } + + private ProtoPsiFileRoot addProto(String name, String text) { + return (ProtoPsiFileRoot) myFixture.addFileToProject(name, text); + } + + private ProtoType findType(final ProtoPsiFileRoot file, final String fullName) { + return ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public ProtoType compute() { + return file.findType(fullName); + } + }); + } + + private List getPopupElements(ChooseByNameModel model, String text, boolean checkboxState) { + return calcPopupElements(createPopup(model, null), text, checkboxState); + } + + private List calcPopupElements(ChooseByNamePopup popup, String text, boolean checkboxState) { + List elements = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(1); + SwingUtilities.invokeLater(() -> + popup.scheduleCalcElements(text, checkboxState, ModalityState.NON_MODAL, + set -> { + elements.addAll(set); + latch.countDown(); + })); + try { + if (!latch.await(10, TimeUnit.SECONDS)) { + Assert.fail(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return elements; + } + + private ChooseByNamePopup createPopup(ChooseByNameModel model, PsiElement context) { + if (popup != null) { + popup.close(false); + } + EdtTestUtil.runInEdtAndWait(new Runnable() { + @Override + public void run() { + ChooseByNamePopup popup = ChooseByNamePopup.createPopup(getProject(), model, context, ""); + Disposer.register(getTestRootDisposable(), () -> popup.close(false)); + GoToClassTest.this.popup = popup; + } + }); + return popup; + } + + @Override + protected boolean runInDispatchThread() { + return false; + } + + @Override + protected void invokeTestRunnable(@NotNull Runnable runnable) throws Exception { + runnable.run(); + } +} diff --git a/src/test/java/io/protostuff/jetbrains/plugin/reference/ReferenceTest.java b/src/test/java/io/protostuff/jetbrains/plugin/reference/ReferenceTest.java index a9e9dfc..630d253 100644 --- a/src/test/java/io/protostuff/jetbrains/plugin/reference/ReferenceTest.java +++ b/src/test/java/io/protostuff/jetbrains/plugin/reference/ReferenceTest.java @@ -4,7 +4,7 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import io.protostuff.jetbrains.plugin.psi.ProtoPsiFileRoot; import io.protostuff.jetbrains.plugin.psi.TypeReferenceNode; -import io.protostuff.jetbrains.plugin.psi.UserType; +import io.protostuff.jetbrains.plugin.psi.DataType; import org.junit.Assert; /** @@ -43,8 +43,8 @@ private void check(String typeReference, String... file) { .getParent() // ident .getParent(); // typeReference PsiElement target = element.getReference().resolve(); - Assert.assertTrue(target instanceof UserType); - UserType userType = (UserType) target; - Assert.assertEquals(typeReference, userType.getQualifiedName()); + Assert.assertTrue(target instanceof DataType); + DataType dataType = (DataType) target; + Assert.assertEquals(typeReference, dataType.getQualifiedName()); } } diff --git a/src/test/resources/parsing/Enum.txt b/src/test/resources/parsing/Enum.txt index d185c8b..1342173 100644 --- a/src/test/resources/parsing/Enum.txt +++ b/src/test/resources/parsing/Enum.txt @@ -1,4 +1,4 @@ -ProtobufFile: Enum.proto(0,61) +Protobuf File: Enum.proto(0,61) ProtoRootNode(proto)(0,61) EnumNode(enumBlock)(0,61) PsiElement('enum')('enum')(0,4) diff --git a/src/test/resources/parsing/HeaderAndFooterComments.txt b/src/test/resources/parsing/HeaderAndFooterComments.txt index 7152262..e87db22 100644 --- a/src/test/resources/parsing/HeaderAndFooterComments.txt +++ b/src/test/resources/parsing/HeaderAndFooterComments.txt @@ -1,4 +1,4 @@ -ProtobufFile: HeaderAndFooterComments.proto(0,118) +Protobuf File: HeaderAndFooterComments.proto(0,118) PsiComment(LINE_COMMENT)('// Header comment does not belong to 'proto' rule')(0,49) PsiWhiteSpace('\n')(49,50) ProtoRootNode(proto)(50,68) diff --git a/src/test/resources/parsing/Message.txt b/src/test/resources/parsing/Message.txt index b44f0a0..de252d9 100644 --- a/src/test/resources/parsing/Message.txt +++ b/src/test/resources/parsing/Message.txt @@ -1,4 +1,4 @@ -ProtobufFile: Message.proto(0,110) +Protobuf File: Message.proto(0,110) ProtoRootNode(proto)(0,110) MessageNode(messageBlock)(0,110) PsiElement('message')('message')(0,7) diff --git a/src/test/resources/parsing/Service.txt b/src/test/resources/parsing/Service.txt index 5fb3a42..1daa323 100644 --- a/src/test/resources/parsing/Service.txt +++ b/src/test/resources/parsing/Service.txt @@ -1,4 +1,4 @@ -ProtobufFile: Service.proto(0,77) +Protobuf File: Service.proto(0,77) ProtoRootNode(proto)(0,77) ServiceNode(serviceBlock)(0,77) PsiElement('service')('service')(0,7)