diff --git a/build.gradle.kts b/build.gradle.kts index fc7bd95..ed1db5e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,7 +38,7 @@ repositories { intellij { version.set("IC-2023.2.5") sandboxDir.set("idea-sandbox") - plugins.set(listOf("java")) + plugins.set(listOf("java","Kotlin")) } dependencies { compileOnly("org.projectlombok:lombok:1.18.32") diff --git a/src/main/grammar/main.bnf b/src/main/grammar/main.bnf index f2415dd..f2d6de1 100644 --- a/src/main/grammar/main.bnf +++ b/src/main/grammar/main.bnf @@ -148,7 +148,9 @@ Component ::= ExportKeyword? ComponentKeyword ComponentName InheritDeclaration? //private LegacyComponent ::= (ComponentName|NamedIdentifier ':=' ) ComponentBody InheritDeclaration ::= InheritsKeyword ReferenceIdentifier { pin=1 + recoverWhile=recoverInherit } +private recoverInherit::=!('{') //组件元素定义 private ComponentElement ::=ChildrenPlaceholder| Property | Callback | Function | PropertyAnimation | CallbackConnection | Transitions diff --git a/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionAutoPopupHandler.kt b/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionAutoPopupHandler.kt new file mode 100644 index 0000000..da6a9c9 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionAutoPopupHandler.kt @@ -0,0 +1,55 @@ +package me.zhouxi.slint.completion + +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.completion.CompletionPhase.EmptyAutoPopup +import com.intellij.codeInsight.completion.impl.CompletionServiceImpl +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate +import com.intellij.codeInsight.lookup.LookupManager +import com.intellij.codeInsight.lookup.impl.LookupImpl +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorModificationUtil +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile + +/** + * @author zhouxi 2024/5/29 + */ +class SlintCompletionAutoPopupHandler : TypedHandlerDelegate() { + override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result { + val lookup = LookupManager.getActiveLookup(editor) as LookupImpl? + + val phase = CompletionServiceImpl.getCompletionPhase() + if (LOG.isDebugEnabled) { + LOG.debug("checkAutoPopup: character=$charTyped;") + LOG.debug("phase=$phase") + LOG.debug("lookup=$lookup") + LOG.debug("currentCompletion=" + CompletionServiceImpl.getCompletionService().currentCompletion) + } + + if (lookup != null) { + if (editor.selectionModel.hasSelection()) { + lookup.performGuardedChange(Runnable { EditorModificationUtil.deleteSelectedText(editor) }) + } + return Result.STOP + } + + if (Character.isLetterOrDigit(charTyped) || charTyped == '_' || charTyped == '@') { + if (phase is EmptyAutoPopup && phase.allowsSkippingNewAutoPopup(editor, charTyped)) { + return Result.CONTINUE + } + + AutoPopupController.getInstance(project).scheduleAutoPopup(editor) + return Result.STOP + } + + return Result.CONTINUE + } + + companion object { + private val LOG = Logger.getInstance( + SlintCompletionAutoPopupHandler::class.java + ) + } +} + diff --git a/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt b/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt index 4b982cb..b326f3f 100644 --- a/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt +++ b/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt @@ -1,47 +1,30 @@ package me.zhouxi.slint.completion import com.intellij.codeInsight.completion.CompletionContributor +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionType -import com.intellij.patterns.PlatformPatterns +import com.intellij.openapi.util.Pair +import com.intellij.patterns.ElementPattern +import com.intellij.psi.PsiElement import me.zhouxi.slint.completion.provider.* -import me.zhouxi.slint.lang.psi.SlintTypes -import me.zhouxi.slint.lang.psi.SlintTypes.Component -import me.zhouxi.slint.lang.psi.stubs.types.SlintFileElementType class SlintCompletionContributor : CompletionContributor() { init { - //文件级别 - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement().withAncestor(3, PlatformPatterns.psiElement(SlintFileElementType)), - TopKeywordProvider - ) + extend(TopKeywordProvider) + extend(BasicTypeProvider) + extend(AtChildrenCompletionProvider) + extend(ElementKeywordProvider) + extend(ComponentProvider) + extend(PropertyBindingProvider) + extend(InheritsCompletionProvider) + } - //类型定义 - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement().withAncestor(3, PlatformPatterns.psiElement(SlintTypes.Type)), - BasicTypeProvider - ) - - //componentBody - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement().withAncestor(2, PlatformPatterns.psiElement(Component)), - ElementKeywordProvider - ) - //componentBody - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement().withAncestor(2,PlatformPatterns.psiElement(Component)), - ComponentProvider - ) - //propertyBinding - extend( - CompletionType.BASIC, - PlatformPatterns.psiElement().withAncestor(2, PlatformPatterns.psiElement(Component)), - PropertyBindingProvider - ) + private fun extend( + provider: AbstractSlintCompletionProvider, + type: CompletionType = CompletionType.BASIC, + ) { + extend(type, provider.pattern(), provider) } } \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/AbstractSlintCompletionProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/AbstractSlintCompletionProvider.kt new file mode 100644 index 0000000..c4da9e9 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/AbstractSlintCompletionProvider.kt @@ -0,0 +1,14 @@ +package me.zhouxi.slint.completion.provider + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionProvider +import com.intellij.patterns.ElementPattern +import com.intellij.psi.PsiElement + +/** + * @author zhouxi 2024/5/29 + */ +abstract class AbstractSlintCompletionProvider : CompletionProvider() { + + abstract fun pattern(): ElementPattern +} diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/AtChildrenCompletionProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/AtChildrenCompletionProvider.kt new file mode 100644 index 0000000..8b1ef7c --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/AtChildrenCompletionProvider.kt @@ -0,0 +1,32 @@ +package me.zhouxi.slint.completion.provider + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.icons.AllIcons +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.psi.PsiElement +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.SlintTypes.Component +/** + * @author zhouxi 2024/5/29 + */ +object AtChildrenCompletionProvider : AbstractSlintCompletionProvider() { + override fun pattern(): ElementPattern { + return psiElement() + .afterLeaf(psiElement().withText("@")) + .withParent(psiElement(Component)) + } + + val element = LookupElementBuilder.create("children").withIcon(AllIcons.Nodes.Annotationtype) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addElement(element) + } + +} diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/BasicTypeProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/BasicTypeProvider.kt index 505383d..966926d 100644 --- a/src/main/kotlin/me/zhouxi/slint/completion/provider/BasicTypeProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/BasicTypeProvider.kt @@ -1,13 +1,16 @@ package me.zhouxi.slint.completion.provider import com.intellij.codeInsight.completion.CompletionParameters -import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.psi.PsiElement import com.intellij.util.ProcessingContext import me.zhouxi.slint.lang.psi.SlintPsiUtils.InternalTypes +import me.zhouxi.slint.lang.psi.SlintTypes.Type -object BasicTypeProvider : CompletionProvider() { +object BasicTypeProvider : AbstractSlintCompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, @@ -17,4 +20,9 @@ object BasicTypeProvider : CompletionProvider() { } private val basicTypes = InternalTypes.map { LookupElementBuilder.create(it) } + + + override fun pattern(): ElementPattern { + return psiElement().withAncestor(3, psiElement(Type)) + } } diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/ComponentProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/ComponentProvider.kt index d08c6a8..501f23a 100644 --- a/src/main/kotlin/me/zhouxi/slint/completion/provider/ComponentProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/ComponentProvider.kt @@ -3,11 +3,14 @@ package me.zhouxi.slint.completion.provider import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.codeInsight.lookup.LookupElementDecorator.withInsertHandler import com.intellij.icons.AllIcons import com.intellij.openapi.vfs.VfsUtil +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.patterns.StandardPatterns.or import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.impl.source.tree.LeafPsiElement @@ -15,17 +18,20 @@ import com.intellij.psi.search.GlobalSearchScope.projectScope import com.intellij.psi.stubs.StubIndex import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.childrenOfType -import com.intellij.psi.util.parentOfTypes +import com.intellij.psi.util.parentOfType import com.intellij.util.ProcessingContext import me.zhouxi.slint.lang.createComma import me.zhouxi.slint.lang.createImport import me.zhouxi.slint.lang.createImportSpecifier import me.zhouxi.slint.lang.psi.SlintComponent import me.zhouxi.slint.lang.psi.SlintImport +import me.zhouxi.slint.lang.psi.SlintInheritDeclaration +import me.zhouxi.slint.lang.psi.SlintTypes.Component +import me.zhouxi.slint.lang.psi.SlintTypes.InheritDeclaration import me.zhouxi.slint.lang.psi.extension.importNames import me.zhouxi.slint.lang.psi.stubs.index.StubIndexKeys -object ComponentProvider : CompletionProvider() { +object ComponentProvider : AbstractSlintCompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, @@ -62,7 +68,18 @@ object ComponentProvider : CompletionProvider() { private val path: String? ) : InsertHandler { override fun handleInsert(context: InsertionContext, item: LookupElement) { + //insert '{}' + val caretOffset: Int = context.editor.caretModel.offset + val elementAt = context.file.findElementAt(caretOffset) + if (elementAt?.prevSibling !is SlintInheritDeclaration) { + context.document.insertString(caretOffset, "{}") + context.editor.caretModel.moveToOffset(caretOffset + 1) + PsiDocumentManager.getInstance(context.project).commitDocument(context.document) + } val component = item.psiElement as SlintComponent + if (component.containingFile.isEquivalentTo(context.file)) { + return + } val targetImport = context.file.childrenOfType().find { import -> val target = import.importElement?.moduleLocation?.reference?.resolve() PsiManager.getInstance(context.project).areElementsEquivalent(target, targetFile) @@ -98,4 +115,11 @@ object ComponentProvider : CompletionProvider() { importElement.addAfter(specifier, element) } } + + override fun pattern(): ElementPattern { + return or( + psiElement().withParent(psiElement(Component)).andNot(AtChildrenCompletionProvider.pattern()), + psiElement().withSuperParent(2, psiElement(InheritDeclaration)) + ) + } } diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/ElementKeywordProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/ElementKeywordProvider.kt index 11eeb6b..832444c 100644 --- a/src/main/kotlin/me/zhouxi/slint/completion/provider/ElementKeywordProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/ElementKeywordProvider.kt @@ -4,9 +4,13 @@ import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.psi.PsiElement import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.SlintTypes.Component -object ElementKeywordProvider : CompletionProvider() { +object ElementKeywordProvider : AbstractSlintCompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, @@ -18,6 +22,11 @@ object ElementKeywordProvider : CompletionProvider() { private val elementKeywords = arrayOf( "in", "out", "in-out", "callback", "property", "private", "changed", - "states", "transitions", "@children" + "states", "transitions", "function" ).map { LookupElementBuilder.create(it) } + + override fun pattern(): ElementPattern { + return psiElement().withParent(psiElement(Component)) + .andNot(AtChildrenCompletionProvider.pattern()) + } } diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/InheritsCompletionProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/InheritsCompletionProvider.kt new file mode 100644 index 0000000..b33e928 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/InheritsCompletionProvider.kt @@ -0,0 +1,28 @@ +package me.zhouxi.slint.completion.provider + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.PsiElement +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.SlintTypes + +/** + * @author zhouxi 2024/5/29 + */ +object InheritsCompletionProvider : AbstractSlintCompletionProvider() { + override fun pattern(): ElementPattern { + return PlatformPatterns.psiElement() + .afterLeaf(PlatformPatterns.psiElement().withParent(PlatformPatterns.psiElement(SlintTypes.ComponentName))) + } + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addElement(LookupElementBuilder.create("inherits")) + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/PropertyBindingProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/PropertyBindingProvider.kt index 2c35a3f..1cfb6b6 100644 --- a/src/main/kotlin/me/zhouxi/slint/completion/provider/PropertyBindingProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/PropertyBindingProvider.kt @@ -5,30 +5,36 @@ import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.icons.AllIcons +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.patterns.StandardPatterns.or +import com.intellij.psi.PsiElement import com.intellij.psi.util.parentOfType import com.intellij.util.ProcessingContext import me.zhouxi.slint.lang.psi.SlintComponent import me.zhouxi.slint.lang.psi.SlintSubComponent +import me.zhouxi.slint.lang.psi.SlintTypes.Component +import me.zhouxi.slint.lang.psi.SlintTypes.SubComponent import me.zhouxi.slint.lang.psi.extension.inheritsProperties +import me.zhouxi.slint.lang.psi.extension.resolve +import me.zhouxi.slint.lang.psi.extension.toLookupElement -object PropertyBindingProvider : CompletionProvider() { +object PropertyBindingProvider : AbstractSlintCompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { val element = parameters.position - val subComponent = element.parentOfType() - if (subComponent != null) { - val component = subComponent.referenceIdentifier.reference?.resolve() as SlintComponent? ?: return - val builders = component.inheritsProperties() - .map { LookupElementBuilder.create(it).withTypeText(component.name).withIcon(AllIcons.Nodes.Property) } - result.addAllElements(builders) - } else { - val component = element.parentOfType() ?: return - val builders = component.inheritsProperties() - .map { LookupElementBuilder.create(it).withTypeText(component.name).withIcon(AllIcons.Nodes.Property) } - result.addAllElements(builders) + val component = element.parentOfType()?.resolve() + ?: element.parentOfType() + component?.let { + result.addAllElements(it.inheritsProperties().map { it.toLookupElement() }) } } + + override fun pattern(): ElementPattern { + return psiElement().withParent(or(psiElement(Component), psiElement(SubComponent))) + .andNot(AtChildrenCompletionProvider.pattern()) + } } \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/completion/provider/TopKeywordProvider.kt b/src/main/kotlin/me/zhouxi/slint/completion/provider/TopKeywordProvider.kt index 46d0f1f..a259b6e 100644 --- a/src/main/kotlin/me/zhouxi/slint/completion/provider/TopKeywordProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/completion/provider/TopKeywordProvider.kt @@ -1,12 +1,15 @@ package me.zhouxi.slint.completion.provider import com.intellij.codeInsight.completion.CompletionParameters -import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.psi.PsiElement import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.stubs.types.SlintFileElementType -object TopKeywordProvider : CompletionProvider() { +object TopKeywordProvider : AbstractSlintCompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, @@ -17,4 +20,8 @@ object TopKeywordProvider : CompletionProvider() { private val topKeywords = arrayOf("export", "component", "global", "enum", "struct").map { LookupElementBuilder.create(it) } + + override fun pattern(): ElementPattern { + return psiElement().withSuperParent(1, psiElement(SlintFileElementType)) + } } diff --git a/src/main/kotlin/me/zhouxi/slint/formatter/FormattingBlock.kt b/src/main/kotlin/me/zhouxi/slint/formatter/FormattingBlock.kt new file mode 100644 index 0000000..ea22d4e --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/formatter/FormattingBlock.kt @@ -0,0 +1,79 @@ +package me.zhouxi.slint.formatter + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.psi.TokenType +import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.psi.tree.TokenSet +import me.zhouxi.slint.lang.psi.SlintTypes +import me.zhouxi.slint.lang.psi.SlintTypes.* +import me.zhouxi.slint.lang.psi.utils.braces +import me.zhouxi.slint.lang.psi.utils.expressions +import me.zhouxi.slint.lang.psi.utils.keywords + +/** + * @author zhouxi 2024/5/28 + */ +class FormattingBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val indent: Indent?, + private val spacingBuilder: SpacingBuilder +) : AbstractBlock(node, wrap, alignment) { + override fun buildChildren(): List { + val childBlocks = arrayListOf() + val syntheticBlocks = arrayListOf() + var current = childBlocks + var myIndent: Indent? = indent + for (child in node.getChildren(null)) { + if (braces.contains(child.elementType) && (node.elementType == SubComponent || node.elementType == Component)) { + current = syntheticBlocks + myIndent = Indent.getNormalIndent() + } + if (child.elementType == TokenType.WHITE_SPACE || child.textLength == 0) { + continue + } + if (child.firstChildNode == null || leafTokens.contains(child.elementType)) { + current.add(LeafBlock(child, wrap, alignment, Indent.getNoneIndent(), spacingBuilder)) + continue + } + if (expressions.contains(child.elementType)) { + current.add(FormattingBlock(child, wrap, alignment, null, spacingBuilder)) + continue + } + current.add(FormattingBlock(child, wrap, alignment, myIndent, spacingBuilder)) + } + if (syntheticBlocks.isNotEmpty()) { + childBlocks.add(SyntheticBlock(syntheticBlocks, wrap, Indent.getNormalIndent(), alignment, spacingBuilder)) + } + return childBlocks + } + + override fun getIndent() = indent + + override fun getSpacing(child1: Block?, child2: Block): Spacing? { + if (child2 is SyntheticBlock) { + return Spacing.createSpacing(1, 1, 0, true, 2) + } + return spacingBuilder.getSpacing(this, child1, child2) + } + + override fun isLeaf(): Boolean { + return false + } + + companion object { + val leafTokens = + TokenSet.orSet( + TokenSet.create( + ReferenceIdentifier, + ComponentName, + InternalName, + LocalVariable, + PropertyName + ), + keywords + ) + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/formatter/LeafBlock.kt b/src/main/kotlin/me/zhouxi/slint/formatter/LeafBlock.kt new file mode 100644 index 0000000..b4b70eb --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/formatter/LeafBlock.kt @@ -0,0 +1,55 @@ +package me.zhouxi.slint.formatter + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.formatter.common.ExtraRangesProvider +import com.intellij.psi.formatter.common.NodeIndentRangesCalculator + +class LeafBlock( + private val treeNode: ASTNode, + private val wrap: Wrap?, + private val alignment: Alignment?, + private val indent: Indent?, + private val spacingBuilder: SpacingBuilder +) : ASTBlock, ExtraRangesProvider { + + + override fun getNode() = treeNode + + override fun getTextRange(): TextRange = treeNode.textRange + + override fun getSubBlocks() = EMPTY_SUB_BLOCKS + + override fun getWrap() = wrap + + override fun getIndent() = indent + + override fun getAlignment() = alignment + + override fun getSpacing(child1: Block?, child2: Block) = null + + override fun getChildAttributes(newChildIndex: Int) = ChildAttributes(indent, null) + + override fun isIncomplete() = false + + override fun isLeaf() = true + + override fun getExtraRangesToFormat(info: FormattingRangesInfo): List? { + val startOffset = textRange.startOffset + if (info.isOnInsertedLine(startOffset) && treeNode.textLength == 1 && treeNode.textContains('}')) { + val parent = treeNode.treeParent + return NodeIndentRangesCalculator(parent).calculateExtraRanges() + } + return null + } + + override fun toString(): String { + return javaClass.simpleName + " '" + StringUtil.escapeLineBreak(node.text) + "' " + textRange + } + + companion object { + private val EMPTY_SUB_BLOCKS = ArrayList() + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/formatter/SlintFormatterModelBuilder.kt b/src/main/kotlin/me/zhouxi/slint/formatter/SlintFormatterModelBuilder.kt new file mode 100644 index 0000000..93ac637 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/formatter/SlintFormatterModelBuilder.kt @@ -0,0 +1,61 @@ +package me.zhouxi.slint.formatter + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.tree.TokenSet +import me.zhouxi.slint.lang.SlintLanguage +import me.zhouxi.slint.lang.psi.SlintTypes +import me.zhouxi.slint.lang.psi.SlintTypes.* +import me.zhouxi.slint.lang.psi.utils.keywords + +/** + * @author zhouxi 2024/5/7 + */ +class SlintFormatterModelBuilder : FormattingModelBuilder { + override fun createModel(formattingContext: FormattingContext): FormattingModel { + val element = formattingContext.psiElement + val spacingBuilder = createSpaceBuilder(formattingContext.codeStyleSettings) + val slintBlock = FormattingBlock( + formattingContext.node, + null, + null, + Indent.getNoneIndent(), + spacingBuilder + ) + return FormattingModelProvider.createFormattingModelForPsiFile( + element.containingFile, + slintBlock, + formattingContext.codeStyleSettings + ) + } + + override fun getRangeAffectingIndent(file: PsiFile, offset: Int, elementAtOffset: ASTNode): TextRange? { + return file.textRange + } + + companion object { + + private fun createSpaceBuilder(settings: CodeStyleSettings): SpacingBuilder { + return SpacingBuilder(settings, SlintLanguage.INSTANCE) + .after(Comma) + .spaces(1) + .before(Comma) + .spaces(0) + .before(Semicolon) + .spacing(0, 0, 0, false, 0) + .after(Semicolon) + .lineBreakInCode() + .after(Colon) + .spaces(1) + .around(TokenSet.create(DoubleArrow, Plus, Minus, Star, Div)) + .spaces(1) + .before(CodeBlock) + .spacing(1, 1, 0, true, 2) + .around(keywords) + .spaces(1) + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt b/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt index 4bb78e1..216374e 100644 --- a/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt @@ -20,19 +20,19 @@ class SlintLineIndentProvider : JavaLikeLangLineIndentProvider() { } companion object { - val SyntaxMap: Map = java.util.Map.ofEntries( - java.util.Map.entry(SlintTypes.LBracket, JavaLikeElement.BlockOpeningBrace), - java.util.Map.entry(SlintTypes.RBracket, JavaLikeElement.BlockClosingBrace), - java.util.Map.entry(SlintTypes.LBrace, JavaLikeElement.BlockOpeningBrace), - java.util.Map.entry(SlintTypes.RBrace, JavaLikeElement.BlockClosingBrace), - java.util.Map.entry(TokenType.WHITE_SPACE, JavaLikeElement.Whitespace), - java.util.Map.entry(SlintTypes.Semicolon, JavaLikeElement.Semicolon), - java.util.Map.entry(SlintTypes.LineComment, JavaLikeElement.LineComment), - java.util.Map.entry(SlintTypes.BlockComment, JavaLikeElement.BlockComment), - java.util.Map.entry(SlintTypes.Colon, JavaLikeElement.Colon), - java.util.Map.entry(SlintTypes.Comma, JavaLikeElement.Comma), - java.util.Map.entry(SlintTypes.LParent, JavaLikeElement.LeftParenthesis), - java.util.Map.entry(SlintTypes.RParent, JavaLikeElement.RightParenthesis) + val SyntaxMap: Map = mapOf( + SlintTypes.LBracket to JavaLikeElement.BlockOpeningBrace, + SlintTypes.RBracket to JavaLikeElement.BlockClosingBrace, + SlintTypes.LBrace to JavaLikeElement.BlockOpeningBrace, + SlintTypes.RBrace to JavaLikeElement.BlockClosingBrace, + TokenType.WHITE_SPACE to JavaLikeElement.Whitespace, + SlintTypes.Semicolon to JavaLikeElement.Semicolon, + SlintTypes.LineComment to JavaLikeElement.LineComment, + SlintTypes.BlockComment to JavaLikeElement.BlockComment, + SlintTypes.Colon to JavaLikeElement.Colon, + SlintTypes.Comma to JavaLikeElement.Comma, + SlintTypes.LParent to JavaLikeElement.LeftParenthesis, + SlintTypes.RParent to JavaLikeElement.RightParenthesis ) } } diff --git a/src/main/kotlin/me/zhouxi/slint/formatter/SyntheticBlock.kt b/src/main/kotlin/me/zhouxi/slint/formatter/SyntheticBlock.kt new file mode 100644 index 0000000..3244017 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/formatter/SyntheticBlock.kt @@ -0,0 +1,52 @@ +package me.zhouxi.slint.formatter + +import com.intellij.formatting.* +import com.intellij.openapi.util.TextRange +import com.intellij.psi.formatter.common.ExtraRangesProvider +import com.intellij.psi.formatter.common.NodeIndentRangesCalculator +import me.zhouxi.slint.lang.psi.SlintTypes +import me.zhouxi.slint.lang.psi.SlintTypes.LBrace +import me.zhouxi.slint.lang.psi.SlintTypes.RBrace + +/** + * @author zhouxi 2024/5/28 + */ +class SyntheticBlock( + private val subBlocks: List, + private val wrap: Wrap?, + private val indent: Indent, + private val alignment: Alignment?, + private val spacingBuilder: SpacingBuilder +) : Block { + override fun getTextRange(): TextRange { + return TextRange(subBlocks.first().textRange.startOffset, subBlocks.last().textRange.endOffset) + } + + override fun getSubBlocks(): List { + return subBlocks + } + + override fun getWrap() = wrap + + override fun getIndent() = indent + + override fun getAlignment() = alignment + + override fun getSpacing(child1: Block?, child2: Block): Spacing? { + if (child2 is ASTBlock && child2.node!!.elementType == SlintTypes.Semicolon) { + return Spacing.createSpacing(1, 1, 0, true, 2) + } + if (child1 is ASTBlock && child2 is ASTBlock + && child1.node!!.elementType == LBrace && child2.node!!.elementType == RBrace + ) { + return Spacing.createSpacing(1, 1, 0, false, 0) + } + return null + } + + override fun getChildAttributes(newChildIndex: Int) = ChildAttributes(null, subBlocks[0].alignment) + + override fun isIncomplete() = false + + override fun isLeaf() = false +} diff --git a/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt b/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt index 2918fb1..8caaaf8 100644 --- a/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt +++ b/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt @@ -5,7 +5,9 @@ import com.intellij.openapi.editor.HighlighterColors import com.intellij.openapi.editor.colors.TextAttributesKey object Definitions { - @JvmField + val _Annotation = + TextAttributesKey.createTextAttributesKey("_Annotation", DefaultLanguageHighlighterColors.METADATA) + val _KeyWord: TextAttributesKey = TextAttributesKey.createTextAttributesKey("_KeyWord", DefaultLanguageHighlighterColors.KEYWORD) @@ -55,4 +57,6 @@ object Definitions { val SemiColon: Array = arrayOf(_SemiColon) val Error: Array = arrayOf(_Error) + + val Annotation: Array = arrayOf(_Annotation) } diff --git a/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt b/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt index 710c311..ddedb07 100644 --- a/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt +++ b/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt @@ -10,6 +10,13 @@ import me.zhouxi.slint.lang.psi.keyword.SlintPsiKeywordIdentifier class KeywordHighlightAnnotator : Annotator { override fun annotate(element: PsiElement, holder: AnnotationHolder) { + if (element is SlintChildrenPlaceholder) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(element) + .textAttributes(Definitions._Annotation) + .create() + return + } if (element is SlintPsiKeywordIdentifier) { holder.newSilentAnnotation(HighlightSeverity.INFORMATION) .range(element) diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/builder/SlintPsiBuilder.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/builder/SlintPsiBuilder.kt new file mode 100644 index 0000000..7e74a9c --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/builder/SlintPsiBuilder.kt @@ -0,0 +1,29 @@ +package me.zhouxi.slint.lang.psi.builder + +import com.intellij.lang.PsiBuilder +import com.intellij.lang.impl.DelegateMarker +import com.intellij.lang.impl.PsiBuilderAdapter +import com.intellij.psi.tree.IElementType + +/** + * @author zhouxi 2024/5/29 + */ +class SlintPsiBuilder(delegate: PsiBuilder) : PsiBuilderAdapter(delegate) { + override fun mark(): PsiBuilder.Marker { + return SlintMarker(super.mark()) + } + + /** + * @author zhouxi 2024/5/29 + */ + class SlintMarker(delegate: PsiBuilder.Marker) : DelegateMarker(delegate) { + override fun precede(): PsiBuilder.Marker { + return SlintMarker(super.precede()) + } + + override fun done(type: IElementType) { + println(type) + super.done(type) + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintComponentEx.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintComponentEx.kt deleted file mode 100644 index d7fc712..0000000 --- a/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintComponentEx.kt +++ /dev/null @@ -1,12 +0,0 @@ -package me.zhouxi.slint.lang.psi.extension - -import me.zhouxi.slint.lang.psi.SlintComponent -import me.zhouxi.slint.lang.psi.SlintProperty - - -fun SlintComponent.inheritsProperties(): List { - val properties = this.propertyList - val inherit = - this.inheritDeclaration?.referenceIdentifier?.reference?.resolve() as SlintComponent? ?: return properties - return properties + inherit.inheritsProperties() -} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintImportEx.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintImportEx.kt deleted file mode 100644 index bc79e8f..0000000 --- a/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintImportEx.kt +++ /dev/null @@ -1,20 +0,0 @@ -package me.zhouxi.slint.lang.psi.extension - -import com.intellij.psi.PsiElement -import me.zhouxi.slint.lang.psi.SlintImport -import me.zhouxi.slint.lang.psi.SlintReferenceIdentifier - - -fun SlintImport.importNames(): List { - return this.importElement?.importSpecifierList?.map { it.referenceIdentifier } ?: emptyList() -} - -/** - * 导入的有效的名字, - * psiElement可能是identifier或者internalName - */ -fun SlintImport.availableNames(): List { - return this.importElement?.importSpecifierList?.map { - it.importAlias?.internalName ?: it.referenceIdentifier - } ?: emptyList() -} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintPsiEx.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintPsiEx.kt new file mode 100644 index 0000000..48b848d --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/extension/SlintPsiEx.kt @@ -0,0 +1,45 @@ +package me.zhouxi.slint.lang.psi.extension + +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.icons.AllIcons +import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import me.zhouxi.slint.lang.psi.* + + +fun SlintComponent.inheritsProperties(): List { + val properties = this.propertyList + val inherit = + this.inheritDeclaration?.referenceIdentifier?.reference?.resolve() as SlintComponent? ?: return properties + if (inherit == this) { + return properties + } + return properties + inherit.inheritsProperties() +} + + +fun SlintImport.importNames(): List { + return this.importElement?.importSpecifierList?.map { it.referenceIdentifier } ?: emptyList() +} + +/** + * 导入的有效的名字, + * psiElement可能是identifier或者internalName + */ +fun SlintImport.availableNames(): List { + return this.importElement?.importSpecifierList?.map { + it.importAlias?.internalName ?: it.referenceIdentifier + } ?: emptyList() +} + + +fun SlintSubComponent.resolve(): SlintComponent? { + return this.referenceIdentifier.reference?.resolve() as SlintComponent? +} + + +fun SlintProperty.toLookupElement(): LookupElementBuilder { + return LookupElementBuilder.create(this) + .withTypeText(this.parentOfType()?.componentName?.text) + .withIcon(AllIcons.Nodes.Property) +} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/stubs/types/SlintImportSpecifierElementType.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/stubs/types/SlintImportSpecifierElementType.kt index 70cf6b7..762c88d 100644 --- a/src/main/kotlin/me/zhouxi/slint/lang/psi/stubs/types/SlintImportSpecifierElementType.kt +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/stubs/types/SlintImportSpecifierElementType.kt @@ -33,8 +33,10 @@ object SlintImportSpecifierElementType : val aliasNode = children.firstOrNull { it.tokenType == ImportAlias } aliasNode?.let { val nameNode = tree.getChildren(aliasNode).firstOrNull { it.tokenType == InternalName } - val token = nameNode?.let { tree.getChildren(nameNode) }?.first() as LighterASTTokenNode - tree.charTable.intern(token.text).toString() + val token = nameNode?.let { tree.getChildren(nameNode) }?.firstOrNull() as LighterASTTokenNode? + token?.let { + tree.charTable.intern(it.text).toString() + } } } return SlintImportSpecifierStubImpl(parentStub, name, alias) diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintPsiTreeUtils.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintPsiTreeUtils.kt deleted file mode 100644 index 6854ea5..0000000 --- a/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintPsiTreeUtils.kt +++ /dev/null @@ -1,118 +0,0 @@ -package me.zhouxi.slint.lang.psi.utils - -import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import me.zhouxi.slint.lang.psi.* -import java.util.function.Function -import java.util.function.Predicate - - -fun resolveComponent(element: SlintReferenceIdentifier?): SlintComponent? { - if (element == null) { - return null - } - val maybeComponent = element.reference?.resolve() - if (maybeComponent is SlintComponent) { - return maybeComponent - } - return resolveReferencedComponent(maybeComponent ?: return null) -} - -fun resolveComponent(element: SlintInternalName): SlintComponent? { - //内部名字解析引用 - val resolve = element.reference?.resolve() - if (resolve is SlintComponent) { - return resolve - } - //InternalName解析不到东西,换成External试一下 - if (resolve == null) { - val externalName = PsiTreeUtil.getPrevSiblingOfType(element, SlintExternalName::class.java) - if (externalName == null) { - val externalName1 = PsiTreeUtil.getNextSiblingOfType(element, SlintExternalName::class.java) ?: return null - return resolveReferencedComponent(externalName1) - } - return resolveReferencedComponent(externalName) - } - return resolveReferencedComponent(resolve) -} - -fun resolveComponent(element: SlintExternalName): SlintComponent? { - val resolve = element.reference?.resolve() - if (resolve is SlintComponent) { - return resolve - } - //InternalName解析不到东西,换成External试一下 - if (resolve == null) { - val internalName = PsiTreeUtil.getPrevSiblingOfType(element, SlintInternalName::class.java) - if (internalName == null) { - val internalName1 = PsiTreeUtil.getNextSiblingOfType(element, SlintInternalName::class.java) ?: return null - return resolveReferencedComponent(internalName1) - } - return resolveReferencedComponent(internalName) - } - return resolveReferencedComponent(resolve) -} - -fun resolveReferencedComponent(element: PsiElement?): SlintComponent? { - if (element == null) { - return null - } - when (element) { - is SlintComponentName -> return element.parent as SlintComponent? - is SlintComponent -> return element - is SlintReferenceIdentifier -> return resolveComponent(element) - is SlintInternalName -> return resolveComponent(element) - is SlintExternalName -> return resolveComponent(element) - } - return null -} - -fun searchProperty( - component: PsiElement?, - predicate: Predicate -): SlintProperty? { - if (component is SlintSubComponent) { - return searchProperty(component, predicate) - } - if (component is SlintComponent) { - return searchElementInParents(component, predicate) { it.propertyList } - } - return null -} - -fun searchProperty( - subComponent: SlintSubComponent?, - predicate: Predicate -): SlintProperty? { - val component = resolveComponent(subComponent?.referenceIdentifier) ?: return null - return searchElementInParents(component, predicate) { it.propertyList } -} - -fun searchCallback( - subComponent: SlintSubComponent?, - predicate: Predicate -): SlintCallback? { - val component = subComponent?.referenceIdentifier?.reference?.resolve() ?: return null - return searchElementInParents( - component as SlintComponent, - predicate - ) { it.callbackList } -} - -fun searchElementInParents( - component: SlintComponent?, - predicate: Predicate, - function: Function?> -): T? { - if (component == null) { - return null - } - val properties = function.apply(component) ?: arrayListOf() - for (property in properties) { - if (predicate.test(property)) { - return property - } - } - val inheritComponent = resolveReferencedComponent(component.inheritDeclaration?.referenceIdentifier) - return searchElementInParents(inheritComponent, predicate, function) -} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintTokens.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintTokens.kt new file mode 100644 index 0000000..ba803c4 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintTokens.kt @@ -0,0 +1,21 @@ +package me.zhouxi.slint.lang.psi.utils + +import com.intellij.psi.tree.IElementType +import com.intellij.psi.tree.TokenSet +import me.zhouxi.slint.lang.psi.SlintTypes + + +val keywords = TokenSet.create(*SlintTypes::class.java.declaredFields + .filter { it.name.endsWith("Keyword") } + .map { it.get(null) as IElementType }.toTypedArray() +) + +val braces = TokenSet.create( + SlintTypes.LBrace, + SlintTypes.LParent, + SlintTypes.LBracket +) +val expressions = TokenSet.create(*SlintTypes::class.java.declaredFields + .filter { it.name.endsWith("Expression") } + .map { it.get(null) as IElementType }.toTypedArray() +) \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt b/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt index 803be00..d650c48 100644 --- a/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt +++ b/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt @@ -5,13 +5,10 @@ import com.intellij.psi.PsiReference import com.intellij.psi.PsiReferenceBase import com.intellij.psi.PsiReferenceProvider import com.intellij.psi.util.parentOfType -import com.intellij.psi.util.parentOfTypes import com.intellij.util.ProcessingContext import me.zhouxi.slint.lang.psi.SlintComponent -import me.zhouxi.slint.lang.psi.SlintPropertyBinding import me.zhouxi.slint.lang.psi.SlintSubComponent import me.zhouxi.slint.lang.psi.extension.inheritsProperties -import me.zhouxi.slint.lang.psi.utils.searchProperty object PropertyReferenceProvider : PsiReferenceProvider() { override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index c84c81b..c0d34cd 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -11,6 +11,8 @@ com.intellij.modules.platform + + +