Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
692 changes: 692 additions & 0 deletions gen/org/elixir_lang/heex/lexer/Flex.java

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,41 @@
<lang.parserDefinition language="EEx" implementationClass="org.elixir_lang.eex.ParserDefinition"/>
<!--<lang.psiStructureViewFactory language="EEx" implementationClass="org.elixir_lang.eex.PsiStructureViewFactory"/>-->

<!-- HEEx -->
<fileType.fileViewProviderFactory
filetype="HEEx"
implementationClass="org.elixir_lang.heex.file.view_provider.Factory"
/>
<fileType name="HTML Embedded Elixir"
implementationClass="org.elixir_lang.heex.file.Type"
fieldName="INSTANCE"
language="HEEx"
extensions="heex"
/>
<lang.fileViewProviderFactory
language="HEEx"
implementationClass="org.elixir_lang.heex.file.view_provider.Factory"
/>
<lang.fileViewProviderFactory
language="HEEx"
implementationClass="org.elixir_lang.heex.file.view_provider.Factory"/>
<lang.parserDefinition
language="HEEx"
implementationClass="org.elixir_lang.heex.ParserDefinition"
/>

<!-- Replaces injected outer element types with a dummy string, ensuring the injections don't break the HTML lexer -->
<outerLanguageRangePatcher
language="HTML"
implementationClass="org.elixir_lang.heex.html.HeexHTMLOuterLanguageRangePatcher"
/>

<!-- Disable some unhelpful HTML inspections that are false positives for HEEx components -->
<lang.inspectionSuppressor
language="HTML"
implementationClass="org.elixir_lang.heex.inspections.HTMLInspectionSuppressor"
/>

<!-- Mix related extensions -->
<configurationType implementation="org.elixir_lang.mix.configuration.Type"/>

Expand Down
17 changes: 17 additions & 0 deletions resources/icons/file/heex.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions resources/icons/file/heex_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions src/org/elixir_lang/HEEx.bnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
// CANNOT be called `Parser` because `
parserClass="org.elixir_lang.heex.Parser"
parserUtilClass="org.elixir_lang.heex.HEExParserUtil"

extends="com.intellij.extapi.psi.ASTWrapperPsiElement"

psiClassPrefix="HEEx"
psiImplClassSuffix="Impl"
psiPackage="org.elixir_lang.heex.psi"
psiImplPackage="org.elixir_lang.heex.psi.impl"

elementTypeHolderClass="org.elixir_lang.heex.psi.Types"
elementTypeClass="org.elixir_lang.heex.psi.ElementType"
tokenTypeClass="org.elixir_lang.heex.psi.TokenType"

tokens = [
CLOSING = "%>"
COMMENT = "Comment"
COMMENT_MARKER = "#"
DATA = "Data"
EMPTY_MARKER = "Empty Marker"
EQUALS_MARKER = "="
ELIXIR = "Elixir"
ESCAPED_OPENING = "<%%"
FORWARD_SLASH_MARKER = "/"
OPENING = "<%"
PIPE_MARKER = "|"
]
}

private heexFile ::= (DATA | ESCAPED_OPENING | tag | braces)*
tag ::= OPENING (commentBody | elixirBody) CLOSING
{ pin = 1 }
braces ::= BRACE_OPENING EQUALS_MARKER ELIXIR BRACE_CLOSING

private commentBody ::= COMMENT_MARKER COMMENT?
{ pin = 1 }
private elixirBody ::= elixirMarker? ELIXIR?
private elixirMarker ::= EMPTY_MARKER | EQUALS_MARKER | FORWARD_SLASH_MARKER | PIPE_MARKER
135 changes: 135 additions & 0 deletions src/org/elixir_lang/HEEx.flex
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package org.elixir_lang.heex.lexer;

import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IElementType;
import kotlinx.html.SCRIPT;import org.elixir_lang.heex.psi.Types;

%%

// public instead of package-local to make testing easier.
%public
%class Flex
%implements com.intellij.lexer.FlexLexer
%unicode
%ignorecase
%function advance
%type IElementType
%eof{ return;
%eof}

%{
private int openBraceCount = 0;

private void handleInState(int nextLexicalState) {
yypushback(yylength());
yybegin(nextLexicalState);
}
%}

