Phase 2: syntax highlighting and colour settings page
- JaiSyntaxHighlighter maps every one of the lexer's token types; asserted over the whole corpus (105 distinct token types seen, all mapped) - JaiHighlightingLexer refines IDENT into built-in types and loop variables for colouring only, so the parser never sees them as reserved - colour settings page with a demo file that is itself asserted to lex cleanly
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package dev.hgh.jai.highlighting
|
||||
|
||||
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 dev.hgh.jai.JaiIcons
|
||||
import javax.swing.Icon
|
||||
|
||||
class JaiColorSettingsPage : ColorSettingsPage {
|
||||
override fun getDisplayName(): String = "Jai"
|
||||
|
||||
override fun getIcon(): Icon = JaiIcons.FILE
|
||||
|
||||
override fun getHighlighter(): SyntaxHighlighter = JaiSyntaxHighlighter()
|
||||
|
||||
override fun getAttributeDescriptors(): Array<AttributesDescriptor> = DESCRIPTORS
|
||||
|
||||
override fun getColorDescriptors(): Array<ColorDescriptor> = ColorDescriptor.EMPTY_ARRAY
|
||||
|
||||
override fun getAdditionalHighlightingTagToDescriptorMap(): Map<String, TextAttributesKey>? = null
|
||||
|
||||
override fun getDemoText(): String = DEMO_TEXT
|
||||
|
||||
companion object {
|
||||
private val DESCRIPTORS =
|
||||
arrayOf(
|
||||
AttributesDescriptor("Keyword", JaiColors.KEYWORD),
|
||||
AttributesDescriptor("Built-in type", JaiColors.BUILTIN_TYPE),
|
||||
AttributesDescriptor("Identifier", JaiColors.IDENTIFIER),
|
||||
AttributesDescriptor("Loop variable (it, it_index)", JaiColors.LOOP_VARIABLE),
|
||||
AttributesDescriptor("Compiler directive", JaiColors.DIRECTIVE),
|
||||
AttributesDescriptor("Note", JaiColors.NOTE),
|
||||
AttributesDescriptor("Number", JaiColors.NUMBER),
|
||||
AttributesDescriptor("String", JaiColors.STRING),
|
||||
AttributesDescriptor("Here-string", JaiColors.HERE_STRING),
|
||||
AttributesDescriptor("Comments//Line comment", JaiColors.LINE_COMMENT),
|
||||
AttributesDescriptor("Comments//Block comment", JaiColors.BLOCK_COMMENT),
|
||||
AttributesDescriptor("Operator", JaiColors.OPERATOR),
|
||||
AttributesDescriptor("Uninitialized (---)", JaiColors.UNINITIALIZED),
|
||||
AttributesDescriptor("Polymorph (\$, \$\$)", JaiColors.POLYMORPH),
|
||||
AttributesDescriptor("Backtick", JaiColors.BACKTICK),
|
||||
AttributesDescriptor("Braces and operators//Parentheses", JaiColors.PARENTHESES),
|
||||
AttributesDescriptor("Braces and operators//Braces", JaiColors.BRACES),
|
||||
AttributesDescriptor("Braces and operators//Brackets", JaiColors.BRACKETS),
|
||||
AttributesDescriptor("Braces and operators//Semicolon", JaiColors.SEMICOLON),
|
||||
AttributesDescriptor("Braces and operators//Comma", JaiColors.COMMA),
|
||||
AttributesDescriptor("Braces and operators//Dot", JaiColors.DOT),
|
||||
AttributesDescriptor("Bad character", JaiColors.BAD_CHARACTER),
|
||||
)
|
||||
|
||||
// Exercises every attribute above, and doubles as a lexer fixture in tests.
|
||||
private val DEMO_TEXT =
|
||||
"""
|
||||
// A tour of Jai syntax.
|
||||
/* Block comments /* nest */ like this. */
|
||||
#import "Basic";
|
||||
|
||||
Vector2 :: struct {
|
||||
x, y: float;
|
||||
label: string = "origin"; @Cleanup
|
||||
}
|
||||
|
||||
Fruit :: enum u32 #specified {
|
||||
BANANA :: 5;
|
||||
APPLE;
|
||||
}
|
||||
|
||||
MESSAGE :: #string DONE
|
||||
Here-strings are raw: "quotes" and /* comments */ mean nothing.
|
||||
DONE
|
||||
|
||||
main :: (count: int, scale: ${'$'}T = 1.5e-7) -> bool {
|
||||
total := 0x1f_ff + 0b1011 + 0h7fbf_ffff;
|
||||
uninitialized: [8] u8 = ---;
|
||||
point := Vector2.{ 1.0, 2.0 };
|
||||
names := .[ "a", "b" ];
|
||||
|
||||
for < values {
|
||||
if it_index == { case 0; continue; case; break; }
|
||||
total += cast(int) it;
|
||||
}
|
||||
|
||||
defer free(point);
|
||||
return total >= 0 && !(total < 0);
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
133
src/main/kotlin/dev/hgh/jai/highlighting/JaiSyntaxHighlighter.kt
Normal file
133
src/main/kotlin/dev/hgh/jai/highlighting/JaiSyntaxHighlighter.kt
Normal file
@@ -0,0 +1,133 @@
|
||||
package dev.hgh.jai.highlighting
|
||||
|
||||
import com.intellij.lexer.Lexer
|
||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey
|
||||
import com.intellij.openapi.fileTypes.SyntaxHighlighter
|
||||
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
|
||||
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.TokenType
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import dev.hgh.jai.lexer.JaiLexer
|
||||
import dev.hgh.jai.lexer.JaiTokenTypes
|
||||
|
||||
/** Text attribute keys for Jai. See `docs/JAI_LANGUAGE_REFERENCE.md` §13. */
|
||||
object JaiColors {
|
||||
val KEYWORD = key("JAI_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD)
|
||||
val BUILTIN_TYPE = key("JAI_BUILTIN_TYPE", DefaultLanguageHighlighterColors.CLASS_NAME)
|
||||
val IDENTIFIER = key("JAI_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER)
|
||||
val LOOP_VARIABLE = key("JAI_LOOP_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE)
|
||||
val DIRECTIVE = key("JAI_DIRECTIVE", DefaultLanguageHighlighterColors.METADATA)
|
||||
val NOTE = key("JAI_NOTE", DefaultLanguageHighlighterColors.METADATA)
|
||||
val NUMBER = key("JAI_NUMBER", DefaultLanguageHighlighterColors.NUMBER)
|
||||
val STRING = key("JAI_STRING", DefaultLanguageHighlighterColors.STRING)
|
||||
val HERE_STRING = key("JAI_HERE_STRING", DefaultLanguageHighlighterColors.STRING)
|
||||
val LINE_COMMENT = key("JAI_LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT)
|
||||
val BLOCK_COMMENT = key("JAI_BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT)
|
||||
val OPERATOR = key("JAI_OPERATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN)
|
||||
val UNINITIALIZED = key("JAI_UNINITIALIZED", DefaultLanguageHighlighterColors.KEYWORD)
|
||||
val POLYMORPH = key("JAI_POLYMORPH", DefaultLanguageHighlighterColors.STATIC_FIELD)
|
||||
val BACKTICK = key("JAI_BACKTICK", DefaultLanguageHighlighterColors.STATIC_FIELD)
|
||||
val PARENTHESES = key("JAI_PARENTHESES", DefaultLanguageHighlighterColors.PARENTHESES)
|
||||
val BRACES = key("JAI_BRACES", DefaultLanguageHighlighterColors.BRACES)
|
||||
val BRACKETS = key("JAI_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS)
|
||||
val SEMICOLON = key("JAI_SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON)
|
||||
val COMMA = key("JAI_COMMA", DefaultLanguageHighlighterColors.COMMA)
|
||||
val DOT = key("JAI_DOT", DefaultLanguageHighlighterColors.DOT)
|
||||
val BAD_CHARACTER = key("JAI_BAD_CHARACTER", com.intellij.openapi.editor.HighlighterColors.BAD_CHARACTER)
|
||||
|
||||
private fun key(
|
||||
name: String,
|
||||
fallback: TextAttributesKey,
|
||||
): TextAttributesKey = createTextAttributesKey(name, fallback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Token types produced *only* for highlighting.
|
||||
*
|
||||
* Built-in type names and the implicit loop variables are ordinary identifiers to the
|
||||
* compiler (`docs/JAI_LANGUAGE_REFERENCE.md` §3, §14.9) and must stay that way for the
|
||||
* parser. The highlighting lexer is a separate instance from the parsing lexer, so it
|
||||
* can refine IDENT without reserving anything.
|
||||
*/
|
||||
object JaiHighlightingTokens {
|
||||
val BUILTIN_TYPE: IElementType =
|
||||
dev.hgh.jai.lexer
|
||||
.JaiTokenType("BUILTIN_TYPE")
|
||||
val LOOP_VARIABLE: IElementType =
|
||||
dev.hgh.jai.lexer
|
||||
.JaiTokenType("LOOP_VARIABLE")
|
||||
}
|
||||
|
||||
/** [JaiLexer] plus the highlight-only refinement of IDENT. */
|
||||
class JaiHighlightingLexer : JaiLexer() {
|
||||
override fun getTokenType(): IElementType? {
|
||||
val type = super.getTokenType()
|
||||
if (type === JaiTokenTypes.IDENT) {
|
||||
val name = bufferSequence.subSequence(tokenStart, tokenEnd).toString()
|
||||
if (name in JaiTokenTypes.BUILTIN_TYPE_NAMES) return JaiHighlightingTokens.BUILTIN_TYPE
|
||||
if (name in JaiTokenTypes.LOOP_VARIABLE_NAMES) return JaiHighlightingTokens.LOOP_VARIABLE
|
||||
}
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
class JaiSyntaxHighlighter : SyntaxHighlighterBase() {
|
||||
override fun getHighlightingLexer(): Lexer = JaiHighlightingLexer()
|
||||
|
||||
override fun getTokenHighlights(tokenType: IElementType): Array<TextAttributesKey> = pack(ATTRIBUTES[tokenType])
|
||||
|
||||
companion object {
|
||||
private val ATTRIBUTES: Map<IElementType, TextAttributesKey> =
|
||||
buildMap {
|
||||
JaiTokenTypes.KEYWORD_MAP.values.forEach { put(it, JaiColors.KEYWORD) }
|
||||
|
||||
put(JaiTokenTypes.IDENT, JaiColors.IDENTIFIER)
|
||||
put(JaiHighlightingTokens.BUILTIN_TYPE, JaiColors.BUILTIN_TYPE)
|
||||
put(JaiHighlightingTokens.LOOP_VARIABLE, JaiColors.LOOP_VARIABLE)
|
||||
|
||||
put(JaiTokenTypes.NUMBER, JaiColors.NUMBER)
|
||||
put(JaiTokenTypes.STRING, JaiColors.STRING)
|
||||
put(JaiTokenTypes.HERE_STRING, JaiColors.HERE_STRING)
|
||||
put(JaiTokenTypes.DIRECTIVE, JaiColors.DIRECTIVE)
|
||||
put(JaiTokenTypes.NOTE, JaiColors.NOTE)
|
||||
|
||||
put(JaiTokenTypes.LINE_COMMENT, JaiColors.LINE_COMMENT)
|
||||
put(JaiTokenTypes.BLOCK_COMMENT, JaiColors.BLOCK_COMMENT)
|
||||
|
||||
put(JaiTokenTypes.LPAREN, JaiColors.PARENTHESES)
|
||||
put(JaiTokenTypes.RPAREN, JaiColors.PARENTHESES)
|
||||
put(JaiTokenTypes.LBRACE, JaiColors.BRACES)
|
||||
put(JaiTokenTypes.RBRACE, JaiColors.BRACES)
|
||||
put(JaiTokenTypes.LBRACKET, JaiColors.BRACKETS)
|
||||
put(JaiTokenTypes.RBRACKET, JaiColors.BRACKETS)
|
||||
put(JaiTokenTypes.SEMICOLON, JaiColors.SEMICOLON)
|
||||
put(JaiTokenTypes.COMMA, JaiColors.COMMA)
|
||||
put(JaiTokenTypes.DOUBLE_COMMA, JaiColors.COMMA)
|
||||
put(JaiTokenTypes.DOT, JaiColors.DOT)
|
||||
|
||||
put(JaiTokenTypes.TRIPLE_MINUS, JaiColors.UNINITIALIZED)
|
||||
put(JaiTokenTypes.DOLLAR, JaiColors.POLYMORPH)
|
||||
put(JaiTokenTypes.DOUBLE_DOLLAR, JaiColors.POLYMORPH)
|
||||
put(JaiTokenTypes.BACKTICK, JaiColors.BACKTICK)
|
||||
|
||||
put(TokenType.BAD_CHARACTER, JaiColors.BAD_CHARACTER)
|
||||
|
||||
// Everything else that is an operator or punctuation.
|
||||
JaiTokenTypes.OPERATORS.types.forEach { putIfAbsent(it, JaiColors.OPERATOR) }
|
||||
}
|
||||
|
||||
/** Every token type the highlighter knows how to colour. Used by tests. */
|
||||
val HIGHLIGHTED_TOKENS: Set<IElementType> get() = ATTRIBUTES.keys
|
||||
}
|
||||
}
|
||||
|
||||
class JaiSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
|
||||
override fun getSyntaxHighlighter(
|
||||
project: Project?,
|
||||
virtualFile: VirtualFile?,
|
||||
): SyntaxHighlighter = JaiSyntaxHighlighter()
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import com.intellij.psi.tree.IElementType
|
||||
* boundary never carries information. That makes the lexer restartable at any token,
|
||||
* which is what incremental re-highlighting needs.
|
||||
*/
|
||||
class JaiLexer : LexerBase() {
|
||||
open class JaiLexer : LexerBase() {
|
||||
private var buf: CharSequence = ""
|
||||
private var bufEnd = 0
|
||||
private var tokStart = 0
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
fieldName="INSTANCE"
|
||||
language="Jai"
|
||||
extensions="jai"/>
|
||||
|
||||
<colorSettingsPage implementation="dev.hgh.jai.highlighting.JaiColorSettingsPage"/>
|
||||
|
||||
<lang.syntaxHighlighterFactory
|
||||
language="Jai"
|
||||
implementationClass="dev.hgh.jai.highlighting.JaiSyntaxHighlighterFactory"/>
|
||||
</extensions>
|
||||
|
||||
</idea-plugin>
|
||||
|
||||
@@ -13,6 +13,16 @@ class JaiFileTypeTest : BasePlatformTestCase() {
|
||||
assertEquals(JaiFileType, type)
|
||||
}
|
||||
|
||||
fun testSyntaxHighlighterIsRegisteredForJai() {
|
||||
val highlighter =
|
||||
com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
|
||||
.getSyntaxHighlighter(JaiLanguage, null, null)
|
||||
assertTrue(
|
||||
"plugin.xml must register our highlighter, got $highlighter",
|
||||
highlighter is dev.hgh.jai.highlighting.JaiSyntaxHighlighter,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The *virtual* file already resolves to our type. The PSI file's language stays
|
||||
* plain text until a ParserDefinition is registered (phase 3) — asserting that here
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package dev.hgh.jai.highlighting
|
||||
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.psi.TokenType
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class JaiColorSettingsPageTest {
|
||||
private val page = JaiColorSettingsPage()
|
||||
|
||||
/**
|
||||
* The demo text is what a user sees when picking colours, so it must be valid Jai:
|
||||
* it has to tile cleanly and contain no bad characters.
|
||||
*/
|
||||
@Test
|
||||
fun demoTextLexesCleanly() {
|
||||
val text = page.demoText
|
||||
val lexer = JaiHighlightingLexer()
|
||||
lexer.start(text, 0, text.length, 0)
|
||||
val rebuilt = StringBuilder()
|
||||
var offset = 0
|
||||
while (true) {
|
||||
val type = lexer.tokenType ?: break
|
||||
assertEquals("gap or overlap in demo text", offset, lexer.tokenStart)
|
||||
assertTrue("empty token in demo text", lexer.tokenEnd > lexer.tokenStart)
|
||||
assertTrue(
|
||||
"demo text contains a bad character at ${lexer.tokenStart}",
|
||||
type != TokenType.BAD_CHARACTER,
|
||||
)
|
||||
rebuilt.append(text, lexer.tokenStart, lexer.tokenEnd)
|
||||
offset = lexer.tokenEnd
|
||||
lexer.advance()
|
||||
}
|
||||
assertEquals(text, rebuilt.toString())
|
||||
}
|
||||
|
||||
/** Every colour the highlighter can emit must be settable by the user. */
|
||||
@Test
|
||||
fun everyColorKeyIsExposedOnThePage() {
|
||||
val exposed: Set<TextAttributesKey> = page.attributeDescriptors.map { it.key }.toSet()
|
||||
val declared =
|
||||
JaiColors::class.java.declaredFields
|
||||
.filter { TextAttributesKey::class.java.isAssignableFrom(it.type) }
|
||||
.map {
|
||||
it.isAccessible = true
|
||||
it.get(JaiColors) as TextAttributesKey
|
||||
}
|
||||
val missing = declared.filterNot { it in exposed }
|
||||
assertEquals("colour keys missing from the settings page: $missing", emptyList<TextAttributesKey>(), missing)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoTextExercisesTheInterestingTokens() {
|
||||
val text = page.demoText
|
||||
val lexer = JaiHighlightingLexer()
|
||||
lexer.start(text, 0, text.length, 0)
|
||||
val seen = mutableSetOf<String>()
|
||||
while (true) {
|
||||
seen += (lexer.tokenType ?: break).toString()
|
||||
lexer.advance()
|
||||
}
|
||||
listOf(
|
||||
"Jai:HERE_STRING",
|
||||
"Jai:BLOCK_COMMENT",
|
||||
"Jai:LINE_COMMENT",
|
||||
"Jai:DIRECTIVE",
|
||||
"Jai:NOTE",
|
||||
"Jai:NUMBER",
|
||||
"Jai:STRING",
|
||||
"Jai:---",
|
||||
"Jai:.{",
|
||||
"Jai:.[",
|
||||
"Jai:BUILTIN_TYPE",
|
||||
"Jai:LOOP_VARIABLE",
|
||||
"Jai:struct",
|
||||
"Jai:$",
|
||||
).forEach { assertTrue("demo text never produces $it", it in seen) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package dev.hgh.jai.highlighting
|
||||
|
||||
import com.intellij.psi.TokenType
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import dev.hgh.jai.lexer.JaiLexer
|
||||
import dev.hgh.jai.lexer.JaiTokenTypes
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Phase 2 gate: the highlighter must have an attribute for every token type the lexer
|
||||
* can produce — a token with no mapping renders as plain text, which is invisible in a
|
||||
* screenshot review but obvious here.
|
||||
*/
|
||||
class JaiSyntaxHighlighterTest {
|
||||
private val highlighter = JaiSyntaxHighlighter()
|
||||
|
||||
/** Every token type declared in the token holder, by reflection over the object. */
|
||||
private fun allDeclaredTokenTypes(): List<IElementType> =
|
||||
JaiTokenTypes::class.java.declaredFields
|
||||
.filter { IElementType::class.java.isAssignableFrom(it.type) }
|
||||
.map {
|
||||
it.isAccessible = true
|
||||
it.get(JaiTokenTypes) as IElementType
|
||||
}
|
||||
|
||||
@Test
|
||||
fun everyTokenTypeHasAHighlight() {
|
||||
val unmapped =
|
||||
allDeclaredTokenTypes().filter {
|
||||
highlighter.getTokenHighlights(it).isEmpty()
|
||||
}
|
||||
assertEquals("token types with no text attributes: $unmapped", emptyList<IElementType>(), unmapped)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun highlightOnlyTokensAreMapped() {
|
||||
assertTrue(highlighter.getTokenHighlights(JaiHighlightingTokens.BUILTIN_TYPE).isNotEmpty())
|
||||
assertTrue(highlighter.getTokenHighlights(JaiHighlightingTokens.LOOP_VARIABLE).isNotEmpty())
|
||||
assertTrue(highlighter.getTokenHighlights(TokenType.BAD_CHARACTER).isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whitespaceIsNotHighlighted() {
|
||||
assertEquals(0, highlighter.getTokenHighlights(TokenType.WHITE_SPACE).size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builtinTypesAndLoopVariablesAreRefinedIdentifiers() {
|
||||
val types = lexTypes("x: int = 0; for v { print(it, it_index); } Foo :: struct {}")
|
||||
assertTrue(JaiHighlightingTokens.BUILTIN_TYPE in types)
|
||||
assertTrue(JaiHighlightingTokens.LOOP_VARIABLE in types)
|
||||
assertTrue(JaiTokenTypes.IDENT in types)
|
||||
|
||||
// ...but the parsing lexer still calls them plain identifiers.
|
||||
val parsing = JaiLexer()
|
||||
parsing.start("int", 0, 3, 0)
|
||||
assertEquals(JaiTokenTypes.IDENT, parsing.tokenType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun highlightingLexerCoversTheWholeCorpusWithNoUnhighlightedTokens() {
|
||||
val root = File(System.getProperty("user.home"), ".local/jai")
|
||||
org.junit.Assume.assumeTrue("Jai distribution not found at $root", root.isDirectory)
|
||||
|
||||
val seen = mutableSetOf<IElementType>()
|
||||
var files = 0
|
||||
root.walkTopDown().filter { it.isFile && it.extension == "jai" }.forEach { file ->
|
||||
files++
|
||||
val lexer = JaiHighlightingLexer()
|
||||
val text = file.readText()
|
||||
lexer.start(text, 0, text.length, 0)
|
||||
while (true) {
|
||||
val type = lexer.tokenType ?: break
|
||||
seen += type
|
||||
lexer.advance()
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue("no corpus files scanned", files > 0)
|
||||
val unmapped =
|
||||
seen.filter {
|
||||
it != TokenType.WHITE_SPACE && highlighter.getTokenHighlights(it).isEmpty()
|
||||
}
|
||||
assertEquals("corpus produced unhighlighted tokens: $unmapped", emptyList<IElementType>(), unmapped)
|
||||
println("Highlighter: $files corpus files produced ${seen.size} distinct token types, all mapped.")
|
||||
}
|
||||
|
||||
private fun lexTypes(code: String): Set<IElementType> {
|
||||
val lexer = JaiHighlightingLexer()
|
||||
lexer.start(code, 0, code.length, 0)
|
||||
val out = mutableSetOf<IElementType>()
|
||||
while (true) {
|
||||
out += lexer.tokenType ?: break
|
||||
lexer.advance()
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user