From c9bab725229b5371b96ad205c31cf6a447377e60 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 18 Sep 2025 09:58:21 +0200 Subject: [PATCH 01/17] refactor: drop reference contributors, implement new goto handler The previous implementation conflicts with webTypes. --- .../ComponentGotoDeclarationHandler.kt | 42 ++++++++++++++ .../ComponentReferenceContributor.kt | 56 ------------------- .../framework/ComponentTagNameProvider.kt | 40 ------------- src/main/resources/META-INF/plugin.xml | 7 +-- 4 files changed, 44 insertions(+), 101 deletions(-) create mode 100644 src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt delete mode 100644 src/main/kotlin/com/github/tempest/framework/ComponentReferenceContributor.kt delete mode 100644 src/main/kotlin/com/github/tempest/framework/ComponentTagNameProvider.kt diff --git a/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt b/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt new file mode 100644 index 0000000..d68263f --- /dev/null +++ b/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt @@ -0,0 +1,42 @@ +package com.github.tempest.framework + +import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.vfs.findPsiFile +import com.intellij.psi.PsiElement +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.html.HtmlTag + +class ComponentGotoDeclarationHandler : GotoDeclarationHandler { + override fun getGotoDeclarationTargets( + element: PsiElement?, + offset: Int, + editor: Editor? + ): Array? { + if (element == null) return null + + val tag = PsiTreeUtil.getParentOfType(element, HtmlTag::class.java) ?: return null + if (!tag.name.startsWith("x-")) return null + + val project = tag.project + val result = mutableListOf() + + FilenameIndex.processFilesByName( + tag.name + TempestFrameworkUtil.TEMPLATE_PREFIX, + true, + GlobalSearchScope.projectScope(project), + { + val psiFile = it.findPsiFile(project) ?: return@processFilesByName true + result.add(psiFile) + + true + } + ) + + if (result.isEmpty()) return null + + return result.toTypedArray() + } +} diff --git a/src/main/kotlin/com/github/tempest/framework/ComponentReferenceContributor.kt b/src/main/kotlin/com/github/tempest/framework/ComponentReferenceContributor.kt deleted file mode 100644 index b745684..0000000 --- a/src/main/kotlin/com/github/tempest/framework/ComponentReferenceContributor.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.github.tempest.framework - -import com.intellij.openapi.vfs.findPsiFile -import com.intellij.patterns.PlatformPatterns -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiReference -import com.intellij.psi.PsiReferenceBase -import com.intellij.psi.PsiReferenceContributor -import com.intellij.psi.PsiReferenceProvider -import com.intellij.psi.PsiReferenceRegistrar -import com.intellij.psi.html.HtmlTag -import com.intellij.psi.search.FilenameIndex -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.psi.xml.XmlChildRole -import com.intellij.util.ProcessingContext - -class ComponentReferenceContributor : PsiReferenceContributor() { - override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { - registrar.registerReferenceProvider( - PlatformPatterns.psiElement(HtmlTag::class.java), - object : PsiReferenceProvider() { - override fun getReferencesByElement( - element: PsiElement, - context: ProcessingContext - ): Array { - if (element !is HtmlTag) return emptyArray() - if (!element.name.startsWith("x-")) return emptyArray() - - val nameElement = XmlChildRole.START_TAG_NAME_FINDER.findChild(element.node) ?: return emptyArray() - - val project = element.project - - val result = mutableListOf() - - FilenameIndex.processFilesByName( - element.name + TempestFrameworkUtil.TEMPLATE_PREFIX, - true, - GlobalSearchScope.projectScope(project), - { - val psiFile = it.findPsiFile(project) ?: return@processFilesByName true -// println("found file $it for ${element.name}") - result.add( - object : PsiReferenceBase(element, nameElement.textRange, false) { - override fun resolve() = psiFile - } - ) - - true - }) - - return result.toTypedArray() - } - } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/tempest/framework/ComponentTagNameProvider.kt b/src/main/kotlin/com/github/tempest/framework/ComponentTagNameProvider.kt deleted file mode 100644 index 8e539be..0000000 --- a/src/main/kotlin/com/github/tempest/framework/ComponentTagNameProvider.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.tempest.framework - -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.psi.search.FilenameIndex -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.psi.xml.XmlTag -import com.intellij.xml.XmlTagNameProvider - -class ComponentTagNameProvider : XmlTagNameProvider { - override fun addTagNameVariants( - elements: MutableList, - tag: XmlTag, - prefix: String? - ) { - val project = tag.project - - val result = mutableListOf() - FilenameIndex.processAllFileNames( - { - if (it.startsWith("x-") && it.endsWith(TempestFrameworkUtil.TEMPLATE_PREFIX)) { - result.add(it.removeSuffix(TempestFrameworkUtil.TEMPLATE_PREFIX)) - } - - true - }, - GlobalSearchScope.projectScope(project), - null, - ) - - result - .distinct() - .map { - LookupElementBuilder.create(it) - .withIcon(TempestIcons.TEMPEST) - .withTypeText("Tempest Component") - } - .apply { elements.addAll(this) } - } -} \ 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 15688d5..06e966c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -15,11 +15,8 @@ - - + From 85b55c1bd7d8d8427a6eb1a25c66cb4fd006cd3c Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 18 Sep 2025 09:58:49 +0200 Subject: [PATCH 02/17] chore: add new transparent icon Taken from the website. --- .../icons/tempest/icon_transparent.svg | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/resources/icons/tempest/icon_transparent.svg diff --git a/src/main/resources/icons/tempest/icon_transparent.svg b/src/main/resources/icons/tempest/icon_transparent.svg new file mode 100644 index 0000000..c09dd3d --- /dev/null +++ b/src/main/resources/icons/tempest/icon_transparent.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + From efc7a434ff1e6adf10958d85eceaca486e4473fd Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 18 Sep 2025 09:59:23 +0200 Subject: [PATCH 03/17] refactor: use transparent icon in the xml tag autocompletion --- src/main/resources/META-INF/webTypes.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/META-INF/webTypes.json b/src/main/resources/META-INF/webTypes.json index dbb5c19..0c0389c 100644 --- a/src/main/resources/META-INF/webTypes.json +++ b/src/main/resources/META-INF/webTypes.json @@ -1,9 +1,9 @@ { "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json", - "name": "Tempest", + "name": "Tempest Component", "version": "1.0.0", "description-markup": "markdown", - "default-icon": "icon", + "default-icon": "icons/tempest/icon_transparent.svg", "contributions": { "html": { "elements": [ From 08628c4e8457dca89e73315452e38f58b18f1232 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 18 Sep 2025 10:55:58 +0200 Subject: [PATCH 04/17] refactor: extend webTypes descriptions and attributes --- src/main/resources/META-INF/webTypes.json | 184 +++++++++++++++++++--- 1 file changed, 164 insertions(+), 20 deletions(-) diff --git a/src/main/resources/META-INF/webTypes.json b/src/main/resources/META-INF/webTypes.json index 0c0389c..811fc7e 100644 --- a/src/main/resources/META-INF/webTypes.json +++ b/src/main/resources/META-INF/webTypes.json @@ -2,45 +2,159 @@ "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json", "name": "Tempest Component", "version": "1.0.0", - "description-markup": "markdown", + "description-markup": "html", "default-icon": "icons/tempest/icon_transparent.svg", "contributions": { "html": { "elements": [ { "name": "x-base", - "description": "A base template you can install into your own project as a starting point. This one includes the Tailwind CDN for quick prototyping.", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-base" + "description": "

A base template you can install into your own project as a starting point. This one includes the Tailwind CDN for quick prototyping.

Available named slots:

  • head
  • scripts
", + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-base", + "attributes": [ + { + "name": "title", + "description": "The webpage's title", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-form", - "description": "This component provides a form element that will post by default and includes the csrf token out of the box.", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-form" + "description": "

This component provides a form element that will post by default and includes the csrf token out of the box.

", + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-form", + "attributes": [ + { + "name": "action", + "description": "", + "value": { + "type": "string", + "required": false + } + }, + { + "name": "method", + "description": "", + "value": { + "type": "string", + "required": false + } + }, + { + "name": "enctype", + "description": "", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-input", - "description": "A versatile input component that will render labels and validation errors automatically.", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-input" + "description": "

A versatile input component that will render labels and validation errors automatically.

", + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-input", + "attributes": [ + { + "name": "name", + "description": "", + "value": { + "type": "string", + "required": true + } + }, + { + "name": "label", + "description": "", + "value": { + "type": "string", + "required": false + } + }, + { + "name": "id", + "description": "", + "value": { + "type": "string", + "required": false + } + }, + { + "name": "type", + "description": "", + "value": { + "type": "string", + "required": false + } + }, + { + "name": "default", + "description": "", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-submit", - "description": "A submit button component that prefills with a \"Submit\" label.", + "description": "

A submit button component that prefills with a \"Submit\" label.

", "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-submit" }, { "name": "x-csrf-token", - "description": "Includes the CSRF token in a form.", + "description": "

Includes the CSRF token in a form.

", "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-csrf-token" }, { "name": "x-icon", - "description": "This component provides the ability to inject any icon from the Iconify project in your templates.", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-icon" + "description": "

This component provides the ability to inject any icon from the Iconify project in your templates.

", + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-icon", + "attributes": [ + { + "name": "name", + "description": "", + "value": { + "type": "string", + "required": true + } + }, + { + "name": "class", + "description": "", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-vite-tags", - "description": "Tempest has built-in support for Vite, the most popular front-end development server and build tool. This component simply inject registered entrypoints where it is called.", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-vite-tags" + "description": "

Tempest has built-in support for Vite, the most popular front-end development server and build tool. This component simply inject registered entrypoints where it is called.

", + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-vite-tags", + "attributes": [ + { + "name": "entrypoints", + "description": "", + "value": { + "type": "array", + "required": false + } + }, + { + "name": "entrypoint", + "description": "", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-template", @@ -50,23 +164,53 @@ { "name": "x-slot", "description": "", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-slot" + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-slot", + "attributes": [ + { + "name": "name", + "description": "The name of the slot", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-markdown", "description": "", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-markdown" + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-markdown", + "attributes": [ + { + "name": "content", + "description": "The markdown content from a variable", + "value": { + "type": "string", + "required": false + } + } + ] }, { "name": "x-component", "description": "", - "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-component" + "doc-url": "https://tempestphp.com/1.x/internals/view-spec#x-component", + "attributes": [ + { + "name": "is", + "description": "", + "value": { + "type": "string", + "required": true + } + } + ] } ], "attributes": [ { "name": ":if", - "description": "Conditional attribute `if`. The element is present in the final html when the condition is true.", + "description": "Conditional attribute if. The element is present in the final html when the condition is true.", "doc-url": "https://tempestphp.com/1.x/internals/view-spec#control-structures", "inject-language": "InjectablePHP", "value": { @@ -76,7 +220,7 @@ }, { "name": ":elseif", - "description": "Conditional attribute `else if`. The element is present in the final html when the condition is true.", + "description": "Conditional attribute else if. The element is present in the final html when the condition is true.", "doc-url": "https://tempestphp.com/1.x/internals/view-spec#control-structures", "inject-language": "InjectablePHP", "value": { @@ -95,7 +239,7 @@ }, { "name": ":foreach", - "description": "Loop attribute `for each`. Values should be as PHP `foreach` loop. Introduce a local variable for each value in the collection.", + "description": "Loop attribute for each. Values should be as PHP foreach loop. Introduce a local variable for each value in the collection.", "doc-url": "https://tempestphp.com/1.x/internals/view-spec#control-structures", "inject-language": "InjectablePHP", "value": { @@ -105,7 +249,7 @@ }, { "name": ":forelse", - "description": "Loop attribute combined with `foreach`. The element is present in the final html when the collection is empty.", + "description": "Loop attribute combined with foreach. The element is present in the final html when the collection is empty.", "doc-url": "https://tempestphp.com/1.x/internals/view-spec#control-structures", "inject-language": "InjectablePHP", "value": { From c213ff1a512586d398aba7dd3495d796d16d995a Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 18 Sep 2025 10:59:49 +0200 Subject: [PATCH 05/17] chore: format files --- .../ComponentGotoDeclarationHandler.kt | 14 +++------ .../github/tempest/framework/TempestBundle.kt | 3 +- .../views/injection/PHPLanguageInjector.kt | 29 +++++++------------ 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt b/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt index d68263f..0a9c395 100644 --- a/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt +++ b/src/main/kotlin/com/github/tempest/framework/ComponentGotoDeclarationHandler.kt @@ -4,16 +4,14 @@ import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler import com.intellij.openapi.editor.Editor import com.intellij.openapi.vfs.findPsiFile import com.intellij.psi.PsiElement +import com.intellij.psi.html.HtmlTag import com.intellij.psi.search.FilenameIndex import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil -import com.intellij.psi.html.HtmlTag class ComponentGotoDeclarationHandler : GotoDeclarationHandler { override fun getGotoDeclarationTargets( - element: PsiElement?, - offset: Int, - editor: Editor? + element: PsiElement?, offset: Int, editor: Editor? ): Array? { if (element == null) return null @@ -24,16 +22,12 @@ class ComponentGotoDeclarationHandler : GotoDeclarationHandler { val result = mutableListOf() FilenameIndex.processFilesByName( - tag.name + TempestFrameworkUtil.TEMPLATE_PREFIX, - true, - GlobalSearchScope.projectScope(project), - { + tag.name + TempestFrameworkUtil.TEMPLATE_PREFIX, true, GlobalSearchScope.projectScope(project), { val psiFile = it.findPsiFile(project) ?: return@processFilesByName true result.add(psiFile) true - } - ) + }) if (result.isEmpty()) return null diff --git a/src/main/kotlin/com/github/tempest/framework/TempestBundle.kt b/src/main/kotlin/com/github/tempest/framework/TempestBundle.kt index d4663e6..6e75701 100644 --- a/src/main/kotlin/com/github/tempest/framework/TempestBundle.kt +++ b/src/main/kotlin/com/github/tempest/framework/TempestBundle.kt @@ -10,8 +10,7 @@ private const val BUNDLE = "messages.TempestBundle" object TempestBundle : DynamicBundle(BUNDLE) { @JvmStatic - fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = - getMessage(key, *params) + fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getMessage(key, *params) @Suppress("unused") @JvmStatic diff --git a/src/main/kotlin/com/github/tempest/framework/views/injection/PHPLanguageInjector.kt b/src/main/kotlin/com/github/tempest/framework/views/injection/PHPLanguageInjector.kt index 8e5b254..176ec89 100644 --- a/src/main/kotlin/com/github/tempest/framework/views/injection/PHPLanguageInjector.kt +++ b/src/main/kotlin/com/github/tempest/framework/views/injection/PHPLanguageInjector.kt @@ -16,8 +16,7 @@ import com.intellij.psi.xml.XmlToken class PHPLanguageInjector : MultiHostInjector { override fun getLanguagesToInject( - registrar: MultiHostRegistrar, - element: PsiElement + registrar: MultiHostRegistrar, element: PsiElement ) { when (element) { is XmlAttributeValue -> { @@ -27,18 +26,14 @@ class PHPLanguageInjector : MultiHostInjector { val injectableHost = element as? PsiLanguageInjectionHost ?: return - registrar - .startInjecting(Language.findLanguageByID("PHP") ?: return) - .addPlace("", injectableHost, TextRange(0, injectableHost.textLength)) - .doneInjecting() + registrar.startInjecting(Language.findLanguageByID("PHP") ?: return) + .addPlace("", injectableHost, TextRange(0, injectableHost.textLength)).doneInjecting() } is HtmlTag -> { - element.children - .mapNotNull { it as? HtmlRawTextImpl } - .forEach { child-> - injectIntoText(HtmlTextInjectionHostWrapper(child), registrar) - } + element.children.mapNotNull { it as? HtmlRawTextImpl }.forEach { child -> + injectIntoText(HtmlTextInjectionHostWrapper(child), registrar) + } } is XmlText -> { @@ -53,13 +48,11 @@ class PHPLanguageInjector : MultiHostInjector { "{!!" to "!!}", "{{" to "}}", ) + private fun injectIntoText( - element: PsiLanguageInjectionHost, - registrar: MultiHostRegistrar + element: PsiLanguageInjectionHost, registrar: MultiHostRegistrar ) { - val children = element.node.children().toList() - .filter { it is XmlToken } - .apply { if (size < 2) return } + val children = element.node.children().toList().filter { it is XmlToken }.apply { if (size < 2) return } // println("children: $children") val openTag = children.find { it.text == "{!!" || it.text == "{{" }?.psi ?: return @@ -71,9 +64,7 @@ class PHPLanguageInjector : MultiHostInjector { val textRange = TextRange(openTag.textRangeInParent.endOffset, closeTag.startOffsetInParent) // println("injecting ${language} into $element, $textRange") - registrar.startInjecting(language) - .addPlace("", element, textRange) - .doneInjecting() + registrar.startInjecting(language).addPlace("", element, textRange).doneInjecting() } } From aa6281a12d463f678be6e754093bcecc83992914 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 18 Sep 2025 11:11:15 +0200 Subject: [PATCH 06/17] refactor: build against PHPStorm specifically --- .idea/gradle.xml | 2 +- gradle.properties | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f2c1963..8b0d111 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -5,7 +5,7 @@