package org.elixir_lang.annonator; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.DumbAware; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiRecursiveElementVisitor; import org.elixir_lang.ElixirSyntaxHighlighter; import org.elixir_lang.psi.call.Call; import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.elixir_lang.psi.call.name.Function.*; import static org.elixir_lang.psi.call.name.Module.KERNEL; /** * Annotates functions and macros from `Kernel` and `Kernel.SpecialForms` modules. */ public class Kernel implements Annotator, DumbAware { /* * CONSTANTS */ private static final Set<String> FUNCTION_NAME_SET = new HashSet<String>( Arrays.asList( new String[] { "abs", "apply", "apply", "binary_part", "bit_size", "byte_size", "div", "elem", "exit", "function_exported?", "get_and_update_in", "get_in", "hd", "inspect", "is_atom", "is_binary", "is_bitstring", "is_boolean", "is_float", "is_function", "is_function", "is_integer", "is_list", "is_map", "is_number", "is_pid", "is_port", "is_reference", "is_tuple", "length", "macro_exported?", "make_ref", "map_size", "max", "min", "node", "node", "not", "put_elem", "put_in", "rem", "round", "self", "send", "spawn", "spawn", "spawn_link", "spawn_link", "spawn_monitor", "spawn_monitor", "struct", "throw", "tl", "trunc", "tuple_size", "update_in" } ) ); private static final Set<String> MACRO_NAME_SET = new HashSet<String>( Arrays.asList( new String[] { "@", "alias!", "and", "binding", DEF, DEFDELEGATE, DEFEXCEPTION, DEFIMPL, DEFMACRO, DEFMACROP, DEFMODULE, DEFOVERRIDABLE, DEFP, DEFPROTOCOL, DEFSTRUCT, DESTRUCTURE, "get_and_update_in", IF, "in", "is_nil", "match?", "or", "put_in", "raise", "raise", "reraise", "reraise", "sigil_C", "sigil_R", "sigil_S", "sigil_W", "sigil_c", "sigil_r", "sigil_s", "sigil_w", "to_char_list", "to_string", UNLESS, "update_in", USE, VAR_BANG } ) ); private static final Set<String> SPECIAL_FORMS_MACRO_NAME_SET = new HashSet<String>( Arrays.asList( new String[]{ "__CALLER__", "__DIR__", "__ENV__", __MODULE__, "__aliases__", "__block__", ALIAS, CASE, COND, "fn", FOR, IMPORT, QUOTE, RECEIVE, REQUIRE, "super", "try", UNQUOTE, "unquote_splicing", "with" } ) ); /* * Public Instance Methods */ /** * Annotates the specified PSI element. * It is guaranteed to be executed in non-reentrant fashion. * I.e there will be no call of this method for this instance before previous call get completed. * Multiple instances of the annotator might exist simultaneously, though. * * @param element to annotate. * @param holder the container which receives annotations created by the plugin. */ @Override public void annotate(@NotNull final PsiElement element, @NotNull final AnnotationHolder holder) { element.accept( new PsiRecursiveElementVisitor() { /* * Public Instance Methods */ @Override public void visitElement(PsiElement element) { if (element instanceof Call) { visitCall((Call) element); } } /* * Private Instance Methods */ private void visitCall(Call call) { String resolvedModuleName = call.resolvedModuleName(); if (resolvedModuleName != null && resolvedModuleName.equals(KERNEL)) { PsiElement functionNameElement = call.functionNameElement(); if (functionNameElement != null) { String functionName = call.functionName(); // a function can't take a `do` block if (call.getDoBlock() == null) { if (FUNCTION_NAME_SET.contains(functionName)) { highlight( functionNameElement, holder, ElixirSyntaxHighlighter.FUNCTION_CALL, ElixirSyntaxHighlighter.PREDEFINED_CALL ); } } if (MACRO_NAME_SET.contains(functionName) || SPECIAL_FORMS_MACRO_NAME_SET.contains(functionName)) { highlight( functionNameElement, holder, ElixirSyntaxHighlighter.MACRO_CALL, ElixirSyntaxHighlighter.PREDEFINED_CALL ); } } } } } ); } /* * Private Instance Methods */ /** * Highlights `element` with the given `textAttributesKey`. * * @param element element to highlight * @param annotationHolder the container which receives annotations created by the plugin. * @param textAttributesKeys text attributes to apply to the `element`. */ private void highlight(@NotNull final PsiElement element, @NotNull AnnotationHolder annotationHolder, @NotNull final TextAttributesKey... textAttributesKeys) { annotationHolder .createInfoAnnotation(element, null) .setEnforcedTextAttributes(TextAttributes.ERASE_MARKER); for (TextAttributesKey textAttributesKey : textAttributesKeys) { annotationHolder .createInfoAnnotation(element, null) .setEnforcedTextAttributes( EditorColorsManager .getInstance() .getGlobalScheme() .getAttributes(textAttributesKey) ); } } }