OPENING = "<%"
CLOSING = "%>"

BRACE_OPENING = "{"
BRACE_CLOSING = "}"

COMMENT_MARKER = "#"
EQUALS_MARKER = "="
// See https://github.com/elixir-lang/elixir/pull/6281
FORWARD_SLASH_MARKER = "/"
PIPE_MARKER = "|"
ESCAPED_OPENING = "<%%"
PROCEDURAL_OPENING = {OPENING} " "

WHITE_SPACE = [\ \t\f\r\n]+
ANY = [^]

START_SCRIPT_TAG = <script[\s>]
END_SCRIPT_TAG = "</script>"
START_STYLE_TAG = <style[\s>]
END_STYLE_TAG = "</style>"

%state WHITESPACE_MAYBE
%state COMMENT
%state ELIXIR
%state MARKER_MAYBE
%state BEGIN_MATCHED_BRACES, MATCHED_BRACES
%state STYLE_TAG,SCRIPT_TAG

%%

<YYINITIAL> {
{BRACE_OPENING} { yybegin(BEGIN_MATCHED_BRACES);
return Types.BRACE_OPENING; }
{START_SCRIPT_TAG} { yybegin(SCRIPT_TAG); return Types.DATA; }
{START_STYLE_TAG} { yybegin(STYLE_TAG); return Types.DATA; }
}

<SCRIPT_TAG> {
{END_SCRIPT_TAG} { yybegin(YYINITIAL); return Types.DATA; }
}

<STYLE_TAG> {
{END_STYLE_TAG} { yybegin(YYINITIAL); return Types.DATA; }
}

<YYINITIAL,SCRIPT_TAG,STYLE_TAG> {
{ESCAPED_OPENING} { return Types.ESCAPED_OPENING; }
{OPENING} { yybegin(MARKER_MAYBE);
return Types.OPENING; }
{ANY} { return Types.DATA; }
}

<MARKER_MAYBE> {
{COMMENT_MARKER} { yybegin(COMMENT);
return Types.COMMENT_MARKER; }
{EQUALS_MARKER} { yybegin(ELIXIR);
return Types.EQUALS_MARKER; }
{FORWARD_SLASH_MARKER} { yybegin(ELIXIR);
return Types.FORWARD_SLASH_MARKER; }
{PIPE_MARKER} { yybegin(ELIXIR);
return Types.PIPE_MARKER; }
{ANY} { handleInState(ELIXIR);
return Types.EMPTY_MARKER; }
}

<BEGIN_MATCHED_BRACES> {
// We pretend there is an equals marker so it looks like a <%= tag to the Elixir parser
{ANY} { handleInState(MATCHED_BRACES);
return Types.EQUALS_MARKER; }
}

<MATCHED_BRACES> {
{BRACE_OPENING} { openBraceCount++;
return Types.ELIXIR; }
{BRACE_CLOSING} {
if (openBraceCount > 0) {
openBraceCount--;
return Types.ELIXIR;
} else {
yybegin(YYINITIAL);
return Types.BRACE_CLOSING;
}
}
{ANY} { return Types.ELIXIR; }
}

<COMMENT, ELIXIR> {
{CLOSING} { yybegin(WHITESPACE_MAYBE);
return Types.CLOSING; }
}

<COMMENT> {
{ANY} { return Types.COMMENT; }
}

<ELIXIR> {
{ANY} { return Types.ELIXIR; }
}

<WHITESPACE_MAYBE> {
// Only completely whitespace before a procedural tag counts as whitespace
{WHITE_SPACE} / {PROCEDURAL_OPENING} { yybegin(YYINITIAL);
return TokenType.WHITE_SPACE; }
{ANY} { handleInState(YYINITIAL); }
}

27 changes: 27 additions & 0 deletions src/org/elixir_lang/HEEx.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.elixir_lang

import com.intellij.psi.ResolveState
import org.elixir_lang.psi.call.Call

