How do I get parameter data from Php method under current offset?

EDIT: working result in last comment, improved Kotlin version here: https://gist.github.com/zvirdaniel/0672e96a8a736c4a7a6f19b64f74de72

Hello,
please help me with the following problem. I want to get the parameter data from the GetTemplate method, and open the coresponding file in the PhpStorm. The following picture will explain. So I have the following code in php:

And I want to get the templateName parameter from the (very frequently used) GetTemplate method. The templateName string is pointing to a file on the server, so I need to extract that string. This is my code:

public class OpenGetTemplateFile extends AnAction {
@Override
public void update(AnActionEvent event) {
final PsiFile psiFile = event.getData(CommonDataKeys.PSI_FILE); // file.getName() = GoodsDeatilCore.php
boolean isPhp = Objects.equals(psiFile.getFileType().getName(), "PHP");
event.getPresentation().setEnabledAndVisible(isPhp); // show only in php files
}

@Override
public void actionPerformed(AnActionEvent event) {
final VirtualFile virtualFile = DataKeys.VIRTUAL_FILE.getData(event.getDataContext());
final Editor editor = event.getData(CommonDataKeys.EDITOR);
final Project project = event.getData(CommonDataKeys.PROJECT);
final Document document = editor.getDocument();
final PsiFile file = event.getData(CommonDataKeys.PSI_FILE);
// What next? I tried a lot of things, but nothing worked.
}
}

<action id="OpenGetTemplateFile" class="OpenGetTemplateFile" text="Open GetTemplate File"
description="Open GetTemplate File in editor">
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
</action>

 

12 comments
Comment actions Permalink
Official comment

Have you tried to implement "PsiReferenceContributor"? It's very powerful technic which allows you to provide custom navigation from one element to another. 

In your case, it makes sense to add a reference from string literal 'goods/GoodsDetails' to target file. You'll need to implement "PsiReferenceContributor" which would map custom "PsiReferenceProvider" with "ElementPattern".

The most tricky part is to implement `ElementPattern` to register references only in your particular case and not in all strings. In your case, it will look like this:

public class GetTemplateReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
PsiNamePatternCondition<PsiElement> templateMethodPattern =
new PsiNamePatternCondition<PsiElement>("withMethodName", StandardPatterns.string().matches("GetTemplate")) {
@Override
public String getPropertyValue(@NotNull Object o) {
return o instanceof MethodReference ? ((MethodReference)o).getName() : null;
}
};

registrar.registerReferenceProvider(
psiElement().withElementType(PhpElementTypes.STRING).
withParent(psiElement().withElementType(PhpElementTypes.PARAMETER_LIST).
withParent(psiElement().withElementType(PhpElementTypes.METHOD_REFERENCE).with(templateMethodPattern))),
new GetTemplateReferenceProvider(), PsiReferenceRegistrar.DEFAULT_PRIORITY);
}
}

 

Here's more information about ReferenceContributor: http://www.jetbrains.org/intellij/sdk/docs/tutorials/custom_language_support/reference_contributor.html

 

Comment actions Permalink

No, I have not, this is the first time I hear of such thing. But how do I implement 'GetTemplateReferenceProvider' class? I can't find any information about that on the link you've attached. And where is the result of all this saved in? I see it gets to the parameter I need, but how do I process it further? Ideally, this plugin should enable 'Go to declaration' which would open the file mentioned in the parameter of GetTemplate. But first things first, how do I retrieve the parameter from all of this?

0
Comment actions Permalink

I guess word reference was not clear? In terms of Intellij plugin development, it's a link between different psi elements. Here you can read more about this term: http://www.jetbrains.org/intellij/sdk/docs/reference_guide/custom_language_support/references_and_resolve.html

'goods/GoodsDetails' is a real file in your project, isn't it? So it would be nice to have a navigation between 'goods/GoodsDetails' string and 'GoodsDetails' file. The simplest way to achieve this is to create a 'reference' between templateName parameter in GetTemplate method and file you want to navigate. Then you'll be able to navigate with "Go To Declaration" from string 'goods/GoodsDetails' (or any other string passed as a parameter to GetTemplate method) to 'GoodsDetails' file.

The code from the previous message will search through your project and call `GetTemplateReferenceProvider#getReferencesByElement` for each GetTemplate method call. `GetTemplateReferenceProvider` - is a reference provider you need to implement which will receive string from templateName parameter and return reference to a corresponding file.

0
Comment actions Permalink

Yes, I've read the link you've and it's clear now, thanks.

Yes, it is a real (phtml) file. So that means I have to implement PsiReference(), right? Are there any examples of that, other than on your website?

