Skip to content

Commit

Permalink
feat: Add mixin autocompletion, fixes #25
Browse files Browse the repository at this point in the history
  • Loading branch information
shyim committed Oct 4, 2021
1 parent 603ab33 commit ced2390
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext
import com.intellij.util.indexing.FileBasedIndex
import de.shyim.shopware6.index.AdminComponentIndex
import de.shyim.shopware6.util.AdminSnippetUtil
import de.shyim.shopware6.util.EntityDefinitionUtil
import de.shyim.shopware6.util.FeatureFlagUtil
import de.shyim.shopware6.util.JavaScriptPattern
import de.shyim.shopware6.util.*
import icons.ShopwareToolBoxIcons

class JavaScriptCompletionProvider : CompletionContributor() {
Expand Down Expand Up @@ -89,5 +86,22 @@ class JavaScriptCompletionProvider : CompletionContributor() {
}
}
)

extend(
CompletionType.BASIC,
JavaScriptPattern.getMixinGetByName(),
object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {

val project: Project = parameters.position.project

result.addAllElements(AdminMixinUtil.getAllLookupItems(project))
}
}
)
}
}
92 changes: 92 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/index/AdminMixinIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package de.shyim.shopware6.index

import com.intellij.lang.javascript.JSTokenTypes
import com.intellij.lang.javascript.JavaScriptFileType
import com.intellij.lang.javascript.psi.JSCallExpression
import com.intellij.lang.javascript.psi.JSReferenceExpression
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.util.indexing.*
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import de.shyim.shopware6.index.dict.AdminMixin
import de.shyim.shopware6.index.externalizer.ObjectStreamDataExternalizer
import gnu.trove.THashMap

class AdminMixinIndex : FileBasedIndexExtension<String, AdminMixin>() {
private val EXTERNALIZER = ObjectStreamDataExternalizer<AdminMixin>()

override fun getName(): ID<String, AdminMixin> {
return key
}

override fun getVersion(): Int {
return 1
}

override fun dependsOnFileContent(): Boolean {
return true
}

override fun getIndexer(): DataIndexer<String, AdminMixin, FileContent> {
return DataIndexer { inputData ->
val mixins = THashMap<String, AdminMixin>()

val stringLiteral = PlatformPatterns.psiElement(JSTokenTypes.STRING_LITERAL)

inputData.psiFile.acceptChildren(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is JSCallExpression) {
if (element.methodExpression == null || element.argumentList == null || element.argumentList!!.arguments.size < 2) {
return
}

if (element.methodExpression!!.firstChild is JSReferenceExpression && element.methodExpression!!.firstChild.firstChild is LeafPsiElement && element.methodExpression!!.firstChild.firstChild.text == "Mixin") {
if (element.methodExpression!!.lastChild is LeafPsiElement && element.methodExpression!!.lastChild.text == "register") {
val arguments = element.argumentList!!.arguments

val mixingName = arguments.get(0).firstChild

if (!stringLiteral.accepts(mixingName)) {
return
}

val mixin = AdminMixin(
mixingName.text.replace("'", "").replace("\"", ""),
inputData.file.path
)

mixins[mixin.name] = mixin
}

return
}
}

super.visitElement(element)
}
})

return@DataIndexer mixins
}
}

override fun getKeyDescriptor(): KeyDescriptor<String> {
return EnumeratorStringDescriptor.INSTANCE
}

override fun getValueExternalizer(): ObjectStreamDataExternalizer<AdminMixin> {
return EXTERNALIZER
}

companion object {
val key = ID.create<String, AdminMixin>("de.shyim.shopware6.admin.mixin")
}

