diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/action/SymfonySymbolSearchAction.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/action/SymfonySymbolSearchAction.java new file mode 100644 index 000000000..6294379bd --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/action/SymfonySymbolSearchAction.java @@ -0,0 +1,285 @@ +package fr.adrienbrault.idea.symfony2plugin.action; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.actions.GotoActionBase; +import com.intellij.ide.util.gotoByName.ChooseByNamePopup; +import com.intellij.navigation.ChooseByNameContributor; +import com.intellij.navigation.ChooseByNameContributorEx; +import com.intellij.navigation.NavigationItem; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.Processor; +import com.intellij.util.indexing.FindSymbolParameters; +import com.intellij.util.indexing.IdFilter; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; +import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; +import fr.adrienbrault.idea.symfony2plugin.action.model.SymfonySymbolSearchModel; +import fr.adrienbrault.idea.symfony2plugin.dic.ContainerService; +import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityHelper; +import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityReference; +import fr.adrienbrault.idea.symfony2plugin.navigation.NavigationItemEx; +import fr.adrienbrault.idea.symfony2plugin.routing.Route; +import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper; +import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; +import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtension; +import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigExtensionParser; +import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; +import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; +import fr.adrienbrault.idea.symfony2plugin.util.SymfonyCommandUtil; +import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyCommand; +import icons.TwigIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Daniel Espendiller + */ +public class SymfonySymbolSearchAction extends GotoActionBase { + + @Override + protected void gotoActionPerformed(AnActionEvent paramAnActionEvent) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.file"); + Project localProject = paramAnActionEvent.getData(CommonDataKeys.PROJECT); + if (localProject != null) { + SymfonySymbolSearchModel searchModel = new SymfonySymbolSearchModel(localProject, new ChooseByNameContributor[] { new Symfony2NavigationContributor(localProject) }); + showNavigationPopup(paramAnActionEvent, searchModel, new MyGotoCallback(), null, true); + } + } + + @Override + public void update(AnActionEvent event) { + super.update(event); + + Project project = event.getData(CommonDataKeys.PROJECT); + + boolean enabled = Symfony2ProjectComponent.isEnabled(project); + + event.getPresentation().setVisible(enabled); + event.getPresentation().setEnabled(enabled); + + } + + private static class Symfony2NavigationContributor implements ChooseByNameContributorEx, DumbAware { + @NotNull + final private Project project; + + private ContainerCollectionResolver.ServiceCollector serviceCollector; + private Map> templateMap; + private Map routes; + private Set twigMacroSet; + private Map lookupElements; + + private Symfony2NavigationContributor(@NotNull Project project) { + this.project = project; + } + + private ContainerCollectionResolver.ServiceCollector getServiceCollector() { + if(this.serviceCollector == null) { + this.serviceCollector = ContainerCollectionResolver.ServiceCollector.create(this.project); + } + + return this.serviceCollector; + } + + private Map> getTemplateMap() { + if(this.templateMap == null) { + this.templateMap = TwigUtil.getTemplateMap(this.project, true); + } + + return this.templateMap; + } + + private Map getRoutes() { + if(this.routes == null) { + this.routes = RouteHelper.getAllRoutes(project); + } + + return this.routes; + } + + private Set getTwigMacroSet() { + if(this.twigMacroSet == null) { + this.twigMacroSet = TwigUtil.getTwigMacroSet(this.project); + } + + return this.twigMacroSet; + } + + private Map getModelLookupElements() { + + if(this.lookupElements == null) { + List modelLookupElements = EntityReference.getModelLookupElements(this.project); + + this.lookupElements = new HashMap<>(); + for(LookupElement lookupElement: modelLookupElements) { + this.lookupElements.put(lookupElement.getLookupString(), lookupElement); + } + + } + + return this.lookupElements; + } + + @Override + public void processNames(@NotNull Processor processor, @NotNull GlobalSearchScope scope, @Nullable IdFilter filter) { + for(String name: getServiceCollector().getServices().keySet()) { + processor.process(name); + } + + for(String templateName: getTemplateMap().keySet()) { + processor.process(templateName); + } + + for(String name: getRoutes().keySet()) { + processor.process(name); + } + + for(Map.Entry entry: getRoutes().entrySet()) { + processor.process(entry.getKey()); + String path = entry.getValue().getPath(); + if(path != null) { + processor.process(path); + } + } + + for(String name: getTwigMacroSet()) { + processor.process(name); + } + + for(String name: getModelLookupElements().keySet()) { + processor.process(name); + } + + for(SymfonyCommand command: SymfonyCommandUtil.getCommands(project)) { + processor.process(command.getName()); + } + + // Twig Extensions + for (Map extensionMap : Arrays.asList(TwigExtensionParser.getFilters(project), TwigExtensionParser.getFunctions(project))) { + for(String twigFilter: extensionMap.keySet()) { + processor.process(twigFilter); + } + } + } + + @Override + public void processElementsWithName(@NotNull String name, @NotNull Processor processor, @NotNull FindSymbolParameters parameters) { + + for(ContainerService containerService: getServiceCollector().collect()) { + if(containerService.getName().equals(name)) { + + String serviceClass = getServiceCollector().resolve(name); + if (serviceClass != null) { + PhpClass phpClass = PhpElementsUtil.getClassInterface(this.project, serviceClass); + if(phpClass != null) { + processor.process(new NavigationItemEx(phpClass, containerService.getName(), containerService.isWeak() ? Symfony2Icons.SERVICE_PRIVATE_OPACITY : Symfony2Icons.SERVICE, "Service")); + } + } + + } + } + + // @TODO name filter + if(getTemplateMap().containsKey(name)) { + for (PsiFile psiFile : TwigUtil.getTemplatePsiElements(project, name)) { + processor.process(new NavigationItemEx(psiFile, name, psiFile.getFileType().getIcon(), "Template")); + } + } + + Set controllers = new HashSet<>(); + if(getRoutes().containsKey(name)) { + String controllerName = getRoutes().get(name).getController(); + if(controllerName != null) { + controllers.add(controllerName); + } + } + + // route path: /foo/bar + for (Route route : getRoutes().values()) { + if(!name.equals(route.getPath())) { + continue; + } + + String controller = route.getController(); + if(controller != null) { + controllers.add(controller); + } + } + + if(!controllers.isEmpty()) { + for (String controller : controllers) { + for(PsiElement psiElement: RouteHelper.getMethodsOnControllerShortcut(this.project, controller)) { + processor.process(new NavigationItemEx(psiElement, name, Symfony2Icons.ROUTE, "Route")); + } + } + } + + if(getTwigMacroSet().contains(name)) { + for(PsiElement macroTarget: TwigUtil.getTwigMacroTargets(project, name)) { + processor.process(new NavigationItemEx(macroTarget, name, TwigIcons.TwigFileIcon, "Macro")); + } + } + + if(getModelLookupElements().containsKey(name)) { + PsiElement[] psiElements = EntityHelper.getModelPsiTargets(this.project, name); + + getModelLookupElements().get(name).getLookupString(); + for(PsiElement target: psiElements) { + processor.process(new NavigationItemEx(target, name, target.getIcon(0), "Entity")); + } + } + + for (SymfonyCommand symfonyCommand : SymfonyCommandUtil.getCommands(project)) { + if(symfonyCommand.getName().equals(name)) { + processor.process(new NavigationItemEx(symfonyCommand.getPhpClass(), name, Symfony2Icons.SYMFONY, "Command")); + } + } + + // Twig Extensions + for (Map extensionMap : Arrays.asList(TwigExtensionParser.getFilters(project), TwigExtensionParser.getFunctions(project))) { + for(Map.Entry twigFunc: extensionMap.entrySet()) { + if(twigFunc.getKey().equals(name)) { + TwigExtension twigExtension = twigFunc.getValue(); + PsiElement extensionTarget = TwigExtensionParser.getExtensionTarget(project, twigExtension); + if(extensionTarget != null) { + processor.process(new NavigationItemEx(extensionTarget, name, TwigExtensionParser.getIcon(twigExtension.getTwigExtensionType()), twigExtension.getTwigExtensionType().toString())); + } + } + } + } + } + + @NotNull + @Override + public String @NotNull [] getNames(Project project, boolean includeNonProjectItems) { + return new String[0]; + } + + @NotNull + @Override + public NavigationItem @NotNull [] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) { + return new NavigationItem[0]; + } + } + + static class MyGotoCallback extends GotoActionCallback { + @Override + public void elementChosen(ChooseByNamePopup popup, Object element) { + if(element instanceof NavigationItem) { + ((NavigationItem) element).navigate(true); + } + } + } +} + diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 9f35223f0..86e967f2a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -878,6 +878,9 @@ + + +