LookupElementBuilder usage to reference PHP class (from XML)

Answered

Hi!

 

I'm writing a plugin for PhpStorm that in principle suggests XML tag names based on existence of some PHP classes.

For example, if the project has a PHP class defined as Vendor/Components/Buttons/MyButton, there would be autocompletion in HTML/XML that suggests <Buttons.MyButton/>.

I got this mostly figured out and it actually works well:

class ComponentProvider : XmlTagNameProvider {
override fun addTagNameVariants(elements: MutableList<LookupElement>?, tag: XmlTag, prefix: String?) {
elements?.addAll(
getComponentClasses(tag.project)
.map {
LookupElementBuilder.create(it.componentName)
}
)
}

private fun getComponentClasses(project: Project) =
PhpIndex
.getInstance(project)
.getAllSubclasses("System\\Components\\AbstractComponent")
.asSequence()
.filterNot { it.isAbstract || it.isInterface || it.isTrait }
.map {
object {
val componentClass = it
val componentName = it.fqn
.split(Regex.fromLiteral("\\Components\\"), 2)
.last()
.replace('\\', '.')
}
}
}

What I'm missing is how to link/reference the element to the declaring class. I.e. ctrl+clicking <Buttons.MyButton> doesn't do anything at the moment, but it should open the PHP class.

At one point, instead of LookupElementBuilder.create(it.componentName) I tried both LookupElementBuilder.createWithSmartPointer(it.componentName, it.componentClass) and LookupElementBuilder.create(it.componentName).withPsiElement(it.componentClass), but neither worked.

 

So, here are my questions:

  1. how do I achieve this referencing?
  2. if the psielement stuff in LookupElementBuilder is not for this purpose, then what is it for?
  3. the flow is a bit confusing for me: if the provider I registered is only invoked for adding suggestions, how does the ide/platform know to link any existing code to the classes/psi-element?
  4. I saw some pretty good guides and tutorials https://plugins.jetbrains.com/docs/intellij, but I can't seem to find any fine-grained documentation, such as for the LookupElementBuilder (and the code comments are also a bit lacking) - am I missing something?

 

Thanks, Chris.

4 comments
Comment actions Permalink
Official comment

Hey. Indeed, LookupElementBuilder is responsible solely for items in completion list. Please see https://plugins.jetbrains.com/docs/intellij/references-and-resolve.html to learn how to implement reference and resolve. This has some advantages over GotoDeclarationHandler since it'll enable additional features like uniform renaming.

Comment actions Permalink

I achieved the desired functionality using GotoDeclarationHandler, is this the right approach? The rest of the questions remain unanswered though.

0
Comment actions Permalink

Kirill Smelov I tried following the document and ended up implementing a PsiReferenceContributor.

The code seems to work in theory, but eventually fails with "Reference element is not the same element..." error.

It's not clear to me why one is able to provide references, but they have to point to the same target.

Here's the code:

override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(
PlatformPatterns.psiElement(XmlTag::class.java),
object : PsiReferenceProvider() {
override fun getReferencesByElement(
element: PsiElement,
context: ProcessingContext
): Array<PsiReference> =
(element as? XmlTag)
?.let {
// find name of component matching with xml element name
val sourceComponentName = fqnToComponentName(element.name)
return getComponentClasses(element.project)
.filter {
// find any components matching the name (needed because
// currently I can have multiple components with the same name)

it.componentName == sourceComponentName
}
.map {
// componentClass contains a PhpClass PSI element
ClassReferenceImpl(it.componentClass.node)
}
.toArray()
} ?: PsiReference.EMPTY_ARRAY
}
)
}
0

Please sign in to leave a comment.