0
Comment actions Permalink

> Are there any examples of that, other than on your website?

Yes, see the section "Define a reference" in http://www.jetbrains.org/intellij/sdk/docs/tutorials/custom_language_support/reference_contributor.html

0
Comment actions Permalink

How can I debug the getReferencesByElement() method? For example, inserting sout to the top of the method does not do anything. Breakpoints dont seem to trigger debug too.

public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
System.out.println(element.toString());
PsiLiteralExpression literalExpression = (PsiLiteralExpression) element;
String value = literalExpression.getValue() instanceof String ?
(String) literalExpression.getValue() : null;

if (value != null) {
return new PsiReference[]{
new PhpGetTemplateReference(element)};
} else {
return PsiReference.EMPTY_ARRAY;
}
}

 

0
Comment actions Permalink

Breakpoints should work fine. Please make sure you registered reference contributor in plugin.xml. Also this method is called lazy, so you need to placed your cursor inside templateName string parameter in GetTemplate method call.

0
Comment actions Permalink

I did register it, but it does not enter debug mode anyway while having the cursor in the GetTemplate call.

<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<psi.referenceContributor implementation="Test.GetTemplateReferenceContributor"/>
</extensions>

EDIT: The only place IDE enter debug mode is on this line (maybe the pattern doesnt match?)

public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { // code you've sent
0
Comment actions Permalink

I see, 'GetTemplate' is a function, not a method, hence you need to adjust ''GetTemplateReferenceContributor'':

@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
PsiNamePatternCondition<PsiElement> templateMethodPattern =
new PsiNamePatternCondition<PsiElement>("withFunctionName", StandardPatterns.string().matches("GetTemplate")) {
@Override
public String getPropertyValue(@NotNull Object o) {
return o instanceof FunctionReference ? ((FunctionReference)o).getName() : null;
}
};

registrar.registerReferenceProvider(
psiElement().withElementType(PhpElementTypes.STRING).
withParent(psiElement().withElementType(PhpElementTypes.PARAMETER_LIST).
withParent(psiElement().withElementType(PhpElementTypes.FUNCTION_CALL).with(templateMethodPattern))),
new GetTemplateReferenceProvider(), PsiReferenceRegistrar.DEFAULT_PRIORITY);
}
0
Comment actions Permalink

It works! I mean, debug works, and element contains the parameter I wanted, hence my original question is answered. If I get lost again, I'll make a new post, with a new question. But for now, thank you! I definitively would not get that far without your help. Have a nice day! :D

0
Comment actions Permalink

Great! I'm happy to help.

0
Comment actions Permalink

Everything works as intended, the working code (for anyone interested in the future) follows:

public class GetTemplateReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
PsiNamePatternCondition<PsiElement> templateMethodPattern =
new PsiNamePatternCondition<PsiElement>("withFunctionName", StandardPatterns.string().matches("GetTemplate")) {
@Override
public String getPropertyValue(@NotNull Object o) {
return o instanceof FunctionReference ? ((FunctionReference)o).getName() : null;
}
};

registrar.registerReferenceProvider(
psiElement().withElementType(PhpElementTypes.STRING).
withParent(psiElement().withElementType(PhpElementTypes.PARAMETER_LIST).
withParent(psiElement().withElementType(PhpElementTypes.FUNCTION_CALL).with(templateMethodPattern))),
new GetTemplateReferenceProvider(), PsiReferenceRegistrar.DEFAULT_PRIORITY);
}
}
public class GetTemplateReferenceProvider extends com.intellij.psi.PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
Project project = element.getProject();
String parameterData = element.getText().substring(1, element.getTextLength() - 1);
String targetString = "standard/views/" + parameterData + ".phtml";
VirtualFile baseDirectory = project.getBaseDir();
VirtualFile virtualFile = baseDirectory.findFileByRelativePath(targetString);

if (virtualFile == null || !(virtualFile.exists())) {
return PsiReference.EMPTY_ARRAY;
}

return new PsiReference[]{
new PhpGetTemplateReference(element)
};
}
}
@Nullable
@Override
public PsiElement resolve() {
Project project = myElement.getProject();
String parameterData = myElement.getText().substring(1, myElement.getTextLength() - 1);
String targetString = "standard/views/" + parameterData + ".phtml";
VirtualFile baseDirectory = project.getBaseDir();
VirtualFile virtualFile = baseDirectory.findFileByRelativePath(targetString);
assert virtualFile != null;
return PsiManager.getInstance(project).findFile(virtualFile);
}
0

Please sign in to leave a comment.