6. PSI Helpers and Utilities
Helper classes and utilities can be embedded in the code generated by Grammar-Kit.
Define Helper Methods for Generated PSI Elements
Custom methods in PSI classes are defined separately, and Grammar-Kit embeds them into generated code. Define a utility class with these helper methods:
package org.intellij.sdk.language.psi.impl;
import com.intellij.lang.ASTNode;
import org.intellij.sdk.language.psi.SimpleProperty;
import org.intellij.sdk.language.psi.SimpleTypes;
public class SimplePsiImplUtil {
public static String getKey(SimpleProperty element) {
ASTNode keyNode = element.getNode().findChildByType(SimpleTypes.KEY);
if (keyNode != null) {
// IMPORTANT: Convert embedded escaped spaces to simple spaces
return keyNode.getText().replaceAll("\\\\ ", " ");
} else {
return null;
}
}
public static String getValue(SimpleProperty element) {
ASTNode valueNode = element.getNode().findChildByType(SimpleTypes.VALUE);
if (valueNode != null) {
return valueNode.getText();
} else {
return null;
}
}
}
The parser generates the SimpleProperty
interface referenced in the code above.
Update Grammar and Regenerate the Parser
Now the utility class is added to the grammar file via the psiImplUtilClass
global attribute. Add the methods
attribute for a particular rule to specify which one should be used for PSI classes.
Compare the property
rule below to the previous definition.
{
parserClass="org.intellij.sdk.language.parser.SimpleParser"
extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
psiClassPrefix="Simple"
psiImplClassSuffix="Impl"
psiPackage="org.intellij.sdk.language.psi"
psiImplPackage="org.intellij.sdk.language.psi.impl"
elementTypeHolderClass="org.intellij.sdk.language.psi.SimpleTypes"
elementTypeClass="org.intellij.sdk.language.psi.SimpleElementType"
tokenTypeClass="org.intellij.sdk.language.psi.SimpleTokenType"
psiImplUtilClass="org.intellij.sdk.language.psi.impl.SimplePsiImplUtil"
}
simpleFile ::= item_*
private item_ ::= (property|COMMENT|CRLF)
property ::= (KEY? SEPARATOR VALUE?) | KEY
{
methods=[getKey getValue]
}
After including the above changes in the grammar, regenerate the parser and PSI classes.
Define a Utility to Search Properties
Create SimpleUtil
utility class to search PSI elements for defined properties over the project. It will be used later when implementing code completion.
public class SimpleUtil {
/**
* Searches the entire project for Simple language files with instances of the Simple property with the given key.
*
* @param project current project
* @param key to check
* @return matching properties
*/
public static List<SimpleProperty> findProperties(Project project, String key) {
List<SimpleProperty> result = new ArrayList<>();
Collection<VirtualFile> virtualFiles =
FileTypeIndex.getFiles(SimpleFileType.INSTANCE, GlobalSearchScope.allScope(project));
for (VirtualFile virtualFile : virtualFiles) {
SimpleFile simpleFile = (SimpleFile) PsiManager.getInstance(project).findFile(virtualFile);
if (simpleFile != null) {
SimpleProperty[] properties = PsiTreeUtil.getChildrenOfType(simpleFile, SimpleProperty.class);
if (properties != null) {
for (SimpleProperty property : properties) {
if (key.equals(property.getKey())) {
result.add(property);
}
}
}
}
}
return result;
}
public static List<SimpleProperty> findProperties(Project project) {
List<SimpleProperty> result = new ArrayList<>();
Collection<VirtualFile> virtualFiles =
FileTypeIndex.getFiles(SimpleFileType.INSTANCE, GlobalSearchScope.allScope(project));
for (VirtualFile virtualFile : virtualFiles) {
SimpleFile simpleFile = (SimpleFile) PsiManager.getInstance(project).findFile(virtualFile);
if (simpleFile != null) {
SimpleProperty[] properties = PsiTreeUtil.getChildrenOfType(simpleFile, SimpleProperty.class);
if (properties != null) {
Collections.addAll(result, properties);
}
}
}
return result;
}
/**
* Attempts to collect any comment elements above the Simple key/value pair.
*/
public static @NotNull String findDocumentationComment(SimpleProperty property) {
List<String> result = new LinkedList<>();
PsiElement element = property.getPrevSibling();
while (element instanceof PsiComment || element instanceof PsiWhiteSpace) {
if (element instanceof PsiComment) {
String commentText = element.getText().replaceFirst("[!# ]+", "");
result.add(commentText);
}
element = element.getPrevSibling();
}
return StringUtil.join(Lists.reverse(result), "\n ");
}
}
Last modified: 24 June 2024