override fun getInputFilter(): FileBasedIndex.InputFilter {
return object : DefaultFileTypeSpecificInputFilter(JavaScriptFileType.INSTANCE) {
}
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/index/dict/AdminMixin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.shyim.shopware6.index.dict

import org.apache.commons.lang.builder.HashCodeBuilder
import java.io.Serializable
import java.util.*

class AdminMixin(val name: String, val file: String) : Serializable {
override fun hashCode(): Int {
return HashCodeBuilder()
.append(this.name)
.append(this.file)
.toHashCode()
}

override fun equals(other: Any?): Boolean {
return other is AdminMixin &&
Objects.equals(other.name, this.name) &&
Objects.equals(other.file, this.file)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package de.shyim.shopware6.navigation

import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import de.shyim.shopware6.util.AdminMixinUtil
import de.shyim.shopware6.util.JavaScriptPattern

class AdminMixinGoToDeclareHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(
element: PsiElement?,
offset: Int,
editor: Editor?
): Array<PsiElement>? {
if (editor === null || editor.project === null || element == null) {
return null
}

val project = editor.project!!

val psiElements: MutableList<PsiElement> = ArrayList()

if (JavaScriptPattern.getMixinGetByName().accepts(element)) {
val text = element.text.replace("\"", "").replace("'", "")

psiElements.addAll(AdminMixinUtil.getTargets(project, text))
}

return psiElements.toTypedArray()
}
}
61 changes: 61 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/util/AdminMixinUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package de.shyim.shopware6.util

import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.FileBasedIndex
import de.shyim.shopware6.index.AdminMixinIndex
import de.shyim.shopware6.index.dict.AdminMixin
import icons.ShopwareToolBoxIcons

object AdminMixinUtil {
fun getAllMixins(project: Project): MutableList<AdminMixin> {
val definitions: MutableList<AdminMixin> = ArrayList()

for (key in FileBasedIndex.getInstance().getAllKeys(AdminMixinIndex.key, project)) {
val vals = FileBasedIndex.getInstance()
.getValues(AdminMixinIndex.key, key, GlobalSearchScope.allScope(project))

definitions.addAll(vals)
}

return definitions
}

fun getAllLookupItems(project: Project): MutableList<LookupElement> {
val list: MutableList<LookupElement> = ArrayList()

getAllMixins(project).forEach {
list.add(
LookupElementBuilder.create(it.name)
.withIcon(ShopwareToolBoxIcons.SHOPWARE)
)
}

return list
}

fun getTargets(project: Project, key: String): MutableCollection<PsiElement> {
val psiElements: MutableList<PsiElement> = java.util.ArrayList()

getAllMixins(project).forEach {
if (it.name == key) {
val file = LocalFileSystem.getInstance().findFileByPath(it.file)

if (file != null) {
val psi = PsiManager.getInstance(project).findFile(file)

if (psi != null) {
psiElements.add(psi)
}
}
}
}

return psiElements
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/util/JavaScriptPattern.kt
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,30 @@ object JavaScriptPattern {
)
)
}

fun getMixinGetByName(): PsiElementPattern.Capture<PsiElement> {
return PlatformPatterns.psiElement()
.withParent(
PlatformPatterns.psiElement(JSLiteralExpression::class.java)
.afterSibling(
PlatformPatterns.psiElement().withText("(")
)
.withParent(
PlatformPatterns.psiElement(JSArgumentList::class.java)
.withParent(
PlatformPatterns.psiElement(JSCallExpression::class.java).withFirstChild(
PlatformPatterns.psiElement(JSReferenceExpression::class.java)
.withFirstChild(
PlatformPatterns.psiElement(JSReferenceExpression::class.java)
.withFirstChild(
PlatformPatterns.psiElement().withText("Mixin")
)
)
.withLastChild(PlatformPatterns.psiElement().withText("getByName"))
)
)
)
)
.withLanguage(JavascriptLanguage.INSTANCE)
}
}
11 changes: 8 additions & 3 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<fileBasedIndex implementation="de.shyim.shopware6.index.SystemConfigIndex"/>
<fileBasedIndex implementation="de.shyim.shopware6.index.ShopwareBundleIndex"/>
<fileBasedIndex implementation="de.shyim.shopware6.index.EntityDefinitionIndex"/>
<fileBasedIndex implementation="de.shyim.shopware6.index.AdminMixinIndex"/>

<gotoDeclarationHandler implementation="de.shyim.shopware6.navigation.FeatureFlagGoToDeclareHandler"/>
<gotoDeclarationHandler implementation="de.shyim.shopware6.navigation.AdminComponentGoToDeclareHandler"/>
Expand All @@ -36,15 +37,19 @@
<gotoDeclarationHandler implementation="de.shyim.shopware6.navigation.ThemeConfigGoToDeclareHandler"/>
<gotoDeclarationHandler implementation="de.shyim.shopware6.navigation.SystemConfigGoToDeclareHandler"/>
<gotoDeclarationHandler implementation="de.shyim.shopware6.navigation.EntityDefinitionGoToDeclareHandler"/>
<gotoDeclarationHandler implementation="de.shyim.shopware6.navigation.AdminMixinGoToDeclareHandler"/>

<completion.contributor language="PHP"
implementationClass="de.shyim.shopware6.completion.PhpCompletionProvider"/>
implementationClass="de.shyim.shopware6.completion.PhpCompletionProvider"
order="first"/>

<completion.contributor language="Twig"
implementationClass="de.shyim.shopware6.completion.TwigCompletionProvider"/>
implementationClass="de.shyim.shopware6.completion.TwigCompletionProvider"
order="first"/>

<completion.contributor language="JavaScript"
implementationClass="de.shyim.shopware6.completion.JavaScriptCompletionProvider"/>
implementationClass="de.shyim.shopware6.completion.JavaScriptCompletionProvider"
order="first"/>

<xml.tagNameProvider implementation="de.shyim.shopware6.xml.AdminComponentProvider"/>
<xml.elementDescriptorProvider implementation="de.shyim.shopware6.xml.AdminComponentProvider"/>
Expand Down

0 comments on commit ced2390

Please sign in to comment.