diff --git a/CHANGELOG.md b/CHANGELOG.md index f544ed425..6cc0ebf97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Released on ... - TODO (see [#NNN](https://github.com/JetBrains-Research/snakecharm/issues/NNN)) ### Added +- Color Settings Page (see [#431](https://github.com/JetBrains-Research/snakecharm/issues/431)) - Inspection: highlights 'use rule' section which overrides several rules as one (see [#411](https://github.com/JetBrains-Research/snakecharm/issues/411)) - Weak warnings for unused 'log' sections in 'use rule' (see [#414](https://github.com/JetBrains-Research/snakecharm/issues/414)) - Weak warnings for unused 'log' sections (see [#300](https://github.com/JetBrains-Research/snakecharm/issues/300)) diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkColorSettingsPage.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkColorSettingsPage.kt new file mode 100644 index 000000000..17504bef8 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkColorSettingsPage.kt @@ -0,0 +1,140 @@ +package com.jetbrains.snakecharm.lang.highlighter + +import com.intellij.codeHighlighting.RainbowHighlighter +import com.intellij.lang.Language +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.fileTypes.SyntaxHighlighter +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory +import com.intellij.openapi.options.colors.AttributesDescriptor +import com.intellij.openapi.options.colors.ColorDescriptor +import com.intellij.openapi.options.colors.RainbowColorSettingsPage +import com.jetbrains.python.PythonLanguage +import com.jetbrains.python.highlighting.PyRainbowVisitor +import com.jetbrains.python.psi.LanguageLevel +import com.jetbrains.snakecharm.SnakemakeBundle +import com.jetbrains.snakecharm.SnakemakeIcons +import com.jetbrains.snakecharm.lang.SnakemakeLanguageDialect +import com.jetbrains.snakecharm.stringLanguage.lang.highlighter.SmkSLSyntaxHighlighter +import javax.swing.Icon + +class SmkColorSettingsPage : RainbowColorSettingsPage { + override fun getAttributeDescriptors(): Array = arrayOf( + AttributesDescriptor( + SnakemakeBundle.message("smk.color.keyword"), + SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.definition"), + SnakemakeSyntaxHighlighterFactory.SMK_FUNC_DEFINITION + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.subsection"), + SnakemakeSyntaxHighlighterFactory.SMK_DECORATOR + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.run"), + SnakemakeSyntaxHighlighterFactory.SMK_PREDEFINED_DEFINITION + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.keyword.arg"), + SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD_ARGUMENT + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.string.text"), + SnakemakeSyntaxHighlighterFactory.SMK_TEXT + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.string.tqs"), + SnakemakeSyntaxHighlighterFactory.SMK_TRIPLE_QUOTED_STRING + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.string.SL.content"), + SmkSLSyntaxHighlighter.STRING_CONTENT + ), + AttributesDescriptor(SnakemakeBundle.message("smk.color.string.SL.braces"), SmkSLSyntaxHighlighter.BRACES), + AttributesDescriptor(SnakemakeBundle.message("smk.color.string.SL.comma"), SmkSLSyntaxHighlighter.COMMA), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.string.SL.format"), + SmkSLSyntaxHighlighter.FORMAT_SPECIFIER + ), + AttributesDescriptor(SnakemakeBundle.message("smk.color.string.SL.key"), SmkSLSyntaxHighlighter.ACCESS_KEY), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.string.SL.reference"), + SmkSLSyntaxHighlighter.IDENTIFIER + ), + AttributesDescriptor( + SnakemakeBundle.message("smk.color.string.SL.wildcard"), + SmkSLSyntaxHighlighter.HIGHLIGHTING_WILDCARDS_KEY + ) + ) + + override fun getColorDescriptors(): Array = ColorDescriptor.EMPTY_ARRAY + + override fun getDisplayName(): String = SnakemakeBundle.message("snakemake.settings.name") + + override fun getIcon(): Icon = SnakemakeIcons.FILE + + override fun getHighlighter(): SyntaxHighlighter { + val lang = SnakemakeLanguageDialect.baseLanguage ?: PythonLanguage.getInstance() + val factory = SyntaxHighlighterFactory.LANGUAGE_FACTORY.forLanguage(lang) + if (factory is SnakemakeSyntaxHighlighterFactory) { + return factory.getSyntaxHighlighterForLanguageLevel(LanguageLevel.getLatest()) + } + return factory.getSyntaxHighlighter(null, null) + } + + override fun getDemoText(): String = + """ + configfile: "config/config.yaml" + localrules: NAME + """.trimIndent() + + "\nrule NAME:\n" + + " \"\"\"\n" + + " Syntax Highlighting Demo" + + RainbowHighlighter.generatePaletteExample("\n ") + + "\n \"\"\"\n" + + """ + input: + "file_{number}.txt", + arg = "file_1.txt" + output: + "file_{number}.txt" + run: + x = 2 + shell("touch {output}") + + number = 0.451 # Python elements are configured in Python color settings + + use rule NAME as NAME_2 with: + message: + "Float number: {number:2f}" + """.trimIndent() + + override fun getAdditionalHighlightingTagToDescriptorMap(): MutableMap = + mutableMapOf().also { + it["keyword"] = SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD + it["identifiers"] = SnakemakeSyntaxHighlighterFactory.SMK_FUNC_DEFINITION + it["sectionName"] = SnakemakeSyntaxHighlighterFactory.SMK_DECORATOR + it["run"] = SnakemakeSyntaxHighlighterFactory.SMK_PREDEFINED_DEFINITION + it["text"] = SnakemakeSyntaxHighlighterFactory.SMK_TEXT + it["TQS"] = SnakemakeSyntaxHighlighterFactory.SMK_TRIPLE_QUOTED_STRING + it["keywordArg"] = SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD_ARGUMENT + + it["injectedText"] = SmkSLSyntaxHighlighter.STRING_CONTENT + it["braces"] = SmkSLSyntaxHighlighter.BRACES + it["comma"] = SmkSLSyntaxHighlighter.COMMA + it["formatSpecifier"] = SmkSLSyntaxHighlighter.FORMAT_SPECIFIER + it["accessKey"] = SmkSLSyntaxHighlighter.ACCESS_KEY + it["reference"] = SmkSLSyntaxHighlighter.IDENTIFIER + it["wildcard"] = SmkSLSyntaxHighlighter.HIGHLIGHTING_WILDCARDS_KEY + + it["localVar"] = DefaultLanguageHighlighterColors.LOCAL_VARIABLE + it.putAll(RainbowHighlighter.createRainbowHLM()) + } + + override fun isRainbowType(type: TextAttributesKey?): Boolean = + PyRainbowVisitor.Holder.HIGHLIGHTING_KEYS.contains(type) + + override fun getLanguage(): Language = SnakemakeLanguageDialect +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt index b2cff3c7a..13d4bc7c8 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt @@ -1,8 +1,6 @@ package com.jetbrains.snakecharm.lang.highlighter import com.jetbrains.python.PyTokenTypes -import com.jetbrains.python.highlighting.PyHighlighter -import com.jetbrains.python.highlighting.PyHighlighter.PY_FUNC_DEFINITION import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes import com.jetbrains.snakecharm.lang.psi.* import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkElementTypes @@ -36,10 +34,10 @@ object SmkSyntaxAnnotator : SmkAnnotator() { SmkTokenTypes.RULE_KEYWORD, SmkTokenTypes.SMK_FROM_KEYWORD, SmkTokenTypes.SMK_AS_KEYWORD, SmkTokenTypes.SMK_WITH_KEYWORD -> addHighlightingAnnotation( next, - PyHighlighter.PY_KEYWORD + SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD ) SmkElementTypes.USE_NAME_IDENTIFIER, PyTokenTypes.IDENTIFIER -> addHighlightingAnnotation( - next, PY_FUNC_DEFINITION + next, SnakemakeSyntaxHighlighterFactory.SMK_FUNC_DEFINITION ) PyTokenTypes.COLON -> done = true } @@ -61,7 +59,7 @@ object SmkSyntaxAnnotator : SmkAnnotator() { override fun visitSmkRunSection(st: SmkRunSection) { st.getSectionKeywordNode()?.let { - addHighlightingAnnotation(it, PyHighlighter.PY_PREDEFINED_DEFINITION) + addHighlightingAnnotation(it, SnakemakeSyntaxHighlighterFactory.SMK_PREDEFINED_DEFINITION) } } @@ -85,19 +83,29 @@ object SmkSyntaxAnnotator : SmkAnnotator() { highlightWorkflowSection(ruleLike) ruleLike.nameIdentifier?.let { nameElement -> - addHighlightingAnnotation(nameElement, PY_FUNC_DEFINITION) + addHighlightingAnnotation(nameElement, SnakemakeSyntaxHighlighterFactory.SMK_FUNC_DEFINITION) } } private fun highlightWorkflowSection(st: SmkSection) { st.getSectionKeywordNode()?.let { - addHighlightingAnnotation(it, PyHighlighter.PY_KEYWORD) + addHighlightingAnnotation(it, SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD) } } private fun highlightRuleLikeSection(st: SmkSection) { st.getSectionKeywordNode()?.let { - addHighlightingAnnotation(it, PyHighlighter.PY_DECORATOR) + addHighlightingAnnotation(it, SnakemakeSyntaxHighlighterFactory.SMK_DECORATOR) + } + if (st is SmkArgsSection) { + st.keywordArguments?.forEach { + it.keywordNode?.psi?.let { name -> + addHighlightingAnnotation( + name, + SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD_ARGUMENT + ) + } + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkWildcardsAnnotator.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkWildcardsAnnotator.kt index aabcdb4c5..095ede21c 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkWildcardsAnnotator.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkWildcardsAnnotator.kt @@ -6,7 +6,7 @@ import com.jetbrains.python.psi.types.TypeEvalContext import com.jetbrains.snakecharm.lang.psi.impl.SmkPsiUtil import com.jetbrains.snakecharm.lang.psi.types.SmkWildcardsType import com.jetbrains.snakecharm.lang.validation.SmkAnnotator -import com.jetbrains.snakecharm.stringLanguage.lang.highlighter.SmkSLWildcardsAnnotator.HIGHLIGHTING_WILDCARDS_KEY +import com.jetbrains.snakecharm.stringLanguage.lang.highlighter.SmkSLSyntaxHighlighter.Companion.HIGHLIGHTING_WILDCARDS_KEY object SmkWildcardsAnnotator : SmkAnnotator() { override fun visitPyReferenceExpression(expr: PyReferenceExpression) { diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SnakemakeSyntaxHighlighterFactory.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SnakemakeSyntaxHighlighterFactory.kt index e50788322..e3675ddc2 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SnakemakeSyntaxHighlighterFactory.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SnakemakeSyntaxHighlighterFactory.kt @@ -1,10 +1,14 @@ package com.jetbrains.snakecharm.lang.highlighter +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.fileTypes.SyntaxHighlighter import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.tree.IElementType import com.intellij.util.containers.FactoryMap +import com.jetbrains.python.PyTokenTypes import com.jetbrains.python.highlighting.PyHighlighter import com.jetbrains.python.psi.LanguageLevel import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher @@ -14,18 +18,57 @@ import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher * @date 2018-12-31 */ class SnakemakeSyntaxHighlighterFactory : SyntaxHighlighterFactory() { + companion object { + val SMK_KEYWORD = TextAttributesKey.createTextAttributesKey( + "SMK_KEYWORD", + DefaultLanguageHighlighterColors.KEYWORD + ) + val SMK_FUNC_DEFINITION = TextAttributesKey.createTextAttributesKey( + "SMK_FUNC_DEFINITION", + DefaultLanguageHighlighterColors.FUNCTION_DECLARATION + ) + val SMK_DECORATOR = TextAttributesKey.createTextAttributesKey( + "SMK_DECORATOR", + DefaultLanguageHighlighterColors.METADATA + ) + val SMK_PREDEFINED_DEFINITION: TextAttributesKey = + PyHighlighter.PY_PREDEFINED_DEFINITION // IDK why, but explicit creating via '.createText...' works improperly + val SMK_KEYWORD_ARGUMENT = TextAttributesKey.createTextAttributesKey( + "SMK_KEYWORD_ARGUMENT", + DefaultLanguageHighlighterColors.PARAMETER + ) + val SMK_TEXT = TextAttributesKey.createTextAttributesKey("SMK_TEXT", DefaultLanguageHighlighterColors.STRING) + val SMK_TRIPLE_QUOTED_STRING = TextAttributesKey.createTextAttributesKey( + "SMK_TRIPLE_QUOTED_STRING", + DefaultLanguageHighlighterColors.STRING + ) + } + private val myMap = FactoryMap.create { key -> object : PyHighlighter(key) { + override fun getTokenHighlights(tokenType: IElementType?): Array { + return when (tokenType) { + PyTokenTypes.SINGLE_QUOTED_UNICODE -> arrayOf(SMK_TEXT) + PyTokenTypes.TRIPLE_QUOTED_UNICODE -> arrayOf(SMK_TRIPLE_QUOTED_STRING) + else -> super.getTokenHighlights(tokenType) + } + } + override fun createHighlightingLexer(level: LanguageLevel) = SnakemakeHighlightingLexer(level) } } override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter { val level = when { - project != null && virtualFile != null -> PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(project, virtualFile) + project != null && virtualFile != null -> PythonLanguageLevelPusher.getLanguageLevelForVirtualFile( + project, + virtualFile + ) else -> LanguageLevel.getDefault() } - return myMap[level]!! + return getSyntaxHighlighterForLanguageLevel(level) } + + fun getSyntaxHighlighterForLanguageLevel(level: LanguageLevel): SyntaxHighlighter = myMap[level]!! } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLSyntaxHighlighter.kt b/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLSyntaxHighlighter.kt index b2e101f0e..c22bb7230 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLSyntaxHighlighter.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLSyntaxHighlighter.kt @@ -1,5 +1,6 @@ package com.jetbrains.snakecharm.stringLanguage.lang.highlighter +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.SyntaxHighlighterBase @@ -10,21 +11,25 @@ import com.jetbrains.snakecharm.stringLanguage.lang.parser.SmkSLTokenTypes class SmkSLSyntaxHighlighter : SyntaxHighlighterBase() { companion object { - val BRACES = arrayOf(createTextAttributesKey("SMKSL_BRACES", PyHighlighter.PY_FSTRING_FRAGMENT_BRACES)) - val COMMA = arrayOf(createTextAttributesKey("SMKSL_COMMA", PyHighlighter.PY_FSTRING_FRAGMENT_COLON)) - val STRING_CONTENT = arrayOf(createTextAttributesKey("SMKSL_STRING_CONTENT", PyHighlighter.PY_BYTE_STRING)) - val FORMAT_SPECIFIER = arrayOf(createTextAttributesKey("SMKSL_FORMAT_SPECIFIER", PyHighlighter.PY_NUMBER)) - val ACCESS_KEY = arrayOf(createTextAttributesKey("SMKSL_ACCESS_KEY", PyHighlighter.PY_KEYWORD_ARGUMENT)) + val BRACES = createTextAttributesKey("SMKSL_BRACES", PyHighlighter.PY_FSTRING_FRAGMENT_BRACES) + val COMMA = createTextAttributesKey("SMKSL_COMMA", PyHighlighter.PY_FSTRING_FRAGMENT_COLON) + val STRING_CONTENT = createTextAttributesKey("SMKSL_STRING_CONTENT", PyHighlighter.PY_BYTE_STRING) + val FORMAT_SPECIFIER = createTextAttributesKey("SMKSL_FORMAT_SPECIFIER", PyHighlighter.PY_NUMBER) + val ACCESS_KEY = createTextAttributesKey("SMKSL_ACCESS_KEY", PyHighlighter.PY_KEYWORD_ARGUMENT) + val IDENTIFIER = createTextAttributesKey("SMKSL_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER) + val HIGHLIGHTING_WILDCARDS_KEY = + createTextAttributesKey("SMKSL_WILDCARD", DefaultLanguageHighlighterColors.NUMBER) } override fun getTokenHighlights(tokenType: IElementType?): Array = when { tokenType === SmkSLTokenTypes.LBRACE || - tokenType === SmkSLTokenTypes.RBRACE -> BRACES - tokenType === SmkSLTokenTypes.COMMA -> COMMA - tokenType === SmkSLTokenTypes.STRING_CONTENT -> STRING_CONTENT - tokenType === SmkSLTokenTypes.FORMAT_SPECIFIER -> FORMAT_SPECIFIER - tokenType === SmkSLTokenTypes.ACCESS_KEY -> ACCESS_KEY + tokenType === SmkSLTokenTypes.RBRACE -> arrayOf(BRACES) + tokenType === SmkSLTokenTypes.COMMA -> arrayOf(COMMA) + tokenType === SmkSLTokenTypes.STRING_CONTENT -> arrayOf(STRING_CONTENT) + tokenType === SmkSLTokenTypes.FORMAT_SPECIFIER -> arrayOf(FORMAT_SPECIFIER) + tokenType === SmkSLTokenTypes.ACCESS_KEY -> arrayOf(ACCESS_KEY) + tokenType === SmkSLTokenTypes.IDENTIFIER -> arrayOf(IDENTIFIER) else -> emptyArray() } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLWildcardsAnnotator.kt b/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLWildcardsAnnotator.kt index a5ec6a932..6ec872165 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLWildcardsAnnotator.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/highlighter/SmkSLWildcardsAnnotator.kt @@ -1,13 +1,12 @@ package com.jetbrains.snakecharm.stringLanguage.lang.highlighter import com.intellij.lang.annotation.HighlightSeverity -import com.jetbrains.python.highlighting.PyHighlighter import com.jetbrains.python.psi.types.TypeEvalContext import com.jetbrains.snakecharm.lang.psi.types.SmkWildcardsType +import com.jetbrains.snakecharm.stringLanguage.lang.highlighter.SmkSLSyntaxHighlighter.Companion.HIGHLIGHTING_WILDCARDS_KEY import com.jetbrains.snakecharm.stringLanguage.lang.psi.SmkSLReferenceExpressionImpl object SmkSLWildcardsAnnotator : AbstractSmkSLAnnotator() { - val HIGHLIGHTING_WILDCARDS_KEY = PyHighlighter.PY_NUMBER!! override fun visitSmkSLReferenceExpression(expr: SmkSLReferenceExpressionImpl) { val exprIdentifier = expr.nameIdentifier diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index c4151026c..e95ffdc0a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -72,6 +72,7 @@ + diff --git a/src/main/resources/SnakemakeBundle.properties b/src/main/resources/SnakemakeBundle.properties index cb43c6bf9..373ca326a 100644 --- a/src/main/resources/SnakemakeBundle.properties +++ b/src/main/resources/SnakemakeBundle.properties @@ -275,4 +275,21 @@ smk.framework.detector.added.to.module=Snakemake support will be enabled for the # Wrapper wrappers.parsing.progress.collecting.data=Loading or collecting snakemake wrappers info smk.framework.configurable.panel.sdk=Python Interpreter -smk.framework.configurable.panel.sdk.hint=Select Python interpreter with snakemake module installed.This setting doesn't affect your pipeline runtime behavior and is only used for SnakeCharm plugin code insight features. \ No newline at end of file +smk.framework.configurable.panel.sdk.hint=Select Python interpreter with snakemake module installed.This setting doesn't affect your pipeline runtime behavior and is only used for SnakeCharm plugin code insight features. + +####################### +# Color settings +smk.color.keyword=Keyword +smk.color.definition=Name definition +smk.color.subsection=Subsection +smk.color.run=Run section +smk.color.keyword.arg=Keyword argument +smk.color.string.text=String//Text +smk.color.string.tqs=String//Triple quoted string +smk.color.string.SL.content=String//SnakemakeSL//String content +smk.color.string.SL.braces=String//SnakemakeSL//Braces +smk.color.string.SL.comma=String//SnakemakeSL//Comma +smk.color.string.SL.format=String//SnakemakeSL//Format specifier +smk.color.string.SL.key=String//SnakemakeSL//Access key +smk.color.string.SL.reference=String//SnakemakeSL//Reference +smk.color.string.SL.wildcard=String//SnakemakeSL//Wildcard \ No newline at end of file diff --git a/src/test/kotlin/features/glue/ActionsSteps.kt b/src/test/kotlin/features/glue/ActionsSteps.kt index f04910066..0d0689ec5 100644 --- a/src/test/kotlin/features/glue/ActionsSteps.kt +++ b/src/test/kotlin/features/glue/ActionsSteps.kt @@ -25,6 +25,9 @@ import com.intellij.util.containers.ContainerUtil import com.jetbrains.snakecharm.FakeSnakemakeInjector import com.jetbrains.snakecharm.codeInsight.completion.wrapper.SmkWrapperCrawler import com.jetbrains.snakecharm.inspections.SmkUnrecognizedSectionInspection +import com.jetbrains.snakecharm.lang.highlighter.SmkColorSettingsPage +import com.jetbrains.snakecharm.lang.highlighter.SnakemakeSyntaxHighlighterFactory +import com.jetbrains.snakecharm.stringLanguage.lang.highlighter.SmkSLSyntaxHighlighter import features.glue.SnakemakeWorld.findPsiElementUnderCaret import features.glue.SnakemakeWorld.fixture import features.glue.SnakemakeWorld.myFixture @@ -82,16 +85,16 @@ class ActionsSteps { val document = PsiDocumentManager.getInstance(fixture.project).getDocument(psiFile)!! val pos = document.text.indexOf(signature) assertTrue( - pos >= 0, - "Signature <$signature> wasn't found in the file ${psiFile.name}." + pos >= 0, + "Signature <$signature> wasn't found in the file ${psiFile.name}." ) val reference = fixture.getReferenceAtCaretPosition() assertNotNull(reference, message = "There is no reference at the caret position") assertEquals( - signature, - reference.canonicalText, - message = "Expected highlighted text wasn't equal to the actual one." + signature, + reference.canonicalText, + message = "Expected highlighted text wasn't equal to the actual one." ) } } @@ -114,6 +117,54 @@ class ActionsSteps { } } + @Then("^I expect the tag highlighting to be the same as the annotator highlighting$") + fun iExpectTheTagHighlightingToBeTheSameAsTheAnnotatorHighlighting() { + val level = "info" + val attributesToCheck = arrayOf( + // Currently, IDK how to check all used tags + // Snakemake: + SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD, + SnakemakeSyntaxHighlighterFactory.SMK_FUNC_DEFINITION, + SnakemakeSyntaxHighlighterFactory.SMK_DECORATOR, + SnakemakeSyntaxHighlighterFactory.SMK_PREDEFINED_DEFINITION, + SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD_ARGUMENT, + // SnakemakeSL: + SmkSLSyntaxHighlighter.HIGHLIGHTING_WILDCARDS_KEY + ) + val page = SmkColorSettingsPage() + val fixture = fixture() + val tagPattern = Pattern.compile("<(.+)>(\\w+)") + val itemPattern = Pattern.compile(">(\\w+)<") + val linesPage = page.demoText.lines() + var problemsCounter = 0 + for (ind in 0 until linesPage.count()) { + val lineMatcher = tagPattern.matcher(linesPage[ind]) + while (lineMatcher.find()) { + val itemMatcher = itemPattern.matcher(lineMatcher.group(0)) + if (itemMatcher.find()) { + val item = itemMatcher.group(1) + val tag = lineMatcher.group(1) + val attributesKey = page.additionalHighlightingTagToDescriptorMap[tag] ?: continue + if (attributesKey in attributesToCheck) { + ++problemsCounter + wrapTextInHighlightingTags( + level, + item, + fixture.editor.document.text.lines()[ind], + attributesKey.externalName + ) + } + } + } + } + if (SnakemakeWorld.myInspectionProblemsCounts == null) { + SnakemakeWorld.myInspectionProblemsCounts = mutableMapOf(level to problemsCounter) + } else { + val counts = SnakemakeWorld.myInspectionProblemsCounts!! + counts[level] = problemsCounter + } + } + private fun updatedInspectionProblemsCounter(level: String) { if (SnakemakeWorld.myInspectionProblemsCounts == null) { SnakemakeWorld.myInspectionProblemsCounts = mutableMapOf(level to 1) @@ -125,11 +176,11 @@ class ActionsSteps { } private fun wrapTextInHighlightingTags( - highlightingLevel: String, - text: String, - signature: String, - message: String, - searchInTags: Boolean = false + highlightingLevel: String, + text: String, + signature: String, + message: String, + searchInTags: Boolean = false ) { require(!Pattern.compile("(^|[^\\\\])\"").matcher(message).find()) { "Quotes (\") should be escaped (with \\) in message: $message" @@ -151,16 +202,16 @@ class ActionsSteps { updatedInspectionProblemsCounter(highlightingLevel) ApplicationManager.getApplication().invokeAndWait { - performAction(project, { + performAction(project) { fixture.editor.document.replaceString(startPos, startPos + text.length, newText) - }) + } } } private fun findTextPositionInsideTags( - tag: String, - text: String, - fullText: String + tag: String, + text: String, + fullText: String ): Int { val textInsideTagsPattern = Pattern.compile("<$tag descr=.+?>([^<>]+)") val matcher = textInsideTagsPattern.matcher(fullText) @@ -168,15 +219,15 @@ class ActionsSteps { } private fun findTextPositionWithSignature( - text: String, - signature: String, - document: Document, - psiFile: PsiFile + text: String, + signature: String, + document: Document, + psiFile: PsiFile ): Int { val pos = document.text.indexOf(signature) assertTrue( - pos >= 0, - "Signature <$signature> wasn't found in the file ${psiFile.name}." + pos >= 0, + "Signature <$signature> wasn't found in the file ${psiFile.name}." ) val posInSignature = signature.indexOf(text) @@ -194,9 +245,10 @@ class ActionsSteps { checkHighlighting(type, true) } - fun checkHighlighting(level: String, ignoreExtra: Boolean) { + private fun checkHighlighting(level: String, ignoreExtra: Boolean) { val problemsCounts = SnakemakeWorld.myInspectionProblemsCounts - SnakemakeWorld.myInspectionProblemsCounts = null // reset counter after check in order to validate in teardown that assertion step was called + SnakemakeWorld.myInspectionProblemsCounts = + null // reset counter after check in order to validate in teardown that assertion step was called requireNotNull(problemsCounts) { "No expected inspections steps in test. Add 'I expect no inspection ..' step if no inspection" + @@ -261,15 +313,15 @@ class ActionsSteps { val docPopupText = myGeneratedDocPopupText assertNotNull(docPopupText) assertTrue( - text in docPopupText, - "Expected <$text> to be in <$docPopupText>" + text in docPopupText, + "Expected <$text> to be in <$docPopupText>" ) } @When("^I invoke rename with name \"([^\"]+)\"$") fun iInvokeRenameWithName(newName: String) { ApplicationManager.getApplication().invokeAndWait { - fixture().renameElementAtCaret(newName) + fixture().renameElementAtCaret(newName) } } @@ -312,13 +364,12 @@ class ActionsSteps { "First call step: I check highlighting ..." } val allQuickFixes = fixture().getAllQuickFixes() - val quickFix = allQuickFixes.firstOrNull { it.familyName == quickFixFamilyName } + return allQuickFixes.firstOrNull { it.familyName == quickFixFamilyName } ?: fail( "Cannot find quickfix '${quickFixFamilyName}', available quick fixes:[\n${ allQuickFixes.joinToString(separator = "\n") { it.familyName } }\n]" ) - return quickFix } @Given("^I emulate quick fix apply: ignore unresolved item '(.*)'") @@ -326,7 +377,7 @@ class ActionsSteps { val list = (LocalInspectionEP.LOCAL_INSPECTION.extensionList .first { it.shortName == "SmkUnrecognizedSectionInspection" } .instance as SmkUnrecognizedSectionInspection).ignoredItems - if (sectionName in list){ + if (sectionName in list) { fail("Section \"${sectionName}\" is already here, but it shouldn't be") } list.add(sectionName) @@ -389,19 +440,19 @@ class ActionsSteps { } @Then("^I inject SmkSL at a caret") - fun iInjectSmkSLAtCaret() { - ApplicationManager.getApplication().invokeAndWait { - val fixture = fixture() - val offsetUnderCaret = SnakemakeWorld.getOffsetUnderCaret() - val elementAtOffset = fixture.file.findElementAt(offsetUnderCaret) - requireNotNull(elementAtOffset) { "No element at a caret offset: $offsetUnderCaret" } - - // auto unregister when fixture is disposed - InjectedLanguageManager.getInstance(fixture.project).registerMultiHostInjector( - FakeSnakemakeInjector(offsetUnderCaret), fixture.projectDisposable - ) - } - } + fun iInjectSmkSLAtCaret() { + ApplicationManager.getApplication().invokeAndWait { + val fixture = fixture() + val offsetUnderCaret = SnakemakeWorld.getOffsetUnderCaret() + val elementAtOffset = fixture.file.findElementAt(offsetUnderCaret) + requireNotNull(elementAtOffset) { "No element at a caret offset: $offsetUnderCaret" } + + // auto unregister when fixture is disposed + InjectedLanguageManager.getInstance(fixture.project).registerMultiHostInjector( + FakeSnakemakeInjector(offsetUnderCaret), fixture.projectDisposable + ) + } + } @Given("^I invoke (EditorCodeBlockStart|EditorCodeBlockEnd) action$") fun iInvokeCodeBlockSelectionAction(actionId: String) { @@ -455,8 +506,8 @@ class ActionsSteps { } private fun findTargetElementFor(element: PsiElement, editor: Editor) = - DocumentationManager.getInstance(element.project) - .findTargetElement(editor, element.containingFile, element) + DocumentationManager.getInstance(element.project) + .findTargetElement(editor, element.containingFile, element) private fun generateDocumentation(generateQuickDoc: Boolean) { ApplicationManager.getApplication().invokeAndWait { @@ -469,18 +520,18 @@ class ActionsSteps { myGeneratedDocPopupText = when { generateQuickDoc -> documentationProvider.getQuickNavigateInfo( - targetElement, element + targetElement, element ) else -> documentationProvider.generateDoc( - targetElement, element + targetElement, element ) } } } @When("^Parse wrapper args for \"meta.yaml\" and \"wrapper.(py|r)\" result is:$") - fun checkWrapperArgsParsing(ext: String, text :String) { - ApplicationManager.getApplication().invokeAndWait() { + fun checkWrapperArgsParsing(ext: String, text: String) { + ApplicationManager.getApplication().invokeAndWait { val fixture = fixture() val wrapperFileContent = fixture.findFileInTempDir("wrapper.$ext")?.let { @@ -496,17 +547,17 @@ class ActionsSteps { ) val args = info.args - val mapped = args.keys.sorted().map { key -> + val mapped = args.keys.sorted().joinToString(separator = "\n") { key -> val values = args[key]!! "$key:${values.joinToString(", ", prefix = "(", postfix = ")") { "'$it'" }}" - }.joinToString(separator="\n") + } Assert.assertEquals(StringUtil.convertLineSeparators(text.trim()), mapped.trim()) } } @Then("^I check ignored element <([^>]+)>") - fun checkIgnoredElementInInspectionList(el : String){ + fun checkIgnoredElementInInspectionList(el: String) { Assert.assertTrue(el in (LocalInspectionEP.LOCAL_INSPECTION.extensionList .first { it.shortName == "SmkUnrecognizedSectionInspection" } .instance as SmkUnrecognizedSectionInspection).ignoredItems) @@ -516,7 +567,7 @@ class ActionsSteps { fun performAction(project: Project, action: Runnable) { ApplicationManager.getApplication().runWriteAction { CommandProcessor.getInstance().executeCommand( - project, action, "SnakeCharmTestCmd", null + project, action, "SnakeCharmTestCmd", null ) } } diff --git a/src/test/kotlin/features/glue/FilesSteps.kt b/src/test/kotlin/features/glue/FilesSteps.kt index f80256854..370775234 100644 --- a/src/test/kotlin/features/glue/FilesSteps.kt +++ b/src/test/kotlin/features/glue/FilesSteps.kt @@ -4,6 +4,7 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.util.text.StringUtil +import com.jetbrains.snakecharm.lang.highlighter.SmkColorSettingsPage import io.cucumber.java.en.Then import io.cucumber.java.en.Given import junit.framework.TestCase.assertEquals @@ -33,6 +34,13 @@ class FilesSteps { createAndAddFile(name, text) } + @Given("^I open a color settings page text$") + fun iOpenAColorSettingsPAge() { + val page = SmkColorSettingsPage() + createAndAddFile("ColorSettingsPageDemo.smk", page.demoText.replace(Regex(""), "")) + } + + @Then("^the file \"(.+)\" should have text$") fun theFileShouldHaveText(path: String, text: String) { ApplicationManager.getApplication().runReadAction { diff --git a/src/test/resources/features/highlighting/color_settings_page.feature b/src/test/resources/features/highlighting/color_settings_page.feature new file mode 100644 index 000000000..847c0ae08 --- /dev/null +++ b/src/test/resources/features/highlighting/color_settings_page.feature @@ -0,0 +1,7 @@ +Feature: Color Settings Page + + Scenario: Checks that color settings page tags have the same highlighting with Smk annotators + Given a snakemake project + Given I open a color settings page text + Then I expect the tag highlighting to be the same as the annotator highlighting + When I check highlighting infos ignoring extra highlighting \ No newline at end of file diff --git a/src/test/resources/features/highlighting/smk_syntax_annotator.feature b/src/test/resources/features/highlighting/smk_syntax_annotator.feature index 0f9a1defa..62d6dbbe5 100644 --- a/src/test/resources/features/highlighting/smk_syntax_annotator.feature +++ b/src/test/resources/features/highlighting/smk_syntax_annotator.feature @@ -9,7 +9,7 @@ Feature: Annotate additional syntax """ Then I expect inspection info on <
> with message """ - PY.KEYWORD + SMK_KEYWORD """ When I check highlighting infos Examples: @@ -31,11 +31,11 @@ Feature: Annotate additional syntax """ Then I expect inspection info on <> with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.FUNC_DEFINITION + SMK_FUNC_DEFINITION """ Then I expect inspection info on <
> with message """ @@ -43,13 +43,13 @@ Feature: Annotate additional syntax """ When I check highlighting infos Examples: - | rule_like | section | text | highlighting | - | rule | input | "file.txt" | PY.DECORATOR | - | rule | new_unrecognized_section | "file.txt" | PY.DECORATOR | - | checkpoint | input | "file.txt" | PY.DECORATOR | - | checkpoint | new_unrecognized_section | "file.txt" | PY.DECORATOR | - | subworkflow | snakefile | "file.txt" | PY.DECORATOR | - | subworkflow | new_unrecognized_section | "file.txt" | PY.DECORATOR | + | rule_like | section | text | highlighting | + | rule | input | "file.txt" | SMK_DECORATOR | + | rule | new_unrecognized_section | "file.txt" | SMK_DECORATOR | + | checkpoint | input | "file.txt" | SMK_DECORATOR | + | checkpoint | new_unrecognized_section | "file.txt" | SMK_DECORATOR | + | subworkflow | snakefile | "file.txt" | SMK_DECORATOR | + | subworkflow | new_unrecognized_section | "file.txt" | SMK_DECORATOR | Scenario Outline: Annotate Rules and Checkpoints Given a snakemake project @@ -60,11 +60,11 @@ Feature: Annotate additional syntax """ Then I expect inspection info on <> with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.FUNC_DEFINITION + SMK_FUNC_DEFINITION """ Then I expect inspection info on <
> with message """ @@ -73,31 +73,31 @@ Feature: Annotate additional syntax When I check highlighting infos Examples: | rule_like | section | text | highlighting | - | rule | output | "file.txt" | PY.DECORATOR | - | rule | input | "file.txt" | PY.DECORATOR | - | rule | params | "file.txt" | PY.DECORATOR | - | rule | log | "file.txt" | PY.DECORATOR | - | rule | resources | foo | PY.DECORATOR | - | rule | version | "" | PY.DECORATOR | - | rule | cache | "" | PY.DECORATOR | - | rule | message | "" | PY.DECORATOR | - | rule | threads | "" | PY.DECORATOR | - | rule | singularity | "" | PY.DECORATOR | - | rule | priority | "" | PY.DECORATOR | - | rule | benchmark | "" | PY.DECORATOR | - | rule | wildcard_constraints | "" | PY.DECORATOR | - | rule | group | "" | PY.DECORATOR | - | rule | envmodules | "" | PY.DECORATOR | - | rule | shadow | "" | PY.DECORATOR | - | rule | conda | "" | PY.DECORATOR | - | rule | cwl | "" | PY.DECORATOR | - | rule | script | "" | PY.DECORATOR | - | rule | shell | "" | PY.DECORATOR | + | rule | output | "file.txt" | SMK_DECORATOR | + | rule | input | "file.txt" | SMK_DECORATOR | + | rule | params | "file.txt" | SMK_DECORATOR | + | rule | log | "file.txt" | SMK_DECORATOR | + | rule | resources | foo | SMK_DECORATOR | + | rule | version | "" | SMK_DECORATOR | + | rule | cache | "" | SMK_DECORATOR | + | rule | message | "" | SMK_DECORATOR | + | rule | threads | "" | SMK_DECORATOR | + | rule | singularity | "" | SMK_DECORATOR | + | rule | priority | "" | SMK_DECORATOR | + | rule | benchmark | "" | SMK_DECORATOR | + | rule | wildcard_constraints | "" | SMK_DECORATOR | + | rule | group | "" | SMK_DECORATOR | + | rule | envmodules | "" | SMK_DECORATOR | + | rule | shadow | "" | SMK_DECORATOR | + | rule | conda | "" | SMK_DECORATOR | + | rule | cwl | "" | SMK_DECORATOR | + | rule | script | "" | SMK_DECORATOR | + | rule | shell | "" | SMK_DECORATOR | | rule | run | "" | PY.PREDEFINED_DEFINITION | - | rule | wrapper | "" | PY.DECORATOR | - | rule | name | "" | PY.DECORATOR | - | rule | handover | "" | PY.DECORATOR | - | checkpoint | output | "file.txt" | PY.DECORATOR | + | rule | wrapper | "" | SMK_DECORATOR | + | rule | name | "" | SMK_DECORATOR | + | rule | handover | "" | SMK_DECORATOR | + | checkpoint | output | "file.txt" | SMK_DECORATOR | | checkpoint | run | "" | PY.PREDEFINED_DEFINITION | Scenario Outline: Annotate Subworkflows @@ -109,11 +109,11 @@ Feature: Annotate additional syntax """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.FUNC_DEFINITION + SMK_FUNC_DEFINITION """ Then I expect inspection info on <
> with message """ @@ -122,9 +122,9 @@ Feature: Annotate additional syntax When I check highlighting infos Examples: | section | text | highlighting | - | workdir | "file.txt" | PY.DECORATOR | - | snakefile | "file.txt" | PY.DECORATOR | - | configfile | "file.txt" | PY.DECORATOR | + | workdir | "file.txt" | SMK_DECORATOR | + | snakefile | "file.txt" | SMK_DECORATOR | + | configfile | "file.txt" | SMK_DECORATOR | Scenario: Do not annotate keyword-like identifiers in run section Given a snakemake project @@ -151,11 +151,11 @@ Feature: Annotate additional syntax """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.FUNC_DEFINITION + SMK_FUNC_DEFINITION """ Then I expect inspection info on with message """ @@ -198,30 +198,45 @@ Feature: Annotate additional syntax """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.FUNC_DEFINITION + SMK_FUNC_DEFINITION """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.DECORATOR + SMK_DECORATOR """ When I check highlighting infos + Scenario: Annotate keyword argument + Given a snakemake project + Given I open a file "foo.smk" with text + """ + rule NAME: + input: + "file1", + arg = "file2" + """ + Then I expect inspection info on with message + """ + SMK_KEYWORD_ARGUMENT + """ + When I check highlighting infos ignoring extra highlighting + Scenario: 'use' section highlighting, part 2 Given a snakemake project Given I open a file "foo.smk" with text @@ -230,22 +245,22 @@ Feature: Annotate additional syntax """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.KEYWORD + SMK_KEYWORD """ Then I expect inspection info on with message """ - PY.FUNC_DEFINITION + SMK_FUNC_DEFINITION """ When I check highlighting infos \ No newline at end of file diff --git a/src/test/resources/features/highlighting/wildcards_annotator.feature b/src/test/resources/features/highlighting/wildcards_annotator.feature index 423256334..f994071e5 100644 --- a/src/test/resources/features/highlighting/wildcards_annotator.feature +++ b/src/test/resources/features/highlighting/wildcards_annotator.feature @@ -9,7 +9,7 @@ Feature: Annotate wildcards """ Then I expect inspection info on with message """ - PY.NUMBER + SMKSL_WILDCARD """ When I check highlighting infos ignoring extra highlighting Examples: @@ -28,7 +28,7 @@ Feature: Annotate wildcards """ Then I expect inspection info on with message """ - PY.NUMBER + SMKSL_WILDCARD """ When I check highlighting infos ignoring extra highlighting Examples: @@ -48,7 +48,7 @@ Feature: Annotate wildcards """ Then I expect inspection info on with message """ - PY.NUMBER + SMKSL_WILDCARD """ When I check highlighting infos ignoring extra highlighting Examples: @@ -67,7 +67,7 @@ Feature: Annotate wildcards """ Then I expect inspection info on with message """ - PY.NUMBER + SMKSL_WILDCARD """ When I check highlighting infos ignoring extra highlighting Examples: @@ -84,7 +84,7 @@ Feature: Annotate wildcards """ Then I expect inspection info on with message """ - PY.NUMBER + SMKSL_WILDCARD """ When I check highlighting infos ignoring extra highlighting Examples: @@ -101,7 +101,7 @@ Feature: Annotate wildcards """ Then I expect inspection info on with message """ - PY.NUMBER + SMKSL_WILDCARD """ When I check highlighting infos ignoring extra highlighting Examples: