From 977c23958065fb8f658ead0443bc485a7bcf8b70 Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Sun, 5 Feb 2023 14:47:57 +0100 Subject: [PATCH 01/11] Review highlighter for all tokens --- .../nixos/idea/lang/NixSyntaxHighlighter.java | 269 ++++++++---------- .../lang/NixSyntaxHighlighterFactory.java | 4 +- src/main/lang/Nix.bnf | 5 +- src/main/lang/Nix.flex | 16 +- .../nixos/idea/_testutil/ReflectionUtils.java | 33 +++ .../idea/lang/NixSyntaxHighlighterTest.java | 76 +++++ .../ParsingTest/StringWithEscapeSequences.txt | 48 ++-- 7 files changed, 264 insertions(+), 187 deletions(-) create mode 100644 src/test/java/org/nixos/idea/_testutil/ReflectionUtils.java create mode 100644 src/test/java/org/nixos/idea/lang/NixSyntaxHighlighterTest.java diff --git a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java index 6299cc07..7ac183a5 100644 --- a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java +++ b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java @@ -2,97 +2,133 @@ import com.intellij.lexer.Lexer; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.HighlighterColors; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; +import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.nixos.idea.psi.NixTypes; -import static com.intellij.openapi.editor.colors.TextAttributesKey.EMPTY_ARRAY; +import java.util.Map; + import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey; +import static java.util.Map.entry; public class NixSyntaxHighlighter extends SyntaxHighlighterBase { - public static final TextAttributesKey LPAREN = createTextAttributesKey("LPAREN", DefaultLanguageHighlighterColors.PARENTHESES); - public static final TextAttributesKey RPAREN = createTextAttributesKey("RPAREN", DefaultLanguageHighlighterColors.PARENTHESES); - - public static final TextAttributesKey LCURLY = createTextAttributesKey("LCURLY", DefaultLanguageHighlighterColors.BRACES); - public static final TextAttributesKey RCURLY = createTextAttributesKey("RCURLY", DefaultLanguageHighlighterColors.BRACES); - - public static final TextAttributesKey LBRAC = createTextAttributesKey("LBRAC", DefaultLanguageHighlighterColors.BRACKETS); - public static final TextAttributesKey RBRAC = createTextAttributesKey("RBRAC", DefaultLanguageHighlighterColors.BRACKETS); - - public static final TextAttributesKey COLON = createTextAttributesKey("COLON", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey SEMI = createTextAttributesKey("SEMI", DefaultLanguageHighlighterColors.SEMICOLON); - public static final TextAttributesKey COMMA = createTextAttributesKey("COMMA", DefaultLanguageHighlighterColors.COMMA); - - public static final TextAttributesKey ID = createTextAttributesKey("ID", DefaultLanguageHighlighterColors.IDENTIFIER); - public static final TextAttributesKey INT = createTextAttributesKey("INT", DefaultLanguageHighlighterColors.NUMBER); - - public static final TextAttributesKey ASSERT = createTextAttributesKey("ASSERT", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey IF = createTextAttributesKey("IF", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey ELSE = createTextAttributesKey("ELSE", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey THEN = createTextAttributesKey("THEN", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey LET = createTextAttributesKey("LET", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey REC = createTextAttributesKey("REC", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey IN = createTextAttributesKey("IN", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey WITH = createTextAttributesKey("WITH", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey INHERIT = createTextAttributesKey("INHERIT", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey OR_KW = createTextAttributesKey("OR_KW", DefaultLanguageHighlighterColors.KEYWORD); - - public static final TextAttributesKey GT = createTextAttributesKey("GT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey LT = createTextAttributesKey("LT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey LEQ = createTextAttributesKey("LEQ", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey GEQ = createTextAttributesKey("GEQ", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey NEQ = createTextAttributesKey("NEQ", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey EQ = createTextAttributesKey("EQ", DefaultLanguageHighlighterColors.OPERATION_SIGN); - - public static final TextAttributesKey PLUS = createTextAttributesKey("PLUS", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey MINUS = createTextAttributesKey("MINUS", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey DIVIDE = createTextAttributesKey("DIVIDE", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey TIMES = createTextAttributesKey("TIMES", DefaultLanguageHighlighterColors.OPERATION_SIGN); - - public static final TextAttributesKey NOT = createTextAttributesKey("NOT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey AND = createTextAttributesKey("AND", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey OR = createTextAttributesKey("OR", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey IMPL = createTextAttributesKey("IMPL", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey UPDATE = createTextAttributesKey("UPDATE", DefaultLanguageHighlighterColors.OPERATION_SIGN); - - public static final TextAttributesKey IS = createTextAttributesKey("IS", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey NAMED = createTextAttributesKey("NAMED", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey CONCAT = createTextAttributesKey("CONCAT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - - public static final TextAttributesKey DOT = createTextAttributesKey("DOT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey DOLLAR_CURLY = createTextAttributesKey("DOLLAR_CURLY", DefaultLanguageHighlighterColors.OPERATION_SIGN); - - public static final TextAttributesKey PATH = createTextAttributesKey("PATH", DefaultLanguageHighlighterColors.LABEL); - public static final TextAttributesKey SPATH = createTextAttributesKey("SPATH", DefaultLanguageHighlighterColors.LABEL); - public static final TextAttributesKey HPATH = createTextAttributesKey("HPATH", DefaultLanguageHighlighterColors.LABEL); - - public static final TextAttributesKey IND_STR = createTextAttributesKey("IND_STR", DefaultLanguageHighlighterColors.STRING); - public static final TextAttributesKey STR = createTextAttributesKey("STR", DefaultLanguageHighlighterColors.STRING); - - public static final TextAttributesKey SCOMMENT = createTextAttributesKey("SCOMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); - public static final TextAttributesKey MCOMMENT = createTextAttributesKey("MCOMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT); - - public static final TextAttributesKey LAMBDA = createTextAttributesKey("LAMBDA", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION); - public static final TextAttributesKey FORMAL = createTextAttributesKey("FORMAL", DefaultLanguageHighlighterColors.PARAMETER); - - public static final TextAttributesKey[] SEMI_KEYS = new TextAttributesKey[]{SEMI}; - public static final TextAttributesKey[] COMMA_KEYS = new TextAttributesKey[]{COMMA}; - public static final TextAttributesKey[] KEYWORD_KEYS = new TextAttributesKey[]{IN,OR_KW,REC,IF,ELSE,THEN,LET,IN,WITH,INHERIT}; - public static final TextAttributesKey[] STRING_KEYS = new TextAttributesKey[]{STR,IND_STR}; - public static final TextAttributesKey[] PAREN_KEYS = new TextAttributesKey[]{LPAREN,RPAREN,LCURLY,RCURLY,LBRAC,RBRAC}; - public static final TextAttributesKey[] COLON_KEYS = new TextAttributesKey[]{COLON}; - public static final TextAttributesKey[] ID_KEYS = new TextAttributesKey[]{ID}; - public static final TextAttributesKey[] NUMBER_KEYS = new TextAttributesKey[]{INT}; - public static final TextAttributesKey[] OPERATOR_KEYS = new TextAttributesKey[]{ - GT,LT,LEQ,GEQ,NEQ,EQ,PLUS,MINUS,DIVIDE,TIMES,NOT,AND,OR,IMPL,UPDATE,IS,NAMED,CONCAT,DOT,DOLLAR_CURLY - }; - public static final TextAttributesKey[] LINE_COMMENT_KEYS = new TextAttributesKey[]{SCOMMENT}; - public static final TextAttributesKey[] MULTI_LINE_COMMENT_KEYS = new TextAttributesKey[]{MCOMMENT}; - public static final TextAttributesKey[] FUNCTION_KEYS = new TextAttributesKey[]{LAMBDA}; - public static final TextAttributesKey[] PARAMETER_KEYS = new TextAttributesKey[]{FORMAL}; + public static final TextAttributesKey KEYWORD = + createTextAttributesKey("NIX_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD); + public static final TextAttributesKey IDENTIFIER = + createTextAttributesKey("NIX_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER); + + public static final TextAttributesKey SEMICOLON = + createTextAttributesKey("NIX_SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON); + public static final TextAttributesKey COMMA = + createTextAttributesKey("NIX_COMMA", DefaultLanguageHighlighterColors.COMMA); + public static final TextAttributesKey DOT = + createTextAttributesKey("NIX_DOT", DefaultLanguageHighlighterColors.DOT); + public static final TextAttributesKey ASSIGN = + createTextAttributesKey("NIX_ASSIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey COLON = + createTextAttributesKey("NIX_COLON", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey AT = + createTextAttributesKey("NIX_AT", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey ELLIPSIS = + createTextAttributesKey("NIX_ELLIPSIS", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey OPERATION_SIGN = + createTextAttributesKey("NIX_OPERATION_SIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN); + + public static final TextAttributesKey PARENTHESES = + createTextAttributesKey("NIX_PARENTHESES", DefaultLanguageHighlighterColors.PARENTHESES); + public static final TextAttributesKey BRACES = + createTextAttributesKey("NIX_BRACES", DefaultLanguageHighlighterColors.BRACES); + public static final TextAttributesKey BRACKETS = + createTextAttributesKey("NIX_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS); + + public static final TextAttributesKey STRING = + createTextAttributesKey("NIX_STRING", DefaultLanguageHighlighterColors.STRING); + public static final TextAttributesKey STRING_ESCAPE = + createTextAttributesKey("NIX_STRING_ESCAPE", DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE); + public static final TextAttributesKey URI = + createTextAttributesKey("NIX_URI", DefaultLanguageHighlighterColors.STRING); + public static final TextAttributesKey PATH = + createTextAttributesKey("NIX_PATH", DefaultLanguageHighlighterColors.STRING); + public static final TextAttributesKey NUMBER = + createTextAttributesKey("NIX_NUMBER", DefaultLanguageHighlighterColors.NUMBER); + + public static final TextAttributesKey LINE_COMMENT = + createTextAttributesKey("NIX_LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); + public static final TextAttributesKey BLOCK_COMMENT = + createTextAttributesKey("NIX_BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT); + + private static final Map TOKEN_MAP = Map.ofEntries( + // Keywords + entry(NixTypes.IF, KEYWORD), + entry(NixTypes.THEN, KEYWORD), + entry(NixTypes.ELSE, KEYWORD), + entry(NixTypes.ASSERT, KEYWORD), + entry(NixTypes.WITH, KEYWORD), + entry(NixTypes.LET, KEYWORD), + entry(NixTypes.IN, KEYWORD), + entry(NixTypes.REC, KEYWORD), + entry(NixTypes.INHERIT, KEYWORD), + entry(NixTypes.OR_KW, KEYWORD), + // Identifiers + entry(NixTypes.ID, IDENTIFIER), + // Operators + entry(NixTypes.ASSIGN, ASSIGN), + entry(NixTypes.COLON, COLON), + entry(NixTypes.SEMI, SEMICOLON), + entry(NixTypes.COMMA, COMMA), + entry(NixTypes.DOT, DOT), + entry(NixTypes.ELLIPSIS, ELLIPSIS), + entry(NixTypes.AT, AT), + entry(NixTypes.HAS, OPERATION_SIGN), + entry(NixTypes.NOT, OPERATION_SIGN), + entry(NixTypes.TIMES, OPERATION_SIGN), + entry(NixTypes.DIVIDE, OPERATION_SIGN), + entry(NixTypes.PLUS, OPERATION_SIGN), + entry(NixTypes.MINUS, OPERATION_SIGN), + entry(NixTypes.LT, OPERATION_SIGN), + entry(NixTypes.GT, OPERATION_SIGN), + entry(NixTypes.CONCAT, OPERATION_SIGN), + entry(NixTypes.UPDATE, OPERATION_SIGN), + entry(NixTypes.LEQ, OPERATION_SIGN), + entry(NixTypes.GEQ, OPERATION_SIGN), + entry(NixTypes.EQ, OPERATION_SIGN), + entry(NixTypes.NEQ, OPERATION_SIGN), + entry(NixTypes.AND, OPERATION_SIGN), + entry(NixTypes.OR, OPERATION_SIGN), + entry(NixTypes.IMPL, OPERATION_SIGN), + // Parentheses + entry(NixTypes.LPAREN, PARENTHESES), + entry(NixTypes.RPAREN, PARENTHESES), + entry(NixTypes.LBRAC, BRACKETS), + entry(NixTypes.RBRAC, BRACKETS), + entry(NixTypes.LCURLY, BRACES), + entry(NixTypes.RCURLY, BRACES), + entry(NixTypes.DOLLAR, BRACES), + // Literals + entry(NixTypes.INT, NUMBER), + entry(NixTypes.FLOAT, NUMBER), + entry(NixTypes.PATH, PATH), + entry(NixTypes.HPATH, PATH), + entry(NixTypes.SPATH, PATH), + entry(NixTypes.URI, URI), + // String literals + entry(NixTypes.STR, STRING), + entry(NixTypes.STRING_CLOSE, STRING), + entry(NixTypes.STRING_OPEN, STRING), + entry(NixTypes.IND_STR, STRING), + entry(NixTypes.IND_STRING_CLOSE, STRING), + entry(NixTypes.IND_STRING_OPEN, STRING), + entry(NixTypes.STR_ESCAPE, STRING_ESCAPE), + entry(NixTypes.IND_STR_ESCAPE, STRING_ESCAPE), + // Other + entry(NixTypes.SCOMMENT, LINE_COMMENT), + entry(NixTypes.MCOMMENT, BLOCK_COMMENT), + entry(TokenType.BAD_CHARACTER, HighlighterColors.BAD_CHARACTER)); @Override public @NotNull Lexer getHighlightingLexer() { @@ -101,75 +137,6 @@ public class NixSyntaxHighlighter extends SyntaxHighlighterBase { @Override public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) { - { - if (tokenType == NixTypes.PARAM) return PARAMETER_KEYS; - if (tokenType == NixTypes.EXPR_LAMBDA) return FUNCTION_KEYS; - if (tokenType == NixTypes.COMMA) return COMMA_KEYS; - if (tokenType == NixTypes.SEMI) return SEMI_KEYS; - if (tokenType == NixTypes.COLON) { - return COLON_KEYS; - } - if (tokenType == NixTypes.SCOMMENT) { - return LINE_COMMENT_KEYS; - } - if (tokenType == NixTypes.MCOMMENT) { - return MULTI_LINE_COMMENT_KEYS; - } - if (tokenType == NixTypes.ID) { - return ID_KEYS; - } - if (tokenType == NixTypes.INT) { - return NUMBER_KEYS; - } - if( - tokenType == NixTypes.GT || - tokenType == NixTypes.LT || - tokenType == NixTypes.LEQ || - tokenType == NixTypes.GEQ || - tokenType == NixTypes.NEQ || - tokenType == NixTypes.EQ || - tokenType == NixTypes.PLUS || - tokenType == NixTypes.MINUS || - tokenType == NixTypes.DIVIDE || - tokenType == NixTypes.TIMES || - tokenType == NixTypes.NOT || - tokenType == NixTypes.AND || - tokenType == NixTypes.OR || - tokenType == NixTypes.IMPL || - tokenType == NixTypes.UPDATE || - tokenType == NixTypes.HAS || - tokenType == NixTypes.AT || - tokenType == NixTypes.CONCAT || - tokenType == NixTypes.DOT ) - return OPERATOR_KEYS; - if ( - tokenType == NixTypes.RBRAC || - tokenType == NixTypes.LBRAC || - tokenType == NixTypes.RPAREN || - tokenType == NixTypes.LPAREN || - tokenType == NixTypes.RCURLY || - tokenType == NixTypes.DOLLAR || - tokenType == NixTypes.LCURLY) { - return PAREN_KEYS; - } - if (tokenType == NixTypes.STR || tokenType == NixTypes.IND_STR) { - return STRING_KEYS; - } - if ( - tokenType == NixTypes.IF || - tokenType == NixTypes.THEN || - tokenType == NixTypes.ELSE || - tokenType == NixTypes.ASSERT || - tokenType == NixTypes.WITH || - tokenType == NixTypes.LET || - tokenType == NixTypes.IN || - tokenType == NixTypes.REC || - tokenType == NixTypes.INHERIT || - tokenType == NixTypes.OR_KW) { - return KEYWORD_KEYS; - } - return EMPTY_ARRAY; - } + return pack(TOKEN_MAP.get(tokenType)); } } - diff --git a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java index 4c370cd1..010eee84 100644 --- a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java +++ b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java @@ -7,10 +7,8 @@ import org.jetbrains.annotations.NotNull; public class NixSyntaxHighlighterFactory extends SyntaxHighlighterFactory { - @NotNull @Override - public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) { + public @NotNull SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) { return new NixSyntaxHighlighter(); } } - diff --git a/src/main/lang/Nix.bnf b/src/main/lang/Nix.bnf index 83e8f7f0..9700e112 100644 --- a/src/main/lang/Nix.bnf +++ b/src/main/lang/Nix.bnf @@ -185,10 +185,11 @@ std_string ::= STRING_OPEN string_part* STRING_CLOSE { pin=1 } ind_string ::= IND_STRING_OPEN string_part* IND_STRING_CLOSE { pin=1 } ;{ extends("string_text|antiquotation")=string_part } string_part ::= string_text | antiquotation { recoverWhile=string_part_recover } -string_text ::= STR | IND_STR +string_text ::= string_token+ antiquotation ::= DOLLAR LCURLY expr recover_antiquotation RCURLY { pin=1 } private recover_antiquotation ::= { recoverWhile=curly_recover } -private string_part_recover ::= !(STR | IND_STR | DOLLAR | STRING_CLOSE | IND_STRING_CLOSE) +private string_part_recover ::= !(DOLLAR | STRING_CLOSE | IND_STRING_CLOSE | string_token) +private string_token ::= STR | IND_STR | STR_ESCAPE | IND_STR_ESCAPE ;{ extends("bind_attr|bind_inherit")=bind } bind ::= bind_attr | bind_inherit diff --git a/src/main/lang/Nix.flex b/src/main/lang/Nix.flex index c52ba4fb..ec9527dd 100644 --- a/src/main/lang/Nix.flex +++ b/src/main/lang/Nix.flex @@ -141,22 +141,20 @@ MCOMMENT=\/\*([^*]|\*[^\/])*\*\/ } { - ([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})*\$/\" { return STR; } - ([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})+ | - \$|\\|\$\\ { return STR; } + [^\$\"\\]+ { return STR; } + "$"|"$$"|\\ { return STR; } + \\{ANY} { return STR_ESCAPE; } "$"/"{" { pushState(ANTIQUOTATION_START); return DOLLAR; } \" { popState(); return STRING_CLOSE; } } { - ([^\$\']|\$[^\{\']|\'[^\'\$])+ | - "''$" | - \$ | - "'''" | - "''"\\{ANY} { return IND_STR; } + [^\$\']+ { return IND_STR; } + "$"|"$$"|"'" { return IND_STR; } + "''$"|"'''" { return IND_STR_ESCAPE; } + "''"\\{ANY} { return IND_STR_ESCAPE; } "$"/"{" { pushState(ANTIQUOTATION_START); return DOLLAR; } "''" { popState(); return IND_STRING_CLOSE; } - "'" { return IND_STR; } } { diff --git a/src/test/java/org/nixos/idea/_testutil/ReflectionUtils.java b/src/test/java/org/nixos/idea/_testutil/ReflectionUtils.java new file mode 100644 index 00000000..3317b377 --- /dev/null +++ b/src/test/java/org/nixos/idea/_testutil/ReflectionUtils.java @@ -0,0 +1,33 @@ +package org.nixos.idea._testutil; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Named; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ReflectionUtils { + + private ReflectionUtils() { + // Cannot be instantiated. + } + + public static @NotNull Stream> getPublicStaticFieldValues(@NotNull Class owner, @NotNull Class valueType) { + return Arrays.stream(owner.getDeclaredFields()) + .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) + .map(field -> { + try { + Object value = field.get(null); + if (valueType.isInstance(value)) { + String name = owner.getSimpleName() + "." + field.getName(); + return Named.of(name, valueType.cast(value)); + } + return null; + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + }).filter(Objects::nonNull); + } +} diff --git a/src/test/java/org/nixos/idea/lang/NixSyntaxHighlighterTest.java b/src/test/java/org/nixos/idea/lang/NixSyntaxHighlighterTest.java new file mode 100644 index 00000000..f2d44d30 --- /dev/null +++ b/src/test/java/org/nixos/idea/lang/NixSyntaxHighlighterTest.java @@ -0,0 +1,76 @@ +package org.nixos.idea.lang; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.psi.TokenType; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.nixos.idea._testutil.ReflectionUtils; +import org.nixos.idea.psi.NixTokenType; +import org.nixos.idea.psi.NixTypes; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +final class NixSyntaxHighlighterTest { + @Test + void testAttributesKeysForUnknownTokenType() { + TextAttributesKey[] tokenHighlights = new NixSyntaxHighlighter().getTokenHighlights(TokenType.CODE_FRAGMENT); + assertNotNull(tokenHighlights, "tokenHighlights"); + assertEquals(0, tokenHighlights.length, "tokenHighlights.length"); + } + + @ParameterizedTest + @MethodSource + void testAttributesKeysForKnownTokenTypes(@NotNull IElementType tokenType) { + TextAttributesKey[] tokenHighlights = new NixSyntaxHighlighter().getTokenHighlights(tokenType); + assertNotNull(tokenHighlights, "tokenHighlights"); + assertNotEquals(0, tokenHighlights.length, "tokenHighlights.length"); + assertAll(IntStream.range(0, tokenHighlights.length).mapToObj(index -> + () -> assertNotNull(tokenHighlights[index], String.format("tokenHighlights[%d]", index)))); + } + + static @NotNull Stream> testAttributesKeysForKnownTokenTypes() { + return Stream.concat( + Stream.of(Named.of("TokenType.BAD_CHARACTER", TokenType.BAD_CHARACTER)), + ReflectionUtils.getPublicStaticFieldValues(NixTypes.class, NixTokenType.class)); + } + + @ParameterizedTest + @MethodSource + void testKeyNamesHaveNixPrefix(@NotNull TextAttributesKey key) { + assertTrue(key.getExternalName().startsWith("NIX_")); + } + + static @NotNull Stream> testKeyNamesHaveNixPrefix() { + return ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class); + } + + @ParameterizedTest + @MethodSource + void testNoDuplicateKeyNames(@NotNull TextAttributesKey key) { + Set duplicates = ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class) + .filter(other -> other.getPayload().getExternalName().equals(key.getExternalName())) + .map(Named::getName) + .collect(Collectors.toSet()); + if (duplicates.size() != 1) { + fail("Duplicates: " + duplicates); + } + } + + static @NotNull Stream> testNoDuplicateKeyNames() { + return ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class); + } +} diff --git a/src/test/testData/ParsingTest/StringWithEscapeSequences.txt b/src/test/testData/ParsingTest/StringWithEscapeSequences.txt index 6aa778a0..ba58444b 100644 --- a/src/test/testData/ParsingTest/StringWithEscapeSequences.txt +++ b/src/test/testData/ParsingTest/StringWithEscapeSequences.txt @@ -5,71 +5,75 @@ Nix File(0,104) NixIndStringImpl(IND_STRING)(2,9) PsiElement(IND_STRING_OPEN)('''')(2,4) NixStringTextImpl(STRING_TEXT)(4,7) - PsiElement(IND_STR)(''''')(4,7) + PsiElement(IND_STR_ESCAPE)(''''')(4,7) PsiElement(IND_STRING_CLOSE)('''')(7,9) PsiWhiteSpace('\n')(9,10) NixStdStringImpl(STD_STRING)(10,19) PsiElement(STRING_OPEN)('"')(10,11) NixStringTextImpl(STRING_TEXT)(11,18) - PsiElement(STR)('a\${b}c')(11,18) + PsiElement(STR)('a')(11,12) + PsiElement(STR_ESCAPE)('\$')(12,14) + PsiElement(STR)('{b}c')(14,18) PsiElement(STRING_CLOSE)('"')(18,19) PsiWhiteSpace('\n')(19,20) NixIndStringImpl(IND_STRING)(20,32) PsiElement(IND_STRING_OPEN)('''')(20,22) - NixStringTextImpl(STRING_TEXT)(22,23) + NixStringTextImpl(STRING_TEXT)(22,30) PsiElement(IND_STR)('a')(22,23) - NixStringTextImpl(STRING_TEXT)(23,26) - PsiElement(IND_STR)('''$')(23,26) - NixStringTextImpl(STRING_TEXT)(26,30) + PsiElement(IND_STR_ESCAPE)('''$')(23,26) PsiElement(IND_STR)('{b}c')(26,30) PsiElement(IND_STRING_CLOSE)('''')(30,32) PsiWhiteSpace('\n')(32,33) NixStdStringImpl(STD_STRING)(33,43) PsiElement(STRING_OPEN)('"')(33,34) NixStringTextImpl(STRING_TEXT)(34,42) - PsiElement(STR)('a\n\r\tc')(34,42) + PsiElement(STR)('a')(34,35) + PsiElement(STR_ESCAPE)('\n')(35,37) + PsiElement(STR_ESCAPE)('\r')(37,39) + PsiElement(STR_ESCAPE)('\t')(39,41) + PsiElement(STR)('c')(41,42) PsiElement(STRING_CLOSE)('"')(42,43) PsiWhiteSpace('\n')(43,44) NixIndStringImpl(IND_STRING)(44,62) PsiElement(IND_STRING_OPEN)('''')(44,46) - NixStringTextImpl(STRING_TEXT)(46,47) + NixStringTextImpl(STRING_TEXT)(46,60) PsiElement(IND_STR)('a')(46,47) - NixStringTextImpl(STRING_TEXT)(47,51) - PsiElement(IND_STR)('''\n')(47,51) - NixStringTextImpl(STRING_TEXT)(51,55) - PsiElement(IND_STR)('''\r')(51,55) - NixStringTextImpl(STRING_TEXT)(55,59) - PsiElement(IND_STR)('''\t')(55,59) - NixStringTextImpl(STRING_TEXT)(59,60) + PsiElement(IND_STR_ESCAPE)('''\n')(47,51) + PsiElement(IND_STR_ESCAPE)('''\r')(51,55) + PsiElement(IND_STR_ESCAPE)('''\t')(55,59) PsiElement(IND_STR)('c')(59,60) PsiElement(IND_STRING_CLOSE)('''')(60,62) PsiWhiteSpace('\n')(62,63) NixStdStringImpl(STD_STRING)(63,69) PsiElement(STRING_OPEN)('"')(63,64) NixStringTextImpl(STRING_TEXT)(64,68) - PsiElement(STR)('a\bc')(64,68) + PsiElement(STR)('a')(64,65) + PsiElement(STR_ESCAPE)('\b')(65,67) + PsiElement(STR)('c')(67,68) PsiElement(STRING_CLOSE)('"')(68,69) PsiWhiteSpace('\n')(69,70) NixIndStringImpl(IND_STRING)(70,80) PsiElement(IND_STRING_OPEN)('''')(70,72) - NixStringTextImpl(STRING_TEXT)(72,73) + NixStringTextImpl(STRING_TEXT)(72,78) PsiElement(IND_STR)('a')(72,73) - NixStringTextImpl(STRING_TEXT)(73,77) - PsiElement(IND_STR)('''\b')(73,77) - NixStringTextImpl(STRING_TEXT)(77,78) + PsiElement(IND_STR_ESCAPE)('''\b')(73,77) PsiElement(IND_STR)('c')(77,78) PsiElement(IND_STRING_CLOSE)('''')(78,80) PsiWhiteSpace('\n')(80,81) NixStdStringImpl(STD_STRING)(81,90) PsiElement(STRING_OPEN)('"')(81,82) NixStringTextImpl(STRING_TEXT)(82,89) - PsiElement(STR)('a$${b}c')(82,89) + PsiElement(STR)('a')(82,83) + PsiElement(STR)('$$')(83,85) + PsiElement(STR)('{b}c')(85,89) PsiElement(STRING_CLOSE)('"')(89,90) PsiWhiteSpace('\n')(90,91) NixIndStringImpl(IND_STRING)(91,102) PsiElement(IND_STRING_OPEN)('''')(91,93) NixStringTextImpl(STRING_TEXT)(93,100) - PsiElement(IND_STR)('a$${b}c')(93,100) + PsiElement(IND_STR)('a')(93,94) + PsiElement(IND_STR)('$$')(94,96) + PsiElement(IND_STR)('{b}c')(96,100) PsiElement(IND_STRING_CLOSE)('''')(100,102) PsiWhiteSpace('\n')(102,103) PsiElement(])(']')(103,104) From c39e6f3f86d483d5966e0a4350894a2777350d8c Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Sun, 5 Feb 2023 17:48:42 +0100 Subject: [PATCH 02/11] Add color settings for highlighting --- .../idea/settings/NixColorSettingsPage.java | 99 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 3 + .../settings/NixColorSettingsPageTest.java | 30 ++++++ 3 files changed, 132 insertions(+) create mode 100644 src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java create mode 100644 src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java diff --git a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java new file mode 100644 index 00000000..e3562551 --- /dev/null +++ b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java @@ -0,0 +1,99 @@ +package org.nixos.idea.settings; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.options.colors.AttributesDescriptor; +import com.intellij.openapi.options.colors.ColorDescriptor; +import com.intellij.openapi.options.colors.ColorSettingsPage; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.nixos.idea.icon.NixIcons; +import org.nixos.idea.lang.NixSyntaxHighlighter; + +import javax.swing.Icon; +import java.util.Map; + +public final class NixColorSettingsPage implements ColorSettingsPage { + private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{ + descriptor("Keyword", NixSyntaxHighlighter.KEYWORD), + descriptor("Identifier", NixSyntaxHighlighter.IDENTIFIER), + descriptor("Operators//Semicolon", NixSyntaxHighlighter.SEMICOLON), + descriptor("Operators//Comma", NixSyntaxHighlighter.COMMA), + descriptor("Operators//Dot", NixSyntaxHighlighter.DOT), + descriptor("Operators//Assignment operator", NixSyntaxHighlighter.ASSIGN), + descriptor("Operators//Colon", NixSyntaxHighlighter.COLON), + descriptor("Operators//At sign (@)", NixSyntaxHighlighter.AT), + descriptor("Operators//Ellipsis", NixSyntaxHighlighter.ELLIPSIS), + descriptor("Operators//Other operators", NixSyntaxHighlighter.OPERATION_SIGN), + descriptor("Braces//Parentheses", NixSyntaxHighlighter.PARENTHESES), + descriptor("Braces//Curly braces", NixSyntaxHighlighter.BRACES), + descriptor("Braces//Brackets", NixSyntaxHighlighter.BRACKETS), + descriptor("Literals and Values//Number", NixSyntaxHighlighter.NUMBER), + descriptor("Literals and Values//String", NixSyntaxHighlighter.STRING), + descriptor("Literals and Values//Escape sequence", NixSyntaxHighlighter.STRING_ESCAPE), + descriptor("Literals and Values//Path", NixSyntaxHighlighter.PATH), + descriptor("Literals and Values//URI", NixSyntaxHighlighter.URI), + descriptor("Comments//Line comment", NixSyntaxHighlighter.LINE_COMMENT), + descriptor("Comments//Block comment", NixSyntaxHighlighter.BLOCK_COMMENT), + }; + + @Override + public @NotNull Icon getIcon() { + return NixIcons.FILE; + } + + @Override + public @NotNull SyntaxHighlighter getHighlighter() { + return new NixSyntaxHighlighter(); + } + + @Override + public @NonNls @NotNull String getDemoText() { + // language=Nix + return "/* This code demonstrates the syntax highlighting for the Nix Expression Language */\n" + + "let\n" + + " literals.number = 42;\n" + + " literals.string1 = \"This is a normal string\";\n" + + " literals.string2 = ''\n" + + " Broken escape sequence: \\${literals.number}\n" + + " Escaped interpolation: ''${literals.number}\n" + + " Generic escape sequence: $''\\{literals.number}\n" + + " '';\n" + + " literals.paths = [/etc/gitconfig ~/.gitconfig .git/config];\n" + + " # Note that unquoted URIs were deperecated by RFC 45\n" + + " literals.uri = https://github.com/NixOS/rfcs/pull/45;\n" + + "in {\n" + + " inherit (literals) number string1 string2 paths uri;\n" + + " nixpkgs = import ;\n" + + " baseNames = map baseNameOf literals.paths;\n" + + " f = { multiply ? 1, add ? 0, ... }@args:\n" + + " builtins.mapAttrs (name: value: multiply * value + add) args;\n" + + "}"; + } + + @Override + public @Nullable Map getAdditionalHighlightingTagToDescriptorMap() { + return null; + } + + @Override + public AttributesDescriptor @NotNull [] getAttributeDescriptors() { + return DESCRIPTORS; + } + + @Override + public ColorDescriptor @NotNull [] getColorDescriptors() { + return ColorDescriptor.EMPTY_ARRAY; + } + + @Override + public @NotNull @Nls(capitalization = Nls.Capitalization.Title) String getDisplayName() { + return "Nix"; + } + + private static @NotNull AttributesDescriptor descriptor(@NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String displayName, @NotNull TextAttributesKey key) { + return new AttributesDescriptor(displayName, key); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8199fe55..76aec18f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -37,6 +37,9 @@ id="org.nixos.idea.settings.NixIDEASettings" instance="org.nixos.idea.settings.NixIDEASettings" /> + + diff --git a/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java b/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java new file mode 100644 index 00000000..33413910 --- /dev/null +++ b/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java @@ -0,0 +1,30 @@ +package org.nixos.idea.settings; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.options.colors.AttributesDescriptor; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.nixos.idea._testutil.ReflectionUtils; +import org.nixos.idea.lang.NixSyntaxHighlighter; + +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +final class NixColorSettingsPageTest { + @ParameterizedTest + @MethodSource + void testNoKeyMissing(@NotNull TextAttributesKey textAttributesKey) { + AttributesDescriptor[] descriptors = new NixColorSettingsPage().getAttributeDescriptors(); + assertTrue(Arrays.stream(descriptors) + .map(AttributesDescriptor::getKey) + .anyMatch(textAttributesKey::equals)); + } + + static @NotNull Stream> testNoKeyMissing() { + return ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class); + } +} From 237c5129c65d36059c34c81c50257a4661c5e20e Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Mon, 6 Feb 2023 20:29:16 +0100 Subject: [PATCH 03/11] Add basic support for "semantic highlighting" --- .../nixos/idea/lang/NixRainbowVisitor.java | 164 ++++++++++++++++++ .../nixos/idea/lang/NixSyntaxHighlighter.java | 9 +- .../idea/settings/NixColorSettingsPage.java | 20 ++- src/main/lang/Nix.bnf | 3 +- src/main/resources/META-INF/plugin.xml | 2 + 5 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java diff --git a/src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java b/src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java new file mode 100644 index 00000000..64e92cf1 --- /dev/null +++ b/src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java @@ -0,0 +1,164 @@ +package org.nixos.idea.lang; + +import com.intellij.codeInsight.daemon.RainbowVisitor; +import com.intellij.codeInsight.daemon.impl.HighlightVisitor; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.nixos.idea.file.NixFile; +import org.nixos.idea.psi.NixAttr; +import org.nixos.idea.psi.NixAttrPath; +import org.nixos.idea.psi.NixBind; +import org.nixos.idea.psi.NixBindAttr; +import org.nixos.idea.psi.NixBindInherit; +import org.nixos.idea.psi.NixExpr; +import org.nixos.idea.psi.NixExprLambda; +import org.nixos.idea.psi.NixExprLet; +import org.nixos.idea.psi.NixExprSelect; +import org.nixos.idea.psi.NixIdentifier; +import org.nixos.idea.psi.NixLegacyLet; +import org.nixos.idea.psi.NixParam; +import org.nixos.idea.psi.NixParamSet; +import org.nixos.idea.psi.NixSet; +import org.nixos.idea.psi.NixTypes; + +import java.util.List; +import java.util.function.BiPredicate; + +public final class NixRainbowVisitor extends RainbowVisitor { + @Override + public boolean suitableForFile(@NotNull PsiFile file) { + return file instanceof NixFile; + } + + @Override + public void visit(@NotNull PsiElement element) { + if (element instanceof NixExprSelect) { + NixExprSelect expr = (NixExprSelect) element; + NixExpr value = expr.getValue(); + if (!(value instanceof NixIdentifier)) { + return; + } + String identifier = value.getText(); + PsiElement source = findSource(element, identifier); + highlight(value, source, identifier); + + NixAttrPath attrPath = expr.getAttrPath(); + if (attrPath != null) { + String pathStr = identifier; + for (NixAttr nixAttr : expr.getAttrPath().getAttrList()) { + pathStr = pathStr + '.' + nixAttr.getText(); + highlight(nixAttr, source, pathStr); + } + } + } else { + iterateVariables(element, true, (var, path) -> { + highlight(var, element, path); + return false; + }); + } + } + + private static @Nullable PsiElement findSource(@NotNull PsiElement context, @NotNull String identifier) { + do { + if (iterateVariables(context, false, (var, __) -> var.textMatches(identifier))) { + return context; + } else { + context = context.getParent(); + } + } while (context != null); + return null; + } + + private static boolean iterateVariables(@NotNull PsiElement element, boolean fullPath, @NotNull BiPredicate action) { + if (element instanceof NixExprLet) { + NixExprLet let = (NixExprLet) element; + return iterateVariables(let.getBindList(), fullPath, action); + } else if (element instanceof NixLegacyLet) { + NixLegacyLet let = (NixLegacyLet) element; + return iterateVariables(let.getBindList(), fullPath, action); + } else if (element instanceof NixSet) { + NixSet set = (NixSet) element; + return set.getNode().findChildByType(NixTypes.REC) != null && + iterateVariables(set.getBindList(), fullPath, action); + } else if (element instanceof NixExprLambda) { + NixExprLambda lambda = (NixExprLambda) element; + ASTNode mainParam = lambda.getNode().findChildByType(NixTypes.ID); + if (mainParam != null && action.test(mainParam.getPsi(), fullPath ? mainParam.getText() : null)) { + return true; + } + NixParamSet paramSet = lambda.getParamSet(); + if (paramSet != null) { + for (NixParam param : paramSet.getParamList()) { + ASTNode paramName = param.getNode().findChildByType(NixTypes.ID); + if (paramName != null && action.test(paramName.getPsi(), fullPath ? paramName.getText() : null)) { + return true; + } + } + } + } + return false; + } + + private static boolean iterateVariables(@NotNull List bindList, boolean fullPath, @NotNull BiPredicate action) { + for (NixBind bind : bindList) { + if (bind instanceof NixBindAttr) { + NixBindAttr bindAttr = (NixBindAttr) bind; + if (fullPath) { + List attrs = bindAttr.getAttrPath().getAttrList(); + NixAttr first = attrs.get(0); + String pathStr = first.getText(); + if (action.test(first, pathStr)) { + return true; + } + for (NixAttr attr : attrs.subList(1, attrs.size())) { + pathStr = pathStr + '.' + attr.getText(); + if (action.test(attr, pathStr)) { + return true; + } + } + } else { + if (action.test(bindAttr.getAttrPath().getFirstAttr(), null)) { + return true; + } + } + } else if (bind instanceof NixBindInherit) { + for (NixAttr attr : ((NixBindInherit) bind).getAttrList()) { + if (action.test(attr, fullPath ? attr.getText() : null)) { + return true; + } + } + } else { + throw new IllegalStateException("Unexpected NixBind implementation: " + bind.getClass()); + } + } + return false; + } + + private static @NotNull TextAttributesKey getAttributesBySource(@NotNull PsiElement source) { + if (source instanceof NixExprLet || + source instanceof NixLegacyLet || + source instanceof NixSet) { + return NixSyntaxHighlighter.LOCAL_VARIABLE; + } else if (source instanceof NixExprLambda) { + return NixSyntaxHighlighter.PARAMETER; + } else { + throw new IllegalArgumentException("Invalid source: " + source); + } + } + + private void highlight(@Nullable PsiElement element, @Nullable PsiElement source, @NotNull String attrPath) { + if (element != null && source != null) { + TextAttributesKey attributesKey = attrPath.contains(".") ? NixSyntaxHighlighter.IDENTIFIER : getAttributesBySource(source); + addInfo(getInfo(source, element, attrPath, attributesKey)); + } + } + + @Override + public @NotNull HighlightVisitor clone() { + return new NixRainbowVisitor(); + } +} diff --git a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java index 7ac183a5..adc63547 100644 --- a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java +++ b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java @@ -19,8 +19,6 @@ public class NixSyntaxHighlighter extends SyntaxHighlighterBase { public static final TextAttributesKey KEYWORD = createTextAttributesKey("NIX_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD); - public static final TextAttributesKey IDENTIFIER = - createTextAttributesKey("NIX_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER); public static final TextAttributesKey SEMICOLON = createTextAttributesKey("NIX_SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON); @@ -46,6 +44,13 @@ public class NixSyntaxHighlighter extends SyntaxHighlighterBase { public static final TextAttributesKey BRACKETS = createTextAttributesKey("NIX_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS); + public static final TextAttributesKey IDENTIFIER = + createTextAttributesKey("NIX_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER); + public static final TextAttributesKey LOCAL_VARIABLE = + createTextAttributesKey("NIX_LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE); + public static final TextAttributesKey PARAMETER = + createTextAttributesKey("NIX_PARAMETER", DefaultLanguageHighlighterColors.PARAMETER); + public static final TextAttributesKey STRING = createTextAttributesKey("NIX_STRING", DefaultLanguageHighlighterColors.STRING); public static final TextAttributesKey STRING_ESCAPE = diff --git a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java index e3562551..b266773c 100644 --- a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java +++ b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java @@ -1,24 +1,25 @@ package org.nixos.idea.settings; +import com.intellij.lang.Language; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.options.colors.AttributesDescriptor; import com.intellij.openapi.options.colors.ColorDescriptor; -import com.intellij.openapi.options.colors.ColorSettingsPage; +import com.intellij.openapi.options.colors.RainbowColorSettingsPage; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.nixos.idea.icon.NixIcons; +import org.nixos.idea.lang.NixLanguage; import org.nixos.idea.lang.NixSyntaxHighlighter; import javax.swing.Icon; import java.util.Map; -public final class NixColorSettingsPage implements ColorSettingsPage { +public final class NixColorSettingsPage implements RainbowColorSettingsPage { private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{ descriptor("Keyword", NixSyntaxHighlighter.KEYWORD), - descriptor("Identifier", NixSyntaxHighlighter.IDENTIFIER), descriptor("Operators//Semicolon", NixSyntaxHighlighter.SEMICOLON), descriptor("Operators//Comma", NixSyntaxHighlighter.COMMA), descriptor("Operators//Dot", NixSyntaxHighlighter.DOT), @@ -30,6 +31,9 @@ public final class NixColorSettingsPage implements ColorSettingsPage { descriptor("Braces//Parentheses", NixSyntaxHighlighter.PARENTHESES), descriptor("Braces//Curly braces", NixSyntaxHighlighter.BRACES), descriptor("Braces//Brackets", NixSyntaxHighlighter.BRACKETS), + descriptor("Variables and Attributes//Other identifier", NixSyntaxHighlighter.IDENTIFIER), + descriptor("Variables and Attributes//Local variable", NixSyntaxHighlighter.LOCAL_VARIABLE), + descriptor("Variables and Attributes//Function parameter", NixSyntaxHighlighter.PARAMETER), descriptor("Literals and Values//Number", NixSyntaxHighlighter.NUMBER), descriptor("Literals and Values//String", NixSyntaxHighlighter.STRING), descriptor("Literals and Values//Escape sequence", NixSyntaxHighlighter.STRING_ESCAPE), @@ -39,6 +43,16 @@ public final class NixColorSettingsPage implements ColorSettingsPage { descriptor("Comments//Block comment", NixSyntaxHighlighter.BLOCK_COMMENT), }; + @Override + public @Nullable Language getLanguage() { + return NixLanguage.INSTANCE; + } + + @Override + public boolean isRainbowType(TextAttributesKey type) { + return type.equals(NixSyntaxHighlighter.LOCAL_VARIABLE) || type.equals(NixSyntaxHighlighter.PARAMETER); + } + @Override public @NotNull Icon getIcon() { return NixIcons.FILE; diff --git a/src/main/lang/Nix.bnf b/src/main/lang/Nix.bnf index 9700e112..cb0b98c8 100644 --- a/src/main/lang/Nix.bnf +++ b/src/main/lang/Nix.bnf @@ -151,6 +151,7 @@ expr_op_base ::= expr_app // one AST node for a series of function calls. expr_app ::= expr_select ( !missing_semi expr_select ) * +;{ methods("expr_select")=[ value="/expr[0]" default="/expr[1]" ] } expr_select ::= expr_simple [ !missing_semi ( select_attr | legacy_app_or )] private select_attr ::= DOT attr_path [ select_default ] { pin=1 } private select_default ::= OR_KW expr_select { pin=1 } @@ -205,7 +206,7 @@ attr ::= std_attr | string_attr std_attr ::= ID | OR_KW string_attr ::= std_string | antiquotation -attr_path ::= attr ( DOT attr )* +attr_path ::= attr ( DOT attr )* { methods=[ firstAttr="/attr[0]" ] } // The lexer uses curly braces to determine its state. To avoid inconsistencies diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 76aec18f..f012f85b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -23,6 +23,8 @@ language="Nix" implementationClass="org.nixos.idea.lang.NixSyntaxHighlighterFactory" /> + + From ca27ea857a317961d67fcb7f5f81add8221f7390 Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Mon, 6 Feb 2023 23:48:50 +0100 Subject: [PATCH 04/11] Move TextAttributesKey constants to separate class --- .../nixos/idea/lang/NixSyntaxHighlighter.java | 147 ------------------ .../{ => highlighter}/NixRainbowVisitor.java | 12 +- .../highlighter/NixSyntaxHighlighter.java | 96 ++++++++++++ .../NixSyntaxHighlighterFactory.java | 2 +- .../lang/highlighter/NixTextAttributes.java | 63 ++++++++ .../idea/settings/NixColorSettingsPage.java | 52 ++++--- src/main/resources/META-INF/plugin.xml | 4 +- .../NixSyntaxHighlighterTest.java | 32 +--- .../highlighter/NixTextAttributesTest.java | 43 +++++ .../settings/NixColorSettingsPageTest.java | 4 +- 10 files changed, 243 insertions(+), 212 deletions(-) delete mode 100644 src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java rename src/main/java/org/nixos/idea/lang/{ => highlighter}/NixRainbowVisitor.java (94%) create mode 100644 src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighter.java rename src/main/java/org/nixos/idea/lang/{ => highlighter}/NixSyntaxHighlighterFactory.java (92%) create mode 100644 src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java rename src/test/java/org/nixos/idea/lang/{ => highlighter}/NixSyntaxHighlighterTest.java (60%) create mode 100644 src/test/java/org/nixos/idea/lang/highlighter/NixTextAttributesTest.java diff --git a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java b/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java deleted file mode 100644 index adc63547..00000000 --- a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighter.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.nixos.idea.lang; - -import com.intellij.lexer.Lexer; -import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; -import com.intellij.openapi.editor.HighlighterColors; -import com.intellij.openapi.editor.colors.TextAttributesKey; -import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; -import com.intellij.psi.TokenType; -import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.NotNull; -import org.nixos.idea.psi.NixTypes; - -import java.util.Map; - -import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey; -import static java.util.Map.entry; - -public class NixSyntaxHighlighter extends SyntaxHighlighterBase { - - public static final TextAttributesKey KEYWORD = - createTextAttributesKey("NIX_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD); - - public static final TextAttributesKey SEMICOLON = - createTextAttributesKey("NIX_SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON); - public static final TextAttributesKey COMMA = - createTextAttributesKey("NIX_COMMA", DefaultLanguageHighlighterColors.COMMA); - public static final TextAttributesKey DOT = - createTextAttributesKey("NIX_DOT", DefaultLanguageHighlighterColors.DOT); - public static final TextAttributesKey ASSIGN = - createTextAttributesKey("NIX_ASSIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey COLON = - createTextAttributesKey("NIX_COLON", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey AT = - createTextAttributesKey("NIX_AT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey ELLIPSIS = - createTextAttributesKey("NIX_ELLIPSIS", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey OPERATION_SIGN = - createTextAttributesKey("NIX_OPERATION_SIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN); - - public static final TextAttributesKey PARENTHESES = - createTextAttributesKey("NIX_PARENTHESES", DefaultLanguageHighlighterColors.PARENTHESES); - public static final TextAttributesKey BRACES = - createTextAttributesKey("NIX_BRACES", DefaultLanguageHighlighterColors.BRACES); - public static final TextAttributesKey BRACKETS = - createTextAttributesKey("NIX_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS); - - public static final TextAttributesKey IDENTIFIER = - createTextAttributesKey("NIX_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER); - public static final TextAttributesKey LOCAL_VARIABLE = - createTextAttributesKey("NIX_LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE); - public static final TextAttributesKey PARAMETER = - createTextAttributesKey("NIX_PARAMETER", DefaultLanguageHighlighterColors.PARAMETER); - - public static final TextAttributesKey STRING = - createTextAttributesKey("NIX_STRING", DefaultLanguageHighlighterColors.STRING); - public static final TextAttributesKey STRING_ESCAPE = - createTextAttributesKey("NIX_STRING_ESCAPE", DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE); - public static final TextAttributesKey URI = - createTextAttributesKey("NIX_URI", DefaultLanguageHighlighterColors.STRING); - public static final TextAttributesKey PATH = - createTextAttributesKey("NIX_PATH", DefaultLanguageHighlighterColors.STRING); - public static final TextAttributesKey NUMBER = - createTextAttributesKey("NIX_NUMBER", DefaultLanguageHighlighterColors.NUMBER); - - public static final TextAttributesKey LINE_COMMENT = - createTextAttributesKey("NIX_LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); - public static final TextAttributesKey BLOCK_COMMENT = - createTextAttributesKey("NIX_BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT); - - private static final Map TOKEN_MAP = Map.ofEntries( - // Keywords - entry(NixTypes.IF, KEYWORD), - entry(NixTypes.THEN, KEYWORD), - entry(NixTypes.ELSE, KEYWORD), - entry(NixTypes.ASSERT, KEYWORD), - entry(NixTypes.WITH, KEYWORD), - entry(NixTypes.LET, KEYWORD), - entry(NixTypes.IN, KEYWORD), - entry(NixTypes.REC, KEYWORD), - entry(NixTypes.INHERIT, KEYWORD), - entry(NixTypes.OR_KW, KEYWORD), - // Identifiers - entry(NixTypes.ID, IDENTIFIER), - // Operators - entry(NixTypes.ASSIGN, ASSIGN), - entry(NixTypes.COLON, COLON), - entry(NixTypes.SEMI, SEMICOLON), - entry(NixTypes.COMMA, COMMA), - entry(NixTypes.DOT, DOT), - entry(NixTypes.ELLIPSIS, ELLIPSIS), - entry(NixTypes.AT, AT), - entry(NixTypes.HAS, OPERATION_SIGN), - entry(NixTypes.NOT, OPERATION_SIGN), - entry(NixTypes.TIMES, OPERATION_SIGN), - entry(NixTypes.DIVIDE, OPERATION_SIGN), - entry(NixTypes.PLUS, OPERATION_SIGN), - entry(NixTypes.MINUS, OPERATION_SIGN), - entry(NixTypes.LT, OPERATION_SIGN), - entry(NixTypes.GT, OPERATION_SIGN), - entry(NixTypes.CONCAT, OPERATION_SIGN), - entry(NixTypes.UPDATE, OPERATION_SIGN), - entry(NixTypes.LEQ, OPERATION_SIGN), - entry(NixTypes.GEQ, OPERATION_SIGN), - entry(NixTypes.EQ, OPERATION_SIGN), - entry(NixTypes.NEQ, OPERATION_SIGN), - entry(NixTypes.AND, OPERATION_SIGN), - entry(NixTypes.OR, OPERATION_SIGN), - entry(NixTypes.IMPL, OPERATION_SIGN), - // Parentheses - entry(NixTypes.LPAREN, PARENTHESES), - entry(NixTypes.RPAREN, PARENTHESES), - entry(NixTypes.LBRAC, BRACKETS), - entry(NixTypes.RBRAC, BRACKETS), - entry(NixTypes.LCURLY, BRACES), - entry(NixTypes.RCURLY, BRACES), - entry(NixTypes.DOLLAR, BRACES), - // Literals - entry(NixTypes.INT, NUMBER), - entry(NixTypes.FLOAT, NUMBER), - entry(NixTypes.PATH, PATH), - entry(NixTypes.HPATH, PATH), - entry(NixTypes.SPATH, PATH), - entry(NixTypes.URI, URI), - // String literals - entry(NixTypes.STR, STRING), - entry(NixTypes.STRING_CLOSE, STRING), - entry(NixTypes.STRING_OPEN, STRING), - entry(NixTypes.IND_STR, STRING), - entry(NixTypes.IND_STRING_CLOSE, STRING), - entry(NixTypes.IND_STRING_OPEN, STRING), - entry(NixTypes.STR_ESCAPE, STRING_ESCAPE), - entry(NixTypes.IND_STR_ESCAPE, STRING_ESCAPE), - // Other - entry(NixTypes.SCOMMENT, LINE_COMMENT), - entry(NixTypes.MCOMMENT, BLOCK_COMMENT), - entry(TokenType.BAD_CHARACTER, HighlighterColors.BAD_CHARACTER)); - - @Override - public @NotNull Lexer getHighlightingLexer() { - return new NixLexer(); - } - - @Override - public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) { - return pack(TOKEN_MAP.get(tokenType)); - } -} diff --git a/src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java b/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java similarity index 94% rename from src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java rename to src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java index 64e92cf1..dbbb9dc2 100644 --- a/src/main/java/org/nixos/idea/lang/NixRainbowVisitor.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java @@ -1,4 +1,4 @@ -package org.nixos.idea.lang; +package org.nixos.idea.lang.highlighter; import com.intellij.codeInsight.daemon.RainbowVisitor; import com.intellij.codeInsight.daemon.impl.HighlightVisitor; @@ -29,6 +29,10 @@ import java.util.function.BiPredicate; public final class NixRainbowVisitor extends RainbowVisitor { + public static final List RAINBOW_ATTRIBUTES = List.of( + NixTextAttributes.LOCAL_VARIABLE, + NixTextAttributes.PARAMETER); + @Override public boolean suitableForFile(@NotNull PsiFile file) { return file instanceof NixFile; @@ -142,9 +146,9 @@ private static boolean iterateVariables(@NotNull List bindList, boolean if (source instanceof NixExprLet || source instanceof NixLegacyLet || source instanceof NixSet) { - return NixSyntaxHighlighter.LOCAL_VARIABLE; + return NixTextAttributes.LOCAL_VARIABLE; } else if (source instanceof NixExprLambda) { - return NixSyntaxHighlighter.PARAMETER; + return NixTextAttributes.PARAMETER; } else { throw new IllegalArgumentException("Invalid source: " + source); } @@ -152,7 +156,7 @@ private static boolean iterateVariables(@NotNull List bindList, boolean private void highlight(@Nullable PsiElement element, @Nullable PsiElement source, @NotNull String attrPath) { if (element != null && source != null) { - TextAttributesKey attributesKey = attrPath.contains(".") ? NixSyntaxHighlighter.IDENTIFIER : getAttributesBySource(source); + TextAttributesKey attributesKey = attrPath.contains(".") ? NixTextAttributes.IDENTIFIER : getAttributesBySource(source); addInfo(getInfo(source, element, attrPath, attributesKey)); } } diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighter.java b/src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighter.java new file mode 100644 index 00000000..f6ab155c --- /dev/null +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighter.java @@ -0,0 +1,96 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.HighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; +import com.intellij.psi.TokenType; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; +import org.nixos.idea.lang.NixLexer; +import org.nixos.idea.psi.NixTypes; + +import java.util.Map; + +import static java.util.Map.entry; + +public class NixSyntaxHighlighter extends SyntaxHighlighterBase { + + private static final Map TOKEN_MAP = Map.ofEntries( + // Keywords + entry(NixTypes.IF, NixTextAttributes.KEYWORD), + entry(NixTypes.THEN, NixTextAttributes.KEYWORD), + entry(NixTypes.ELSE, NixTextAttributes.KEYWORD), + entry(NixTypes.ASSERT, NixTextAttributes.KEYWORD), + entry(NixTypes.WITH, NixTextAttributes.KEYWORD), + entry(NixTypes.LET, NixTextAttributes.KEYWORD), + entry(NixTypes.IN, NixTextAttributes.KEYWORD), + entry(NixTypes.REC, NixTextAttributes.KEYWORD), + entry(NixTypes.INHERIT, NixTextAttributes.KEYWORD), + entry(NixTypes.OR_KW, NixTextAttributes.KEYWORD), + // Identifiers + entry(NixTypes.ID, NixTextAttributes.IDENTIFIER), + // Operators + entry(NixTypes.ASSIGN, NixTextAttributes.ASSIGN), + entry(NixTypes.COLON, NixTextAttributes.COLON), + entry(NixTypes.SEMI, NixTextAttributes.SEMICOLON), + entry(NixTypes.COMMA, NixTextAttributes.COMMA), + entry(NixTypes.DOT, NixTextAttributes.DOT), + entry(NixTypes.ELLIPSIS, NixTextAttributes.ELLIPSIS), + entry(NixTypes.AT, NixTextAttributes.AT), + entry(NixTypes.HAS, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.NOT, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.TIMES, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.DIVIDE, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.PLUS, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.MINUS, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.LT, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.GT, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.CONCAT, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.UPDATE, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.LEQ, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.GEQ, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.EQ, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.NEQ, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.AND, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.OR, NixTextAttributes.OPERATION_SIGN), + entry(NixTypes.IMPL, NixTextAttributes.OPERATION_SIGN), + // Parentheses + entry(NixTypes.LPAREN, NixTextAttributes.PARENTHESES), + entry(NixTypes.RPAREN, NixTextAttributes.PARENTHESES), + entry(NixTypes.LBRAC, NixTextAttributes.BRACKETS), + entry(NixTypes.RBRAC, NixTextAttributes.BRACKETS), + entry(NixTypes.LCURLY, NixTextAttributes.BRACES), + entry(NixTypes.RCURLY, NixTextAttributes.BRACES), + entry(NixTypes.DOLLAR, NixTextAttributes.BRACES), + // Literals + entry(NixTypes.INT, NixTextAttributes.NUMBER), + entry(NixTypes.FLOAT, NixTextAttributes.NUMBER), + entry(NixTypes.PATH, NixTextAttributes.PATH), + entry(NixTypes.HPATH, NixTextAttributes.PATH), + entry(NixTypes.SPATH, NixTextAttributes.PATH), + entry(NixTypes.URI, NixTextAttributes.URI), + // String literals + entry(NixTypes.STR, NixTextAttributes.STRING), + entry(NixTypes.STRING_CLOSE, NixTextAttributes.STRING), + entry(NixTypes.STRING_OPEN, NixTextAttributes.STRING), + entry(NixTypes.IND_STR, NixTextAttributes.STRING), + entry(NixTypes.IND_STRING_CLOSE, NixTextAttributes.STRING), + entry(NixTypes.IND_STRING_OPEN, NixTextAttributes.STRING), + entry(NixTypes.STR_ESCAPE, NixTextAttributes.STRING_ESCAPE), + entry(NixTypes.IND_STR_ESCAPE, NixTextAttributes.STRING_ESCAPE), + // Other + entry(NixTypes.SCOMMENT, NixTextAttributes.LINE_COMMENT), + entry(NixTypes.MCOMMENT, NixTextAttributes.BLOCK_COMMENT), + entry(TokenType.BAD_CHARACTER, HighlighterColors.BAD_CHARACTER)); + + @Override + public @NotNull Lexer getHighlightingLexer() { + return new NixLexer(); + } + + @Override + public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) { + return pack(TOKEN_MAP.get(tokenType)); + } +} diff --git a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java b/src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighterFactory.java similarity index 92% rename from src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java rename to src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighterFactory.java index 010eee84..8723f681 100644 --- a/src/main/java/org/nixos/idea/lang/NixSyntaxHighlighterFactory.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighterFactory.java @@ -1,4 +1,4 @@ -package org.nixos.idea.lang; +package org.nixos.idea.lang.highlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java b/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java new file mode 100644 index 00000000..53c25a4c --- /dev/null +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java @@ -0,0 +1,63 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; + +import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey; + +public final class NixTextAttributes { + + public static final TextAttributesKey KEYWORD = + createTextAttributesKey("NIX_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD); + + public static final TextAttributesKey SEMICOLON = + createTextAttributesKey("NIX_SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON); + public static final TextAttributesKey COMMA = + createTextAttributesKey("NIX_COMMA", DefaultLanguageHighlighterColors.COMMA); + public static final TextAttributesKey DOT = + createTextAttributesKey("NIX_DOT", DefaultLanguageHighlighterColors.DOT); + public static final TextAttributesKey ASSIGN = + createTextAttributesKey("NIX_ASSIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey COLON = + createTextAttributesKey("NIX_COLON", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey AT = + createTextAttributesKey("NIX_AT", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey ELLIPSIS = + createTextAttributesKey("NIX_ELLIPSIS", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey OPERATION_SIGN = + createTextAttributesKey("NIX_OPERATION_SIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN); + + public static final TextAttributesKey PARENTHESES = + createTextAttributesKey("NIX_PARENTHESES", DefaultLanguageHighlighterColors.PARENTHESES); + public static final TextAttributesKey BRACES = + createTextAttributesKey("NIX_BRACES", DefaultLanguageHighlighterColors.BRACES); + public static final TextAttributesKey BRACKETS = + createTextAttributesKey("NIX_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS); + + public static final TextAttributesKey IDENTIFIER = + createTextAttributesKey("NIX_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER); + public static final TextAttributesKey LOCAL_VARIABLE = + createTextAttributesKey("NIX_LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE); + public static final TextAttributesKey PARAMETER = + createTextAttributesKey("NIX_PARAMETER", DefaultLanguageHighlighterColors.PARAMETER); + + public static final TextAttributesKey STRING = + createTextAttributesKey("NIX_STRING", DefaultLanguageHighlighterColors.STRING); + public static final TextAttributesKey STRING_ESCAPE = + createTextAttributesKey("NIX_STRING_ESCAPE", DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE); + public static final TextAttributesKey URI = + createTextAttributesKey("NIX_URI", DefaultLanguageHighlighterColors.STRING); + public static final TextAttributesKey PATH = + createTextAttributesKey("NIX_PATH", DefaultLanguageHighlighterColors.STRING); + public static final TextAttributesKey NUMBER = + createTextAttributesKey("NIX_NUMBER", DefaultLanguageHighlighterColors.NUMBER); + + public static final TextAttributesKey LINE_COMMENT = + createTextAttributesKey("NIX_LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); + public static final TextAttributesKey BLOCK_COMMENT = + createTextAttributesKey("NIX_BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT); + + private NixTextAttributes() { + // Cannot be instantiated + } +} diff --git a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java index b266773c..a438715f 100644 --- a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java +++ b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java @@ -12,45 +12,47 @@ import org.jetbrains.annotations.Nullable; import org.nixos.idea.icon.NixIcons; import org.nixos.idea.lang.NixLanguage; -import org.nixos.idea.lang.NixSyntaxHighlighter; +import org.nixos.idea.lang.highlighter.NixRainbowVisitor; +import org.nixos.idea.lang.highlighter.NixSyntaxHighlighter; +import org.nixos.idea.lang.highlighter.NixTextAttributes; import javax.swing.Icon; import java.util.Map; public final class NixColorSettingsPage implements RainbowColorSettingsPage { private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{ - descriptor("Keyword", NixSyntaxHighlighter.KEYWORD), - descriptor("Operators//Semicolon", NixSyntaxHighlighter.SEMICOLON), - descriptor("Operators//Comma", NixSyntaxHighlighter.COMMA), - descriptor("Operators//Dot", NixSyntaxHighlighter.DOT), - descriptor("Operators//Assignment operator", NixSyntaxHighlighter.ASSIGN), - descriptor("Operators//Colon", NixSyntaxHighlighter.COLON), - descriptor("Operators//At sign (@)", NixSyntaxHighlighter.AT), - descriptor("Operators//Ellipsis", NixSyntaxHighlighter.ELLIPSIS), - descriptor("Operators//Other operators", NixSyntaxHighlighter.OPERATION_SIGN), - descriptor("Braces//Parentheses", NixSyntaxHighlighter.PARENTHESES), - descriptor("Braces//Curly braces", NixSyntaxHighlighter.BRACES), - descriptor("Braces//Brackets", NixSyntaxHighlighter.BRACKETS), - descriptor("Variables and Attributes//Other identifier", NixSyntaxHighlighter.IDENTIFIER), - descriptor("Variables and Attributes//Local variable", NixSyntaxHighlighter.LOCAL_VARIABLE), - descriptor("Variables and Attributes//Function parameter", NixSyntaxHighlighter.PARAMETER), - descriptor("Literals and Values//Number", NixSyntaxHighlighter.NUMBER), - descriptor("Literals and Values//String", NixSyntaxHighlighter.STRING), - descriptor("Literals and Values//Escape sequence", NixSyntaxHighlighter.STRING_ESCAPE), - descriptor("Literals and Values//Path", NixSyntaxHighlighter.PATH), - descriptor("Literals and Values//URI", NixSyntaxHighlighter.URI), - descriptor("Comments//Line comment", NixSyntaxHighlighter.LINE_COMMENT), - descriptor("Comments//Block comment", NixSyntaxHighlighter.BLOCK_COMMENT), + descriptor("Keyword", NixTextAttributes.KEYWORD), + descriptor("Operators//Semicolon", NixTextAttributes.SEMICOLON), + descriptor("Operators//Comma", NixTextAttributes.COMMA), + descriptor("Operators//Dot", NixTextAttributes.DOT), + descriptor("Operators//Assignment operator", NixTextAttributes.ASSIGN), + descriptor("Operators//Colon", NixTextAttributes.COLON), + descriptor("Operators//At sign (@)", NixTextAttributes.AT), + descriptor("Operators//Ellipsis", NixTextAttributes.ELLIPSIS), + descriptor("Operators//Other operators", NixTextAttributes.OPERATION_SIGN), + descriptor("Braces//Parentheses", NixTextAttributes.PARENTHESES), + descriptor("Braces//Curly braces", NixTextAttributes.BRACES), + descriptor("Braces//Brackets", NixTextAttributes.BRACKETS), + descriptor("Variables and Attributes//Other identifier", NixTextAttributes.IDENTIFIER), + descriptor("Variables and Attributes//Local variable", NixTextAttributes.LOCAL_VARIABLE), + descriptor("Variables and Attributes//Function parameter", NixTextAttributes.PARAMETER), + descriptor("Literals and Values//Number", NixTextAttributes.NUMBER), + descriptor("Literals and Values//String", NixTextAttributes.STRING), + descriptor("Literals and Values//Escape sequence", NixTextAttributes.STRING_ESCAPE), + descriptor("Literals and Values//Path", NixTextAttributes.PATH), + descriptor("Literals and Values//URI", NixTextAttributes.URI), + descriptor("Comments//Line comment", NixTextAttributes.LINE_COMMENT), + descriptor("Comments//Block comment", NixTextAttributes.BLOCK_COMMENT), }; @Override - public @Nullable Language getLanguage() { + public @NotNull Language getLanguage() { return NixLanguage.INSTANCE; } @Override public boolean isRainbowType(TextAttributesKey type) { - return type.equals(NixSyntaxHighlighter.LOCAL_VARIABLE) || type.equals(NixSyntaxHighlighter.PARAMETER); + return NixRainbowVisitor.RAINBOW_ATTRIBUTES.contains(type); } @Override diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f012f85b..f885e521 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -21,9 +21,9 @@ + implementationClass="org.nixos.idea.lang.highlighter.NixSyntaxHighlighterFactory" /> - + > testKeyNamesHaveNixPrefix() { - return ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class); - } - - @ParameterizedTest - @MethodSource - void testNoDuplicateKeyNames(@NotNull TextAttributesKey key) { - Set duplicates = ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class) - .filter(other -> other.getPayload().getExternalName().equals(key.getExternalName())) - .map(Named::getName) - .collect(Collectors.toSet()); - if (duplicates.size() != 1) { - fail("Duplicates: " + duplicates); - } - } - - static @NotNull Stream> testNoDuplicateKeyNames() { - return ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class); - } } diff --git a/src/test/java/org/nixos/idea/lang/highlighter/NixTextAttributesTest.java b/src/test/java/org/nixos/idea/lang/highlighter/NixTextAttributesTest.java new file mode 100644 index 00000000..511ae155 --- /dev/null +++ b/src/test/java/org/nixos/idea/lang/highlighter/NixTextAttributesTest.java @@ -0,0 +1,43 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.nixos.idea._testutil.ReflectionUtils; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +final class NixTextAttributesTest { + @ParameterizedTest + @MethodSource + void testKeyNamesHaveNixPrefix(@NotNull TextAttributesKey key) { + assertTrue(key.getExternalName().startsWith("NIX_")); + } + + static @NotNull Stream> testKeyNamesHaveNixPrefix() { + return ReflectionUtils.getPublicStaticFieldValues(NixTextAttributes.class, TextAttributesKey.class); + } + + @ParameterizedTest + @MethodSource + void testNoDuplicateKeyNames(@NotNull TextAttributesKey key) { + Set duplicates = ReflectionUtils.getPublicStaticFieldValues(NixTextAttributes.class, TextAttributesKey.class) + .filter(other -> other.getPayload().getExternalName().equals(key.getExternalName())) + .map(Named::getName) + .collect(Collectors.toSet()); + if (duplicates.size() != 1) { + fail("Duplicates: " + duplicates); + } + } + + static @NotNull Stream> testNoDuplicateKeyNames() { + return ReflectionUtils.getPublicStaticFieldValues(NixTextAttributes.class, TextAttributesKey.class); + } +} diff --git a/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java b/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java index 33413910..5dea78dd 100644 --- a/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java +++ b/src/test/java/org/nixos/idea/settings/NixColorSettingsPageTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.nixos.idea._testutil.ReflectionUtils; -import org.nixos.idea.lang.NixSyntaxHighlighter; +import org.nixos.idea.lang.highlighter.NixTextAttributes; import java.util.Arrays; import java.util.stream.Stream; @@ -25,6 +25,6 @@ void testNoKeyMissing(@NotNull TextAttributesKey textAttributesKey) { } static @NotNull Stream> testNoKeyMissing() { - return ReflectionUtils.getPublicStaticFieldValues(NixSyntaxHighlighter.class, TextAttributesKey.class); + return ReflectionUtils.getPublicStaticFieldValues(NixTextAttributes.class, TextAttributesKey.class); } } From d3a008bbb44673494d278bdd2016207bbdc51ab1 Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Tue, 7 Feb 2023 20:34:47 +0100 Subject: [PATCH 05/11] Distinguish highlighting for local variables and parameters --- .../lang/highlighter/NixHighlightVisitor.java | 56 ++++++ .../NixHighlightVisitorDelegate.java | 164 ++++++++++++++++++ .../lang/highlighter/NixRainbowVisitor.java | 154 +++------------- src/main/resources/META-INF/plugin.xml | 1 + 4 files changed, 241 insertions(+), 134 deletions(-) create mode 100644 src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java create mode 100644 src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java new file mode 100644 index 00000000..f51f2c58 --- /dev/null +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java @@ -0,0 +1,56 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.HighlightVisitor; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class NixHighlightVisitor implements HighlightVisitor { + + private HighlightInfoHolder myHolder; + private Delegate myDelegate; + + @Override + public boolean suitableForFile(@NotNull PsiFile file) { + return NixHighlightVisitorDelegate.suitableForFile(file); + } + + @Override + public void visit(@NotNull PsiElement element) { + myDelegate.visit(element); + } + + @Override + public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action) { + try { + myHolder = holder; + myDelegate = new Delegate(); + action.run(); + } finally { + myHolder = null; + myDelegate = null; + } + return true; + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public @NotNull HighlightVisitor clone() { + return new NixHighlightVisitor(); + } + + private final class Delegate extends NixHighlightVisitorDelegate { + @Override + void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type) { + if (type != null) { + myHolder.add(HighlightInfo.newHighlightInfo(type) + .range(element) + .create()); + } + } + } +} diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java new file mode 100644 index 00000000..5e398196 --- /dev/null +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java @@ -0,0 +1,164 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.nixos.idea.file.NixFile; +import org.nixos.idea.psi.NixAttr; +import org.nixos.idea.psi.NixAttrPath; +import org.nixos.idea.psi.NixBind; +import org.nixos.idea.psi.NixBindAttr; +import org.nixos.idea.psi.NixBindInherit; +import org.nixos.idea.psi.NixExpr; +import org.nixos.idea.psi.NixExprLambda; +import org.nixos.idea.psi.NixExprLet; +import org.nixos.idea.psi.NixExprSelect; +import org.nixos.idea.psi.NixIdentifier; +import org.nixos.idea.psi.NixLegacyLet; +import org.nixos.idea.psi.NixParam; +import org.nixos.idea.psi.NixParamSet; +import org.nixos.idea.psi.NixSet; +import org.nixos.idea.psi.NixTypes; + +import java.util.List; +import java.util.function.BiPredicate; + +/** + * Delegate for the highlighting logic used by {@link NixHighlightVisitor} and {@link NixRainbowVisitor}. + */ +abstract class NixHighlightVisitorDelegate { + + private static final HighlightInfoType LOCAL_VARIABLE = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.LOCAL_VARIABLE, false); + private static final HighlightInfoType PARAMETER = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.PARAMETER, false); + + static boolean suitableForFile(@NotNull PsiFile file) { + return file instanceof NixFile; + } + + abstract void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type); + + void visit(@NotNull PsiElement element) { + if (element instanceof NixExprSelect) { + NixExprSelect expr = (NixExprSelect) element; + NixExpr value = expr.getValue(); + if (!(value instanceof NixIdentifier)) { + return; + } + String identifier = value.getText(); + PsiElement source = findSource(element, identifier); + highlight(value, source, identifier); + + NixAttrPath attrPath = expr.getAttrPath(); + if (attrPath != null) { + String pathStr = identifier; + for (NixAttr nixAttr : expr.getAttrPath().getAttrList()) { + pathStr = pathStr + '.' + nixAttr.getText(); + highlight(nixAttr, source, pathStr); + } + } + } else { + iterateVariables(element, true, (var, path) -> { + highlight(var, element, path); + return false; + }); + } + } + + private static @Nullable PsiElement findSource(@NotNull PsiElement context, @NotNull String identifier) { + do { + if (iterateVariables(context, false, (var, __) -> var.textMatches(identifier))) { + return context; + } else { + context = context.getParent(); + } + } while (context != null); + return null; + } + + private static boolean iterateVariables(@NotNull PsiElement element, boolean fullPath, @NotNull BiPredicate action) { + if (element instanceof NixExprLet) { + NixExprLet let = (NixExprLet) element; + return iterateVariables(let.getBindList(), fullPath, action); + } else if (element instanceof NixLegacyLet) { + NixLegacyLet let = (NixLegacyLet) element; + return iterateVariables(let.getBindList(), fullPath, action); + } else if (element instanceof NixSet) { + NixSet set = (NixSet) element; + return set.getNode().findChildByType(NixTypes.REC) != null && + iterateVariables(set.getBindList(), fullPath, action); + } else if (element instanceof NixExprLambda) { + NixExprLambda lambda = (NixExprLambda) element; + ASTNode mainParam = lambda.getNode().findChildByType(NixTypes.ID); + if (mainParam != null && action.test(mainParam.getPsi(), fullPath ? mainParam.getText() : null)) { + return true; + } + NixParamSet paramSet = lambda.getParamSet(); + if (paramSet != null) { + for (NixParam param : paramSet.getParamList()) { + ASTNode paramName = param.getNode().findChildByType(NixTypes.ID); + if (paramName != null && action.test(paramName.getPsi(), fullPath ? paramName.getText() : null)) { + return true; + } + } + } + } + return false; + } + + private static boolean iterateVariables(@NotNull List bindList, boolean fullPath, @NotNull BiPredicate action) { + for (NixBind bind : bindList) { + if (bind instanceof NixBindAttr) { + NixBindAttr bindAttr = (NixBindAttr) bind; + if (fullPath) { + List attrs = bindAttr.getAttrPath().getAttrList(); + NixAttr first = attrs.get(0); + String pathStr = first.getText(); + if (action.test(first, pathStr)) { + return true; + } + for (NixAttr attr : attrs.subList(1, attrs.size())) { + pathStr = pathStr + '.' + attr.getText(); + if (action.test(attr, pathStr)) { + return true; + } + } + } else { + if (action.test(bindAttr.getAttrPath().getFirstAttr(), null)) { + return true; + } + } + } else if (bind instanceof NixBindInherit) { + for (NixAttr attr : ((NixBindInherit) bind).getAttrList()) { + if (action.test(attr, fullPath ? attr.getText() : null)) { + return true; + } + } + } else { + throw new IllegalStateException("Unexpected NixBind implementation: " + bind.getClass()); + } + } + return false; + } + + private static @NotNull HighlightInfoType getAttributesBySource(@NotNull PsiElement source) { + if (source instanceof NixExprLet || + source instanceof NixLegacyLet || + source instanceof NixSet) { + return LOCAL_VARIABLE; + } else if (source instanceof NixExprLambda) { + return PARAMETER; + } else { + throw new IllegalArgumentException("Invalid source: " + source); + } + } + + private void highlight(@NotNull PsiElement element, @Nullable PsiElement source, @NotNull String attrPath) { + if (source != null) { + HighlightInfoType type = attrPath.contains(".") ? null : getAttributesBySource(source); + highlight(element, source, attrPath, type); + } + } +} diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java b/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java index dbbb9dc2..688d7f81 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java @@ -1,168 +1,54 @@ package org.nixos.idea.lang.highlighter; import com.intellij.codeInsight.daemon.RainbowVisitor; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.HighlightVisitor; -import com.intellij.lang.ASTNode; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.nixos.idea.file.NixFile; -import org.nixos.idea.psi.NixAttr; -import org.nixos.idea.psi.NixAttrPath; -import org.nixos.idea.psi.NixBind; -import org.nixos.idea.psi.NixBindAttr; -import org.nixos.idea.psi.NixBindInherit; -import org.nixos.idea.psi.NixExpr; -import org.nixos.idea.psi.NixExprLambda; -import org.nixos.idea.psi.NixExprLet; -import org.nixos.idea.psi.NixExprSelect; -import org.nixos.idea.psi.NixIdentifier; -import org.nixos.idea.psi.NixLegacyLet; -import org.nixos.idea.psi.NixParam; -import org.nixos.idea.psi.NixParamSet; -import org.nixos.idea.psi.NixSet; -import org.nixos.idea.psi.NixTypes; import java.util.List; -import java.util.function.BiPredicate; public final class NixRainbowVisitor extends RainbowVisitor { public static final List RAINBOW_ATTRIBUTES = List.of( NixTextAttributes.LOCAL_VARIABLE, NixTextAttributes.PARAMETER); + private Delegate myDelegate; + @Override public boolean suitableForFile(@NotNull PsiFile file) { - return file instanceof NixFile; + return NixHighlightVisitorDelegate.suitableForFile(file); } @Override public void visit(@NotNull PsiElement element) { - if (element instanceof NixExprSelect) { - NixExprSelect expr = (NixExprSelect) element; - NixExpr value = expr.getValue(); - if (!(value instanceof NixIdentifier)) { - return; - } - String identifier = value.getText(); - PsiElement source = findSource(element, identifier); - highlight(value, source, identifier); - - NixAttrPath attrPath = expr.getAttrPath(); - if (attrPath != null) { - String pathStr = identifier; - for (NixAttr nixAttr : expr.getAttrPath().getAttrList()) { - pathStr = pathStr + '.' + nixAttr.getText(); - highlight(nixAttr, source, pathStr); - } - } - } else { - iterateVariables(element, true, (var, path) -> { - highlight(var, element, path); - return false; - }); - } - } - - private static @Nullable PsiElement findSource(@NotNull PsiElement context, @NotNull String identifier) { - do { - if (iterateVariables(context, false, (var, __) -> var.textMatches(identifier))) { - return context; - } else { - context = context.getParent(); - } - } while (context != null); - return null; + myDelegate.visit(element); } - private static boolean iterateVariables(@NotNull PsiElement element, boolean fullPath, @NotNull BiPredicate action) { - if (element instanceof NixExprLet) { - NixExprLet let = (NixExprLet) element; - return iterateVariables(let.getBindList(), fullPath, action); - } else if (element instanceof NixLegacyLet) { - NixLegacyLet let = (NixLegacyLet) element; - return iterateVariables(let.getBindList(), fullPath, action); - } else if (element instanceof NixSet) { - NixSet set = (NixSet) element; - return set.getNode().findChildByType(NixTypes.REC) != null && - iterateVariables(set.getBindList(), fullPath, action); - } else if (element instanceof NixExprLambda) { - NixExprLambda lambda = (NixExprLambda) element; - ASTNode mainParam = lambda.getNode().findChildByType(NixTypes.ID); - if (mainParam != null && action.test(mainParam.getPsi(), fullPath ? mainParam.getText() : null)) { - return true; - } - NixParamSet paramSet = lambda.getParamSet(); - if (paramSet != null) { - for (NixParam param : paramSet.getParamList()) { - ASTNode paramName = param.getNode().findChildByType(NixTypes.ID); - if (paramName != null && action.test(paramName.getPsi(), fullPath ? paramName.getText() : null)) { - return true; - } - } - } - } - return false; - } - - private static boolean iterateVariables(@NotNull List bindList, boolean fullPath, @NotNull BiPredicate action) { - for (NixBind bind : bindList) { - if (bind instanceof NixBindAttr) { - NixBindAttr bindAttr = (NixBindAttr) bind; - if (fullPath) { - List attrs = bindAttr.getAttrPath().getAttrList(); - NixAttr first = attrs.get(0); - String pathStr = first.getText(); - if (action.test(first, pathStr)) { - return true; - } - for (NixAttr attr : attrs.subList(1, attrs.size())) { - pathStr = pathStr + '.' + attr.getText(); - if (action.test(attr, pathStr)) { - return true; - } - } - } else { - if (action.test(bindAttr.getAttrPath().getFirstAttr(), null)) { - return true; - } - } - } else if (bind instanceof NixBindInherit) { - for (NixAttr attr : ((NixBindInherit) bind).getAttrList()) { - if (action.test(attr, fullPath ? attr.getText() : null)) { - return true; - } - } - } else { - throw new IllegalStateException("Unexpected NixBind implementation: " + bind.getClass()); - } + @Override + public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action) { + myDelegate = new Delegate(); + try { + return super.analyze(file, updateWholeFile, holder, action); + } finally { + myDelegate = null; } - return false; } - private static @NotNull TextAttributesKey getAttributesBySource(@NotNull PsiElement source) { - if (source instanceof NixExprLet || - source instanceof NixLegacyLet || - source instanceof NixSet) { - return NixTextAttributes.LOCAL_VARIABLE; - } else if (source instanceof NixExprLambda) { - return NixTextAttributes.PARAMETER; - } else { - throw new IllegalArgumentException("Invalid source: " + source); - } + @Override + public @NotNull HighlightVisitor clone() { + return new NixRainbowVisitor(); } - private void highlight(@Nullable PsiElement element, @Nullable PsiElement source, @NotNull String attrPath) { - if (element != null && source != null) { - TextAttributesKey attributesKey = attrPath.contains(".") ? NixTextAttributes.IDENTIFIER : getAttributesBySource(source); + private final class Delegate extends NixHighlightVisitorDelegate { + @Override + void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type) { + TextAttributesKey attributesKey = type == null ? NixTextAttributes.IDENTIFIER : type.getAttributesKey(); addInfo(getInfo(source, element, attrPath, attributesKey)); } } - - @Override - public @NotNull HighlightVisitor clone() { - return new NixRainbowVisitor(); - } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f885e521..13f1222e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -23,6 +23,7 @@ language="Nix" implementationClass="org.nixos.idea.lang.highlighter.NixSyntaxHighlighterFactory" /> + Date: Sun, 12 Feb 2023 19:00:03 +0100 Subject: [PATCH 06/11] Add tests and fix NixHighlightVisitorDelegate --- .../NixHighlightVisitorDelegate.java | 32 ++-- .../highlighter/NixHighlightVisitorTest.java | 152 ++++++++++++++++++ .../highlighter/NixRainbowVisitorTest.java | 95 +++++++++++ 3 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java create mode 100644 src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java index 5e398196..da8907bc 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java @@ -21,6 +21,7 @@ import org.nixos.idea.psi.NixParam; import org.nixos.idea.psi.NixParamSet; import org.nixos.idea.psi.NixSet; +import org.nixos.idea.psi.NixStdAttr; import org.nixos.idea.psi.NixTypes; import java.util.List; @@ -31,8 +32,8 @@ */ abstract class NixHighlightVisitorDelegate { - private static final HighlightInfoType LOCAL_VARIABLE = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.LOCAL_VARIABLE, false); - private static final HighlightInfoType PARAMETER = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.PARAMETER, false); + public static final HighlightInfoType LOCAL_VARIABLE = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.LOCAL_VARIABLE, false); + public static final HighlightInfoType PARAMETER = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.PARAMETER, false); static boolean suitableForFile(@NotNull PsiFile file) { return file instanceof NixFile; @@ -41,20 +42,23 @@ static boolean suitableForFile(@NotNull PsiFile file) { abstract void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type); void visit(@NotNull PsiElement element) { - if (element instanceof NixExprSelect) { - NixExprSelect expr = (NixExprSelect) element; - NixExpr value = expr.getValue(); - if (!(value instanceof NixIdentifier)) { - return; - } + if (element instanceof NixIdentifier) { + NixExpr value = (NixIdentifier) element; String identifier = value.getText(); PsiElement source = findSource(element, identifier); highlight(value, source, identifier); - + } else if (element instanceof NixExprSelect) { + NixExprSelect expr = (NixExprSelect) element; + NixExpr value = expr.getValue(); NixAttrPath attrPath = expr.getAttrPath(); - if (attrPath != null) { + if (attrPath != null && value instanceof NixIdentifier) { + String identifier = value.getText(); + PsiElement source = findSource(element, identifier); String pathStr = identifier; for (NixAttr nixAttr : expr.getAttrPath().getAttrList()) { + if (!(nixAttr instanceof NixStdAttr)) { + break; + } pathStr = pathStr + '.' + nixAttr.getText(); highlight(nixAttr, source, pathStr); } @@ -115,11 +119,17 @@ private static boolean iterateVariables(@NotNull List bindList, boolean if (fullPath) { List attrs = bindAttr.getAttrPath().getAttrList(); NixAttr first = attrs.get(0); + if (!(first instanceof NixStdAttr)) { + continue; + } String pathStr = first.getText(); if (action.test(first, pathStr)) { return true; } for (NixAttr attr : attrs.subList(1, attrs.size())) { + if (!(attr instanceof NixStdAttr)) { + break; + } pathStr = pathStr + '.' + attr.getText(); if (action.test(attr, pathStr)) { return true; @@ -132,7 +142,7 @@ private static boolean iterateVariables(@NotNull List bindList, boolean } } else if (bind instanceof NixBindInherit) { for (NixAttr attr : ((NixBindInherit) bind).getAttrList()) { - if (action.test(attr, fullPath ? attr.getText() : null)) { + if (attr instanceof NixStdAttr && action.test(attr, fullPath ? attr.getText() : null)) { return true; } } diff --git a/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java b/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java new file mode 100644 index 00000000..b703cda2 --- /dev/null +++ b/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java @@ -0,0 +1,152 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.vfs.VirtualFileFilter; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.testFramework.ExpectedHighlightingData; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import org.jetbrains.annotations.NotNull; +import org.nixos.idea.file.NixFileType; + +import java.lang.reflect.Field; +import java.util.Collection; + +public final class NixHighlightVisitorTest extends BasePlatformTestCase { + + public void testSelectExpression() { + // TODO: Highlight x.y as a local variable + doTest("let\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + "in [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + " x.z\n" + + " x.\"no-highlighting-for-string-attributes\"\n" + + "]"); + } + + public void testLetExpression() { + doTest("let\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.y.z = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " copy = x;\n" + + "in [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + "]"); + } + + public void testLegacyLetExpression() { + doTest("let {\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.y.z = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " body = [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + " ];\n" + + "}"); + } + + public void testRecursiveSet() { + doTest("rec {\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.y.z = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " body = [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + " ];\n" + + "}"); + } + + public void testNoHighlightingForNonRecursiveSet() { + doTest("{\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + "}"); + } + + public void testLambda() { + doTest("x:\n" + + "{y}:\n" + + "z@{za, ...}: [\n" + + " x\n" + + " y\n" + + " z\n" + + " za\n" + + "]"); + } + + public void testVariableHidesParameter() { + doTest("{f, hidden}: [\n" + + " f hidden\n" + + " (\n" + + " let hidden = null;\n" + + " in f hidden\n" + + " )\n" + + " (let {\n" + + " hidden = null;\n" + + " body = f hidden;\n" + + " })\n" + + " (rec {\n" + + " hidden = null;\n" + + " body = f hidden;\n" + + " })\n" + + "]"); + } + + public void testParameterHidesVariable() { + doTest("let\n" + + " f = null;\n" + + " hidden = null;\n" + + "in\n" + + " hidden:\n" + + " f hidden"); + } + + private void doTest(@NotNull String code) { + PsiFile file = myFixture.configureByText(NixFileType.INSTANCE, code); + Document document = myFixture.getEditor().getDocument(); + PsiManagerEx.getInstanceEx(getProject()).setAssertOnFileLoadingFilter(VirtualFileFilter.NONE, getTestRootDisposable()); + ExpectedHighlightingData data = new NixExpectedHighlightingData(document, false); + data.init(); + Collection infos = myFixture.doHighlighting(); + data.checkResult(file, infos, document.getText(), null); + } + + private static final class NixExpectedHighlightingData extends ExpectedHighlightingData { + public NixExpectedHighlightingData(@NotNull Document document, boolean ignoreExtraHighlighting) { + super(document, true, true, true, ignoreExtraHighlighting); + checkSymbolNames(); + } + + @Override + protected HighlightInfoType getTypeByName(String typeString) throws Exception { + try { + Field field = NixHighlightVisitorDelegate.class.getField(typeString); + return (HighlightInfoType) field.get(null); + } catch (NoSuchFieldException e) { + return super.getTypeByName(typeString); + } + } + } +} diff --git a/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java b/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java new file mode 100644 index 00000000..d557d7ee --- /dev/null +++ b/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java @@ -0,0 +1,95 @@ +package org.nixos.idea.lang.highlighter; + +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import org.jetbrains.annotations.NotNull; + +public final class NixRainbowVisitorTest extends BasePlatformTestCase { + + public void testNoHighlightingWhenDisabled() { + myFixture.testRainbow("rainbow.nix", + "let x = y; in x", + false, false); + } + + public void testSelectExpression() { + doTest("let\n" + + " x = null;\n" + + "in [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + " x.z\n" + + " x.\"no-highlighting-for-string-attributes\"\n" + + "]"); + } + + public void testLetExpression() { + doTest("let\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.y.z = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " copy = x;\n" + + "in [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + "]"); + } + + public void testLegacyLetExpression() { + doTest("let {\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.y.z = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " body = [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + " ];\n" + + "}"); + } + + public void testRecursiveSet() { + doTest("rec {\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.y.z = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " body = [\n" + + " x\n" + + " x.y\n" + + " x.y.z\n" + + " ];\n" + + "}"); + } + + public void testNoHighlightingForNonRecursiveSet() { + doTest("{\n" + + " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + + " x = null;\n" + + " x.y = null;\n" + + " x.\"no-highlighting-for-string-attributes\" = null;\n" + + "}"); + } + + public void testLambda() { + // TODO: Ideally, za should have the same color as z.za. + doTest("x:\n" + + "{y}:\n" + + "z@{za, ...}: [\n" + + " x\n" + + " y\n" + + " z\n" + + " za\n" + + "]"); + } + + private void doTest(@NotNull String code) { + myFixture.testRainbow("rainbow.nix", code, true, true); + } +} From 4af43dbd48da879c99f92cc00d0d9283c5a0db2b Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Thu, 9 Mar 2023 16:28:55 +0100 Subject: [PATCH 07/11] Add disabled test for hiding variables --- .../lang/highlighter/NixRainbowVisitorTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java b/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java index d557d7ee..05963cfb 100644 --- a/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java +++ b/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java @@ -89,6 +89,19 @@ public void testLambda() { "]"); } + // TODO: Ideally, hidden elements should have a different color then the elements hiding them. Unfortunately, + // I haven't found a good way to implement this. + @SuppressWarnings("unused") + public void ignoreTestHidingChangesColor() { + doTest("{f, hidden}: [\n" + + " f hidden\n" + + " (let hidden = null; in f hidden)\n" + + " (let { hidden = null; body = f hidden; })\n" + + " (rec { hidden = null; body = f hidden; })\n" + + " (hidden: f hidden)\n" + + "]"); + } + private void doTest(@NotNull String code) { myFixture.testRainbow("rainbow.nix", code, true, true); } From 1eea9363c3aa58356f3ef475b10b3c7e918e0228 Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Fri, 10 Mar 2023 01:17:25 +0100 Subject: [PATCH 08/11] Implement highlighting for builtins --- .../nixos/idea/lang/builtins/NixBuiltin.java | 239 ++++++++++++++++++ .../lang/highlighter/NixHighlightVisitor.java | 2 +- .../NixHighlightVisitorDelegate.java | 48 +++- .../lang/highlighter/NixRainbowVisitor.java | 18 +- .../lang/highlighter/NixTextAttributes.java | 6 + .../idea/settings/NixColorSettingsPage.java | 47 ++-- .../java/org/nixos/idea/util/NixVersion.java | 33 +++ .../highlighter/NixHighlightVisitorTest.java | 68 +++-- .../highlighter/NixRainbowVisitorTest.java | 20 ++ 9 files changed, 427 insertions(+), 54 deletions(-) create mode 100644 src/main/java/org/nixos/idea/lang/builtins/NixBuiltin.java create mode 100644 src/main/java/org/nixos/idea/util/NixVersion.java diff --git a/src/main/java/org/nixos/idea/lang/builtins/NixBuiltin.java b/src/main/java/org/nixos/idea/lang/builtins/NixBuiltin.java new file mode 100644 index 00000000..521c37ba --- /dev/null +++ b/src/main/java/org/nixos/idea/lang/builtins/NixBuiltin.java @@ -0,0 +1,239 @@ +package org.nixos.idea.lang.builtins; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.nixos.idea.util.NixVersion; + +import java.util.Map; +import java.util.Set; + +public final class NixBuiltin { + //region Constants + + /** + * List of builtins which are available in the global scope (i.e. without “builtins.” prefix). + * To verify this list against your installation, start {@code nix repl} and press Tab. + */ + private static final Set GLOBAL_SCOPE = Set.of( + "false", + "null", + "true", + "abort", + "baseNameOf", + "break", + "builtins", + "derivation", + "derivationStrict", + "dirOf", + "fetchGit", + "fetchMercurial", + "fetchTarball", + "fetchTree", + "fromTOML", + "import", + "isNull", + "map", + "placeholder", + "removeAttrs", + "scopedImport", + "throw", + "toString"); + + /** + * List of all builtins. To verify this list against your installation, start {@code nix repl}, + * type {@code builtins.}, and press Tab. + */ + private static final Map BUILTINS = Map.ofEntries( + builtin("false", HighlightingType.LITERAL), + builtin("null", HighlightingType.LITERAL), + builtin("true", HighlightingType.LITERAL), + builtin("import", HighlightingType.IMPORT), + builtin("abort"), + builtin("add"), + builtin("addErrorContext"), + builtin("all"), + builtin("any"), + builtin("appendContext"), + builtin("attrNames"), + builtin("attrValues"), + builtin("baseNameOf"), + builtin("bitAnd"), + builtin("bitOr"), + builtin("bitXor"), + builtin("break", NixVersion.V2_09), + builtin("builtins"), + builtin("catAttrs"), + builtin("ceil", NixVersion.V2_04), + builtin("compareVersions"), + builtin("concatLists"), + builtin("concatMap"), + builtin("concatStringsSep"), + builtin("currentSystem"), + builtin("currentTime"), + builtin("deepSeq"), + builtin("derivation"), + builtin("derivationStrict"), + builtin("dirOf"), + builtin("div"), + builtin("elem"), + builtin("elemAt"), + builtin("fetchClosure", NixVersion.V2_08, "fetch-closure"), + builtin("fetchGit"), + builtin("fetchMercurial"), + builtin("fetchTarball"), + builtin("fetchTree", NixVersion.V2_04), + builtin("fetchurl"), + builtin("filter"), + builtin("filterSource"), + builtin("findFile"), + builtin("floor", NixVersion.V2_04), + builtin("foldl'"), + builtin("fromJSON"), + builtin("fromTOML"), + builtin("functionArgs"), + builtin("genList"), + builtin("genericClosure"), + builtin("getAttr"), + builtin("getContext"), + builtin("getEnv"), + builtin("getFlake", NixVersion.V2_04, "flakes"), + builtin("groupBy", NixVersion.V2_05), + builtin("hasAttr"), + builtin("hasContext"), + builtin("hashFile"), + builtin("hashString"), + builtin("head"), + builtin("intersectAttrs"), + builtin("isAttrs"), + builtin("isBool"), + builtin("isFloat"), + builtin("isFunction"), + builtin("isInt"), + builtin("isList"), + builtin("isNull"), + builtin("isPath"), + builtin("isString"), + builtin("langVersion"), + builtin("length"), + builtin("lessThan"), + builtin("listToAttrs"), + builtin("map"), + builtin("mapAttrs"), + builtin("match"), + builtin("mul"), + builtin("nixPath"), + builtin("nixVersion"), + builtin("parseDrvName"), + builtin("partition"), + builtin("path"), + builtin("pathExists"), + builtin("placeholder"), + builtin("readDir"), + builtin("readFile"), + builtin("removeAttrs"), + builtin("replaceStrings"), + builtin("scopedImport"), + builtin("seq"), + builtin("sort"), + builtin("split"), + builtin("splitVersion"), + builtin("storeDir"), + builtin("storePath"), + builtin("stringLength"), + builtin("sub"), + builtin("substring"), + builtin("tail"), + builtin("throw"), + builtin("toFile"), + builtin("toJSON"), + builtin("toPath"), + builtin("toString"), + builtin("toXML"), + builtin("trace"), + builtin("traceVerbose", NixVersion.V2_10), + builtin("tryEval"), + builtin("typeOf"), + builtin("unsafeDiscardOutputDependency"), + builtin("unsafeDiscardStringContext"), + builtin("unsafeGetAttrPos"), + builtin("zipAttrsWith", NixVersion.V2_06)); + + //endregion + //region Factories + + private static @NotNull Map.Entry builtin(@NotNull String name) { + return Map.entry(name, new NixBuiltin(name, null, null, null, HighlightingType.OTHER)); + } + + private static @NotNull Map.Entry builtin(@NotNull String name, @NotNull HighlightingType highlightingType) { + return Map.entry(name, new NixBuiltin(name, null, null, null, highlightingType)); + } + + private static @NotNull Map.Entry builtin(@NotNull String name, @NotNull NixVersion since) { + return Map.entry(name, new NixBuiltin(name, since, null, null, HighlightingType.OTHER)); + } + + private static @NotNull Map.Entry builtin(@NotNull String name, @NotNull NixVersion since, @NotNull String featureFlag) { + return Map.entry(name, new NixBuiltin(name, since, featureFlag, null, HighlightingType.OTHER)); + } + + //endregion + //region Instance members + + private final @NotNull String name; + private final @Nullable NixVersion since; + private final @Nullable String featureFlag; + private final @Nullable NixVersion featureFlagIntegration; + private final @NotNull HighlightingType highlightingType; + private final boolean global; + + private NixBuiltin(@NotNull String name, + @Nullable NixVersion since, + @Nullable String featureFlag, + @Nullable NixVersion featureFlagIntegration, + @NotNull HighlightingType highlightingType) { + this.name = name; + this.since = since; + this.featureFlag = featureFlag; + this.featureFlagIntegration = featureFlagIntegration; + this.highlightingType = highlightingType; + this.global = GLOBAL_SCOPE.contains(name); + } + + public HighlightingType highlightingType() { + return highlightingType; + } + + //endregion + //region Static members + + public static @Nullable NixBuiltin resolveBuiltin(@NotNull String name) { + return BUILTINS.get(name); + } + + public static @Nullable NixBuiltin resolveGlobal(@NotNull String name) { + NixBuiltin builtin = BUILTINS.get(name); + return builtin != null && builtin.global ? builtin : null; + } + + //endregion + //region Inner classes + + public enum HighlightingType { + /** + * Builtins which are casually described as literals. + * Specifically, only “{@code true}”, “{@code null}”, and “{@code false} have this type”. + */ + LITERAL, + /** + * Import function. + */ + IMPORT, + /** + * Any other builtin which does not match any of the other types. + */ + OTHER, + } + + //endregion +} diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java index f51f2c58..553b41c0 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitor.java @@ -45,7 +45,7 @@ public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull private final class Delegate extends NixHighlightVisitorDelegate { @Override - void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type) { + void highlight(@NotNull PsiElement element, @Nullable PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type) { if (type != null) { myHolder.add(HighlightInfo.newHighlightInfo(type) .range(element) diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java index da8907bc..4084f680 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorDelegate.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.nixos.idea.file.NixFile; +import org.nixos.idea.lang.builtins.NixBuiltin; import org.nixos.idea.psi.NixAttr; import org.nixos.idea.psi.NixAttrPath; import org.nixos.idea.psi.NixBind; @@ -32,6 +33,11 @@ */ abstract class NixHighlightVisitorDelegate { + private static final String BUILTINS_PREFIX = "builtins."; + + public static final HighlightInfoType LITERAL = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.LITERAL, true); + public static final HighlightInfoType IMPORT = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.IMPORT, true); + public static final HighlightInfoType BUILTIN = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.BUILTIN, true); public static final HighlightInfoType LOCAL_VARIABLE = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.LOCAL_VARIABLE, false); public static final HighlightInfoType PARAMETER = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, NixTextAttributes.PARAMETER, false); @@ -39,7 +45,15 @@ static boolean suitableForFile(@NotNull PsiFile file) { return file instanceof NixFile; } - abstract void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type); + /** + * Callback which gets called for usages of variables and their attributes in the given file. + * + * @param element The element which refers to a variable or attribute. + * @param source The element which defines the corresponding variable. Is {@code null} if the source cannot be determined. + * @param attrPath The used attribute path. + * @param type The type of variable, i.e. {@link #LOCAL_VARIABLE} or {@link #PARAMETER}. + */ + abstract void highlight(@NotNull PsiElement element, @Nullable PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type); void visit(@NotNull PsiElement element) { if (element instanceof NixIdentifier) { @@ -153,7 +167,7 @@ private static boolean iterateVariables(@NotNull List bindList, boolean return false; } - private static @NotNull HighlightInfoType getAttributesBySource(@NotNull PsiElement source) { + private static @NotNull HighlightInfoType getHighlightingBySource(@NotNull PsiElement source) { if (source instanceof NixExprLet || source instanceof NixLegacyLet || source instanceof NixSet) { @@ -165,10 +179,34 @@ private static boolean iterateVariables(@NotNull List bindList, boolean } } + private static @NotNull HighlightInfoType getHighlightingByBuiltin(@NotNull NixBuiltin builtin) { + switch (builtin.highlightingType()) { + case IMPORT: return IMPORT; + case LITERAL: return LITERAL; + case OTHER: return BUILTIN; + default: throw new IllegalStateException("unknown type: " + builtin.highlightingType()); + } + } + private void highlight(@NotNull PsiElement element, @Nullable PsiElement source, @NotNull String attrPath) { - if (source != null) { - HighlightInfoType type = attrPath.contains(".") ? null : getAttributesBySource(source); - highlight(element, source, attrPath, type); + HighlightInfoType type = null; + if (!attrPath.contains(".")) { + if (source != null) { + type = getHighlightingBySource(source); + } + else { + NixBuiltin builtin = NixBuiltin.resolveGlobal(attrPath); + if (builtin != null) { + type = getHighlightingByBuiltin(builtin); + } + } + } + else if (attrPath.startsWith(BUILTINS_PREFIX)) { + NixBuiltin builtin = NixBuiltin.resolveBuiltin(attrPath.substring(BUILTINS_PREFIX.length())); + if (builtin != null) { + type = getHighlightingByBuiltin(builtin); + } } + highlight(element, source, attrPath, type); } } diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java b/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java index 688d7f81..0994e523 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixRainbowVisitor.java @@ -13,6 +13,7 @@ import java.util.List; public final class NixRainbowVisitor extends RainbowVisitor { + public static final List RAINBOW_ATTRIBUTES = List.of( NixTextAttributes.LOCAL_VARIABLE, NixTextAttributes.PARAMETER); @@ -31,7 +32,7 @@ public void visit(@NotNull PsiElement element) { @Override public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action) { - myDelegate = new Delegate(); + myDelegate = new Delegate(file); try { return super.analyze(file, updateWholeFile, holder, action); } finally { @@ -45,10 +46,19 @@ public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull } private final class Delegate extends NixHighlightVisitorDelegate { + private final @NotNull PsiFile file; + + private Delegate(@NotNull PsiFile file) { + this.file = file; + } + @Override - void highlight(@NotNull PsiElement element, @NotNull PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type) { - TextAttributesKey attributesKey = type == null ? NixTextAttributes.IDENTIFIER : type.getAttributesKey(); - addInfo(getInfo(source, element, attrPath, attributesKey)); + void highlight(@NotNull PsiElement element, @Nullable PsiElement source, @NotNull String attrPath, @Nullable HighlightInfoType type) { + if (type != LITERAL && type != IMPORT && type != BUILTIN) { + TextAttributesKey attributesKey = type == null ? NixTextAttributes.IDENTIFIER : type.getAttributesKey(); + PsiElement context = source == null ? file : source; + addInfo(getInfo(context, element, attrPath, attributesKey)); + } } } } diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java b/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java index 53c25a4c..54e6e8e5 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java @@ -36,6 +36,12 @@ public final class NixTextAttributes { public static final TextAttributesKey IDENTIFIER = createTextAttributesKey("NIX_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER); + public static final TextAttributesKey LITERAL = + createTextAttributesKey("NIX_LITERAL", DefaultLanguageHighlighterColors.KEYWORD); + public static final TextAttributesKey IMPORT = + createTextAttributesKey("NIX_IMPORT", DefaultLanguageHighlighterColors.KEYWORD); + public static final TextAttributesKey BUILTIN = + createTextAttributesKey("NIX_BUILTIN", DefaultLanguageHighlighterColors.CONSTANT); public static final TextAttributesKey LOCAL_VARIABLE = createTextAttributesKey("NIX_LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE); public static final TextAttributesKey PARAMETER = diff --git a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java index a438715f..a715cc86 100644 --- a/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java +++ b/src/main/java/org/nixos/idea/settings/NixColorSettingsPage.java @@ -9,7 +9,6 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.nixos.idea.icon.NixIcons; import org.nixos.idea.lang.NixLanguage; import org.nixos.idea.lang.highlighter.NixRainbowVisitor; @@ -36,6 +35,9 @@ public final class NixColorSettingsPage implements RainbowColorSettingsPage { descriptor("Variables and Attributes//Other identifier", NixTextAttributes.IDENTIFIER), descriptor("Variables and Attributes//Local variable", NixTextAttributes.LOCAL_VARIABLE), descriptor("Variables and Attributes//Function parameter", NixTextAttributes.PARAMETER), + descriptor("Built-in constants and functions//Literals", NixTextAttributes.LITERAL), + descriptor("Built-in constants and functions//Import function", NixTextAttributes.IMPORT), + descriptor("Built-in constants and functions//Other built-ins", NixTextAttributes.BUILTIN), descriptor("Literals and Values//Number", NixTextAttributes.NUMBER), descriptor("Literals and Values//String", NixTextAttributes.STRING), descriptor("Literals and Values//Escape sequence", NixTextAttributes.STRING_ESCAPE), @@ -45,6 +47,13 @@ public final class NixColorSettingsPage implements RainbowColorSettingsPage { descriptor("Comments//Block comment", NixTextAttributes.BLOCK_COMMENT), }; + private static final Map ADDITIONAL_HIGHLIGHTING_TAG = Map.of( + "builtin", NixTextAttributes.BUILTIN, + "import", NixTextAttributes.IMPORT, + "literal", NixTextAttributes.LITERAL, + "variable", NixTextAttributes.LOCAL_VARIABLE, + "parameter", NixTextAttributes.PARAMETER); + @Override public @NotNull Language getLanguage() { return NixLanguage.INSTANCE; @@ -52,6 +61,9 @@ public final class NixColorSettingsPage implements RainbowColorSettingsPage { @Override public boolean isRainbowType(TextAttributesKey type) { + // I think this method shall return true if the NixRainbowVisitor will always override the color of the given + // key. Unfortunately this method is not documented, but I assume the semantic highlighting will avoid using + // colors which correspond to attribute keys for which this method returns false. return NixRainbowVisitor.RAINBOW_ATTRIBUTES.contains(type); } @@ -67,31 +79,32 @@ public boolean isRainbowType(TextAttributesKey type) { @Override public @NonNls @NotNull String getDemoText() { - // language=Nix return "/* This code demonstrates the syntax highlighting for the Nix Expression Language */\n" + "let\n" + - " literals.number = 42;\n" + - " literals.string1 = \"This is a normal string\";\n" + - " literals.string2 = ''\n" + - " Broken escape sequence: \\${literals.number}\n" + - " Escaped interpolation: ''${literals.number}\n" + - " Generic escape sequence: $''\\{literals.number}\n" + + " literals.null = null;\n" + + " literals.boolean = true;\n" + + " literals.number = 42;\n" + + " literals.string1 = \"This is a normal string\";\n" + + " literals.string2 = ''\n" + + " Broken escape sequence: \\${literals.number}\n" + + " Escaped interpolation: ''${literals.number}\n" + + " Generic escape sequence: $''\\{literals.number}\n" + " '';\n" + - " literals.paths = [/etc/gitconfig ~/.gitconfig .git/config];\n" + + " literals.paths = [/etc/gitconfig ~/.gitconfig .git/config];\n" + " # Note that unquoted URIs were deperecated by RFC 45\n" + - " literals.uri = https://github.com/NixOS/rfcs/pull/45;\n" + + " literals.uri = https://github.com/NixOS/rfcs/pull/45;\n" + "in {\n" + - " inherit (literals) number string1 string2 paths uri;\n" + - " nixpkgs = import ;\n" + - " baseNames = map baseNameOf literals.paths;\n" + - " f = { multiply ? 1, add ? 0, ... }@args:\n" + - " builtins.mapAttrs (name: value: multiply * value + add) args;\n" + + " inherit (literals) number string1 string2 paths uri;\n" + + " nixpkgs = import ;\n" + + " baseNames = map baseNameOf literals.paths;\n" + + " f = { multiply ? 1, add ? 0, ... }@args:\n" + + " builtins.mapAttrs (name: value: multiply * value + add) args;\n" + "}"; } @Override - public @Nullable Map getAdditionalHighlightingTagToDescriptorMap() { - return null; + public @NotNull Map getAdditionalHighlightingTagToDescriptorMap() { + return ADDITIONAL_HIGHLIGHTING_TAG; } @Override diff --git a/src/main/java/org/nixos/idea/util/NixVersion.java b/src/main/java/org/nixos/idea/util/NixVersion.java new file mode 100644 index 00000000..608c6c25 --- /dev/null +++ b/src/main/java/org/nixos/idea/util/NixVersion.java @@ -0,0 +1,33 @@ +package org.nixos.idea.util; + +/** + * Known versions of the Nix package manager. + * + * @see Release Notes + */ +public enum NixVersion { + /** + * Release 2.4 (2021-11-01). + */ + V2_04, + /** + * Release 2.5 (2021-12-13). + */ + V2_05, + /** + * Release 2.6 (2022-01-24). + */ + V2_06, + /** + * Release 2.8 (2022-04-19). + */ + V2_08, + /** + * Release 2.9 (2022-05-30). + */ + V2_09, + /** + * Release 2.10 (2022-07-11). + */ + V2_10, +} diff --git a/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java b/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java index b703cda2..e44eecef 100644 --- a/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java +++ b/src/test/java/org/nixos/idea/lang/highlighter/NixHighlightVisitorTest.java @@ -19,9 +19,9 @@ public final class NixHighlightVisitorTest extends BasePlatformTestCase { public void testSelectExpression() { // TODO: Highlight x.y as a local variable doTest("let\n" + - " x = null;\n" + - " x.y = null;\n" + - " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " x = some_value;\n" + + " x.y = some_value;\n" + + " x.\"no-highlighting-for-string-attributes\" = some_value;\n" + "in [\n" + " x\n" + " x.y\n" + @@ -31,13 +31,27 @@ public void testSelectExpression() { "]"); } + public void testBuiltins() { + doTest("[\n" + + " null\n" + + " true\n" + + " false\n" + + " import\n" + + " map\n" + + " builtins.null\n" + + " builtins.map\n" + + " builtins.compareVersions\n" + + " compareVersions\n" + + "]"); + } + public void testLetExpression() { doTest("let\n" + - " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + - " x = null;\n" + - " x.y = null;\n" + - " x.y.z = null;\n" + - " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " inherit (some_value) \"no-highlighting-for-string-attributes\" x;\n" + + " x = some_value;\n" + + " x.y = some_value;\n" + + " x.y.z = some_value;\n" + + " x.\"no-highlighting-for-string-attributes\" = some_value;\n" + " copy = x;\n" + "in [\n" + " x\n" + @@ -48,11 +62,11 @@ public void testLetExpression() { public void testLegacyLetExpression() { doTest("let {\n" + - " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + - " x = null;\n" + - " x.y = null;\n" + - " x.y.z = null;\n" + - " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " inherit (some_value) \"no-highlighting-for-string-attributes\" x;\n" + + " x = some_value;\n" + + " x.y = some_value;\n" + + " x.y.z = some_value;\n" + + " x.\"no-highlighting-for-string-attributes\" = some_value;\n" + " body = [\n" + " x\n" + " x.y\n" + @@ -63,11 +77,11 @@ public void testLegacyLetExpression() { public void testRecursiveSet() { doTest("rec {\n" + - " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + - " x = null;\n" + - " x.y = null;\n" + - " x.y.z = null;\n" + - " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " inherit (some_value) \"no-highlighting-for-string-attributes\" x;\n" + + " x = some_value;\n" + + " x.y = some_value;\n" + + " x.y.z = some_value;\n" + + " x.\"no-highlighting-for-string-attributes\" = some_value;\n" + " body = [\n" + " x\n" + " x.y\n" + @@ -78,10 +92,10 @@ public void testRecursiveSet() { public void testNoHighlightingForNonRecursiveSet() { doTest("{\n" + - " inherit (null) \"no-highlighting-for-string-attributes\" x;\n" + - " x = null;\n" + - " x.y = null;\n" + - " x.\"no-highlighting-for-string-attributes\" = null;\n" + + " inherit (some_value) \"no-highlighting-for-string-attributes\" x;\n" + + " x = some_value;\n" + + " x.y = some_value;\n" + + " x.\"no-highlighting-for-string-attributes\" = some_value;\n" + "}"); } @@ -100,15 +114,15 @@ public void testVariableHidesParameter() { doTest("{f, hidden}: [\n" + " f hidden\n" + " (\n" + - " let hidden = null;\n" + + " let hidden = some_value;\n" + " in f hidden\n" + " )\n" + " (let {\n" + - " hidden = null;\n" + + " hidden = some_value;\n" + " body = f hidden;\n" + " })\n" + " (rec {\n" + - " hidden = null;\n" + + " hidden = some_value;\n" + " body = f hidden;\n" + " })\n" + "]"); @@ -116,8 +130,8 @@ public void testVariableHidesParameter() { public void testParameterHidesVariable() { doTest("let\n" + - " f = null;\n" + - " hidden = null;\n" + + " f = some_value;\n" + + " hidden = some_value;\n" + "in\n" + " hidden:\n" + " f hidden"); diff --git a/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java b/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java index 05963cfb..318b5e57 100644 --- a/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java +++ b/src/test/java/org/nixos/idea/lang/highlighter/NixRainbowVisitorTest.java @@ -89,6 +89,26 @@ public void testLambda() { "]"); } + public void testUnknownSource() { + doTest("[\n" + + " x\n" + + " compareVersions\n" + + "]"); + } + + public void testNoRainbowForBuiltins() { + doTest("[\n" + + " null\n" + + " true\n" + + " false\n" + + " import\n" + + " map\n" + + " builtins.null\n" + + " builtins.map\n" + + " builtins.compareVersions\n" + + "]"); + } + // TODO: Ideally, hidden elements should have a different color then the elements hiding them. Unfortunately, // I haven't found a good way to implement this. @SuppressWarnings("unused") From 26a29df0b0a95c7920dc07e09672dbb6f89d30fd Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Fri, 10 Mar 2023 01:18:54 +0100 Subject: [PATCH 09/11] Specify encoding of source files --- build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index c5589741..a93eabfd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -97,6 +97,12 @@ gradle.buildFinished { } tasks { + withType { + options.encoding = "UTF-8" + } + withType { + options.encoding = "UTF-8" + } task("jbr") { description = "Create a symlink to package jetbrains.jdk" From 3d098578a3588c493886300c475558e4aa3b28f0 Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Fri, 10 Mar 2023 01:58:26 +0100 Subject: [PATCH 10/11] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4df011b..3fa926d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] ### Added +- Highlighting of built-in functions and constants +- Support for [semantic highlighting](https://www.jetbrains.com/help/idea/configuring-colors-and-fonts.html#semantic-highlighting) +- Settings to change the colors used by the highlighter ### Changed From 19972f9d66d09045fd431771c295a9f2af762abd Mon Sep 17 00:00:00 2001 From: Johannes Spangenberg Date: Fri, 10 Mar 2023 03:18:42 +0100 Subject: [PATCH 11/11] Use PREDEFINED_SYMBOL for builtins --- .../nixos/idea/lang/highlighter/NixTextAttributes.java | 2 +- src/main/resources/META-INF/plugin.xml | 3 +++ .../resources/org/nixos/idea/colorSchemes/NixDarcula.xml | 8 ++++++++ .../resources/org/nixos/idea/colorSchemes/NixDefault.xml | 8 ++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/org/nixos/idea/colorSchemes/NixDarcula.xml create mode 100644 src/main/resources/org/nixos/idea/colorSchemes/NixDefault.xml diff --git a/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java b/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java index 54e6e8e5..ab28b625 100644 --- a/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java +++ b/src/main/java/org/nixos/idea/lang/highlighter/NixTextAttributes.java @@ -41,7 +41,7 @@ public final class NixTextAttributes { public static final TextAttributesKey IMPORT = createTextAttributesKey("NIX_IMPORT", DefaultLanguageHighlighterColors.KEYWORD); public static final TextAttributesKey BUILTIN = - createTextAttributesKey("NIX_BUILTIN", DefaultLanguageHighlighterColors.CONSTANT); + createTextAttributesKey("NIX_BUILTIN", DefaultLanguageHighlighterColors.PREDEFINED_SYMBOL); public static final TextAttributesKey LOCAL_VARIABLE = createTextAttributesKey("NIX_LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE); public static final TextAttributesKey PARAMETER = diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 13f1222e..1fd59e5b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -26,6 +26,9 @@ + + + diff --git a/src/main/resources/org/nixos/idea/colorSchemes/NixDarcula.xml b/src/main/resources/org/nixos/idea/colorSchemes/NixDarcula.xml new file mode 100644 index 00000000..70ae5067 --- /dev/null +++ b/src/main/resources/org/nixos/idea/colorSchemes/NixDarcula.xml @@ -0,0 +1,8 @@ + + + + diff --git a/src/main/resources/org/nixos/idea/colorSchemes/NixDefault.xml b/src/main/resources/org/nixos/idea/colorSchemes/NixDefault.xml new file mode 100644 index 00000000..64199fab --- /dev/null +++ b/src/main/resources/org/nixos/idea/colorSchemes/NixDefault.xml @@ -0,0 +1,8 @@ + + + +