object HEEx {
fun isFunctionFrom(call: Call, state: ResolveState): Boolean =
call.functionName()?.let { functionName ->
when (functionName) {
FUNCTION_FROM_FILE_ARITY_RANGE.name ->
call.resolvedFinalArity() in FUNCTION_FROM_FILE_ARITY_RANGE.arityRange &&
resolvesToHEEx(call, state)
FUNCTION_FROM_STRING_ARITY_RANGE.name ->
call.resolvedFinalArity() in FUNCTION_FROM_STRING_ARITY_RANGE.arityRange &&
resolvesToHEEx(call, state)
else -> false
}
} ?: false

private fun resolvesToHEEx(call: Call, state: ResolveState): Boolean =
resolvesToModularName(call, state, "HEEx")

// function_from_file(kind, name, file, args \\ [], options \\ [])
val FUNCTION_FROM_FILE_ARITY_RANGE = NameArityRange("function_from_file", 3..5)
// function_from_string(kind, name, source, args \\ [], options \\ [])
val FUNCTION_FROM_STRING_ARITY_RANGE = NameArityRange("function_from_string", 3..5)
}
12 changes: 12 additions & 0 deletions src/org/elixir_lang/heex/ElementType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.elixir_lang.heex;


import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;

// See https://github.com/JetBrains/intellij-plugins/blob/500f42337a87f463e0340f43e2411266fcfa9c5f/handlebars/src/com/dmarcotte/handlebars/parsing/HbElementType.java
public class ElementType extends IElementType {
public ElementType(@NotNull String debugName) {
super(debugName, HeexLanguage.INSTANCE);
}
}
25 changes: 25 additions & 0 deletions src/org/elixir_lang/heex/File.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.elixir_lang.heex;

import com.intellij.extapi.psi.PsiFileBase;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.psi.FileViewProvider;
import org.elixir_lang.heex.file.Type;
import org.jetbrains.annotations.NotNull;

public class File extends PsiFileBase {
public File(@NotNull FileViewProvider fileViewProvider) {
super(fileViewProvider, HeexLanguage.INSTANCE);
}

@NotNull
@Override
public FileType getFileType() {
return Type.INSTANCE;
}

@NotNull
@Override
public String toString() {
return "HTML Embedded Elixir File";
}
}
6 changes: 6 additions & 0 deletions src/org/elixir_lang/heex/HEExParserUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.elixir_lang.heex;

import com.intellij.lang.parser.GeneratedParserUtilBase;

public class HEExParserUtil extends GeneratedParserUtilBase {
}
28 changes: 28 additions & 0 deletions src/org/elixir_lang/heex/HeexLanguage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.elixir_lang.heex;

import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.psi.templateLanguages.TemplateLanguage;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

// See https://github.com/JetBrains/intellij-plugins/blob/500f42337a87f463e0340f43e2411266fcfa9c5f/handlebars/src/com/dmarcotte/handlebars/HbLanguage.java
public class HeexLanguage extends com.intellij.lang.Language implements TemplateLanguage {
public static final HeexLanguage INSTANCE = new HeexLanguage();

protected HeexLanguage(@Nullable com.intellij.lang.Language baseLanguage,
@NotNull String ID,
@NotNull String... mimeTypes) {
super(baseLanguage, ID, mimeTypes);
}

public HeexLanguage() {
super("HEEx");
}

@Contract(pure = true)
public static LanguageFileType defaultTemplateLanguageFileType() {
return FileTypes.PLAIN_TEXT;
}
}
23 changes: 23 additions & 0 deletions src/org/elixir_lang/heex/Highlighter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.elixir_lang.heex;

import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
import com.intellij.psi.tree.IElementType;
import org.elixir_lang.heex.lexer.LookAhead;
import org.jetbrains.annotations.NotNull;

// See https://github.com/JetBrains/intellij-plugins/blob/500f42337a87f463e0340f43e2411266fcfa9c5f/handlebars/src/com/dmarcotte/handlebars/HbHighlighter.java
public class Highlighter extends SyntaxHighlighterBase {
@NotNull
@Override
public Lexer getHighlightingLexer() {
return new LookAhead();
}

@NotNull
@Override
public TextAttributesKey[] getTokenHighlights(IElementType tokenType) {
return new TextAttributesKey[0];
}
}
8 changes: 8 additions & 0 deletions src/org/elixir_lang/heex/Icons.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.elixir_lang.heex

import com.intellij.openapi.util.IconLoader

object Icons {
@JvmField
val FILE = IconLoader.getIcon("/icons/file/heex.svg", Icons.javaClass)
}
Loading