From 848fe20100f7f87dce72cba160a44e492a49d690 Mon Sep 17 00:00:00 2001 From: Oleg Kizeev Date: Sun, 19 Nov 2023 16:11:38 +0700 Subject: [PATCH] Added new features - Added folding imports - Added a handler for brackets and buckets --- CHANGELOG.md | 2 + src/main/grammars/SlintParser.bnf | 18 ++--- .../ide/editor/SlintBraceMatcher.kt | 23 +++++++ .../ide/editor/SlintQuoteHandler.kt | 6 ++ .../ide/folding/SlintBlockFoldingBuilder.kt | 31 +++++++++ .../ide/folding/SlintImportFoldingBuilder.kt | 69 +++++++++++++++++++ .../dev/slint/ideaplugin/util/parser/Psi.kt | 34 +++++++++ src/main/resources/META-INF/plugin.xml | 8 +++ 8 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintBraceMatcher.kt create mode 100644 src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintQuoteHandler.kt create mode 100644 src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintBlockFoldingBuilder.kt create mode 100644 src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintImportFoldingBuilder.kt create mode 100644 src/main/kotlin/dev/slint/ideaplugin/util/parser/Psi.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f26eff1..792f6f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Added - Added an action to create a new empty file +- Added folding imports +- Added a handler for brackets and buckets ### Fixed - Fixed linear marker preview diff --git a/src/main/grammars/SlintParser.bnf b/src/main/grammars/SlintParser.bnf index 26668e6..e641d03 100644 --- a/src/main/grammars/SlintParser.bnf +++ b/src/main/grammars/SlintParser.bnf @@ -119,7 +119,7 @@ private ExportComponentDefinition ::= '{' Expression (',' (Expression | &'}'))* } -private ImportDefinition ::= IMPORT ImportElementDefinitionBlock? ImportPathDeclaration ';' +ImportDefinition ::= IMPORT ImportElementDefinitionBlock? ImportPathDeclaration ';' private ImportPathDeclaration ::= STRING_LITERAL private ImportElementDefinitionBlock ::= '{' (ComponentName ','?)* '}' FROM @@ -142,7 +142,7 @@ ComponentDefinition ::= COMPONENT ComponentName ComponentInheritsDeclaration? Co private ComponentInheritsDeclaration ::= INHERITS ComponentName { pin=2 } -private ComponentBody ::= '{' ComponentMembers '}' { +ComponentBody ::= '{' ComponentMembers '}' { pin=1 } private ComponentMembers ::= ComponentMemberDefinition* { @@ -185,7 +185,7 @@ private ComponentMember_recover ::= ) private InternalComponentDeclaration ::= ComponentName InternalComponentBody -private InternalComponentBody ::= '{' ComponentMembers '}' { +InternalComponentBody ::= '{' ComponentMembers '}' { pin=1 } @@ -217,7 +217,7 @@ private CallbackBindingDeclaration ::= '<=>' PropertyExpression private CallbackDeclaration ::= Identifier CallArgumentList? '=>' CallbackBody { pin=3 } -private CallbackBody ::= '{' CallbackMembers '}' +CallbackBody ::= '{' CallbackMembers '}' private CallbackMembers ::= FunctionMemberDefinition* { recoverWhile="simple_scope_recover" } @@ -229,7 +229,7 @@ private AnimateDeclaration ::= ANIMATE AnimatePropertyName AnimateBody { private AnimatePropertyName ::= (PropertyExpression | '*' ) (',' Expression)* { pin(".*")=1 } -private AnimateBody ::= '{' AnimateMembers '}' { +AnimateBody ::= '{' AnimateMembers '}' { pin=1 } private AnimateMembers ::= AnimateMemberDefinition* { @@ -244,7 +244,7 @@ private AnimateMember_recover ::= !(Identifier | '}') // States private StatesDefinition ::= STATES StatesBody -private StatesBody ::= '[' StateMembers ']' { +StatesBody ::= '[' StateMembers ']' { pin=1 } private StateMembers ::= StateMemberDefinition* { @@ -294,7 +294,7 @@ private FunctionDefinitionArgumentList ::= '(' FunctionArgument? (',' (FunctionA } private FunctionArgument ::= Identifier ':' Type -private FunctionBody ::= '{' FunctionMembers '}' { +FunctionBody ::= '{' FunctionMembers '}' { pin=1 } private FunctionMembers ::= FunctionMemberDefinition* { @@ -343,7 +343,7 @@ EnumDefinition ::= ENUM ComponentName '{' Identifier (',' Identifier)* '}' { // Struct /////////////////////////////////////////////////////////////////////////////////////////////////// StructDefinition ::= STRUCT ComponentName StructBody -private StructBody ::= '{' StructMembers '}' { pin=1 } +StructBody ::= '{' StructMembers '}' { pin=1 } private StructMembers ::= StructMemberDefinition* { recoverWhile="simple_scope_recover" } @@ -360,7 +360,7 @@ private StructFieldDeclaration ::= FieldIdentifier Type ','? { pin=2 } private AnonymousStructDefinition ::= StructBody private AnonymousStructDeclaration ::= AnonymousStructBody -private AnonymousStructBody ::= '{' AnonymousStructMembers '}' { pin=1 } +AnonymousStructBody ::= '{' AnonymousStructMembers '}' { pin=1 } private AnonymousStructMembers ::= AnonymousStructMemberDefinition* { recoverWhile="simple_scope_recover" } diff --git a/src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintBraceMatcher.kt b/src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintBraceMatcher.kt new file mode 100644 index 0000000..f7c7425 --- /dev/null +++ b/src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintBraceMatcher.kt @@ -0,0 +1,23 @@ +package dev.slint.ideaplugin.ide.editor + +import com.intellij.lang.BracePair +import com.intellij.lang.PairedBraceMatcher +import com.intellij.psi.PsiFile +import com.intellij.psi.tree.IElementType +import dev.slint.ideaplugin.lang.psi.SlintElementTypes + +class SlintBraceMatcher: PairedBraceMatcher { + override fun getPairs(): Array = braces + + override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?): Boolean = true + + override fun getCodeConstructStart(file: PsiFile, openingBraceOffset: Int): Int = openingBraceOffset + + companion object { + private val braces = arrayOf( + BracePair(SlintElementTypes.LBRACE, SlintElementTypes.RBRACE, true), + BracePair(SlintElementTypes.LBRACKET, SlintElementTypes.RBRACKET, false), + BracePair(SlintElementTypes.LPAREN, SlintElementTypes.RPAREN, false), + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintQuoteHandler.kt b/src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintQuoteHandler.kt new file mode 100644 index 0000000..67c48d5 --- /dev/null +++ b/src/main/kotlin/dev/slint/ideaplugin/ide/editor/SlintQuoteHandler.kt @@ -0,0 +1,6 @@ +package dev.slint.ideaplugin.ide.editor + +import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler +import dev.slint.ideaplugin.lang.psi.SLINT_STRINGS + +class SlintQuoteHandler: SimpleTokenSetQuoteHandler(SLINT_STRINGS) \ No newline at end of file diff --git a/src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintBlockFoldingBuilder.kt b/src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintBlockFoldingBuilder.kt new file mode 100644 index 0000000..d2e650b --- /dev/null +++ b/src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintBlockFoldingBuilder.kt @@ -0,0 +1,31 @@ +package dev.slint.ideaplugin.ide.folding + +import com.intellij.lang.ASTNode +import com.intellij.lang.folding.FoldingBuilderEx +import com.intellij.lang.folding.FoldingDescriptor +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.DumbAware +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import dev.slint.ideaplugin.lang.psi.* + +class SlintBlockFoldingBuilder : FoldingBuilderEx(), DumbAware { + override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array { + val blocks = PsiTreeUtil.findChildrenOfAnyType( + root, + SlintComponentBody::class.java, + SlintInternalComponentBody::class.java, + SlintCallbackBody::class.java, + SlintAnimateBody::class.java, + SlintStatesBody::class.java, + SlintFunctionBody::class.java, + SlintStructBody::class.java, + SlintAnonymousStructBody::class.java, + ) + return blocks.map { FoldingDescriptor(it, it.textRange) }.toTypedArray() + } + + override fun getPlaceholderText(node: ASTNode): String = "{...}" + + override fun isCollapsedByDefault(node: ASTNode): Boolean = false +} \ No newline at end of file diff --git a/src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintImportFoldingBuilder.kt b/src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintImportFoldingBuilder.kt new file mode 100644 index 0000000..8657247 --- /dev/null +++ b/src/main/kotlin/dev/slint/ideaplugin/ide/folding/SlintImportFoldingBuilder.kt @@ -0,0 +1,69 @@ +package dev.slint.ideaplugin.ide.folding + +import com.intellij.lang.ASTNode +import com.intellij.lang.folding.CustomFoldingBuilder +import com.intellij.lang.folding.FoldingDescriptor +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.refactoring.suggested.endOffset +import com.intellij.refactoring.suggested.startOffset +import dev.slint.ideaplugin.lang.psi.SlintFile +import dev.slint.ideaplugin.lang.psi.SlintImportDefinition +import dev.slint.ideaplugin.util.parser.childrenOfType + +class SlintImportFoldingBuilder : CustomFoldingBuilder(), DumbAware { + override fun buildLanguageFoldRegions( + descriptors: MutableList, + root: PsiElement, + document: Document, + quick: Boolean + ) { + if (root !is SlintFile) { + return + } + + val covered = HashSet() + val imports = root.allImports() + + for (import in imports) { + if (import in covered) { + continue + } + + var next: SlintImportDefinition? = import + var last: SlintImportDefinition = import + while (next != null) { + covered += next + last = next + next = next.nextImport() + } + + descriptors += FoldingDescriptor(import, TextRange(import.startOffset, last.endOffset)) + } + } + + override fun getLanguagePlaceholderText(node: ASTNode, textRange: TextRange): String = "import ..." + + override fun isRegionCollapsedByDefault(node: ASTNode): Boolean = false + +} + +private fun PsiElement.allImports() = childrenOfType(SlintImportDefinition::class).toList() + +private fun PsiElement.nextImport(): SlintImportDefinition? { + val next = this.nextSibling + + if (next is PsiWhiteSpace) { + return next.nextImport() + } + + if (next is PsiComment) { + return next.nextImport() + } + + return next as? SlintImportDefinition +} \ No newline at end of file diff --git a/src/main/kotlin/dev/slint/ideaplugin/util/parser/Psi.kt b/src/main/kotlin/dev/slint/ideaplugin/util/parser/Psi.kt new file mode 100644 index 0000000..456ca00 --- /dev/null +++ b/src/main/kotlin/dev/slint/ideaplugin/util/parser/Psi.kt @@ -0,0 +1,34 @@ +package dev.slint.ideaplugin.util.parser + +import com.intellij.openapi.application.runReadAction +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import kotlin.reflect.KClass + +/** + * @see [PsiTreeUtil.getChildrenOfType] + */ +fun PsiElement.childrenOfType(clazz: KClass): Collection { + if (project.isDisposed || !this.isValid) return emptyList() + return runReadAction { PsiTreeUtil.findChildrenOfType(this, clazz.java) } +} + +/** + * Finds the first child of a certain type. + */ +@Suppress("UNCHECKED_CAST") +fun PsiElement.firstChildOfType(clazz: KClass): T? { + val children = runReadAction { this.children } + for (child in children) { + if (clazz.java.isAssignableFrom(child.javaClass)) { + return child as? T + } + + val first = child.firstChildOfType(clazz) + if (first != null) { + return first + } + } + + return null +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 362f7cf..5555b5f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -32,8 +32,16 @@ + + + +