Skip to content

Helper classes and utilities can be embedded in the code generated by Grammar-Kit.

6.1. 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:

java
package org.consulo.sdk.language.psi.impl;

import consulo.language.ast.ASTNode;

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.

6.2. Update Grammar and Regenerate the Parser ​

Now the utility class is added to the grammar file via the psiImplUtilClass attribute. Add methods for a particular rule to specify which one should be used for PSI classes. Compare the last line of the grammar below to the previous definition.

java
{
  parserClass="org.consulo.sdk.language.parser.SimpleParser"

  extends="consulo.language.impl.psi.ASTWrapperPsiElement"

  psiClassPrefix="Simple"
  psiImplClassSuffix="Impl"
  psiPackage="org.consulo.sdk.language.psi"
  psiImplPackage="org.consulo.sdk.language.psi.impl"

  elementTypeHolderClass="org.consulo.sdk.language.psi.SimpleTypes"
  elementTypeClass="org.consulo.sdk.language.psi.SimpleElementType"
  tokenTypeClass="org.consulo.sdk.language.psi.SimpleTokenType"

  psiImplUtilClass="org.consulo.sdk.language.psi.impl.SimplePsiImplUtil"
}

simpleFile ::= item_*

private item_ ::= (property|COMMENT|CRLF)

property ::= (KEY? SEPARATOR VALUE?) | KEY {methods=[getKey getValue]}

After making changes to the grammar, regenerate the parser and PSI classes.

6.3. Define a Utility to Search Properties ​

Create a utility class to search PSI elements for defined properties over the project. This utility will be used later when implementing code completion.

java
package org.consulo.sdk.language;

import consulo.language.psi.PsiComment;
import consulo.language.psi.PsiElement;
import consulo.language.psi.PsiManager;
import consulo.language.psi.PsiWhiteSpace;
import consulo.language.psi.search.FileTypeIndex;
import consulo.language.psi.scope.GlobalSearchScope;
import consulo.language.psi.util.PsiTreeUtil;
import consulo.project.Project;
import consulo.util.lang.StringUtil;
import consulo.virtualFileSystem.VirtualFile;
import org.consulo.sdk.language.psi.SimpleFile;
import org.consulo.sdk.language.psi.SimpleProperty;

import jakarta.annotation.Nonnull;

import java.util.*;

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 @Nonnull 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();
    }
    Collections.reverse(result);
    return StringUtil.join(result, "\n ");
  }

}