From b4f73fb1ee28cb5cbb008f5b89ed61a6403464fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=91=E5=AE=87?= <738576547@qq.com>
Date: Fri, 9 Aug 2024 07:40:07 +0800
Subject: [PATCH 1/2] =?UTF-8?q?=E6=81=A2=E5=A4=8Dsymbol-search?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/main/resources/META-INF/plugin.xml | 3 +++
 1 file changed, 3 insertions(+)

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 @@
             <add-to-group group-id="NewGroup" anchor="before" relative-to-action="SymfonyGroup" />
         </group>
 
+        <action id="Symfony2Symbols" class="fr.adrienbrault.idea.symfony2plugin.action.SymfonySymbolSearchAction" text="Symfony Symbol..." icon="SymfonyIcons.Symfony">
+            <add-to-group group-id="GoToTargetEx"/>
+        </action>
         <action icon="SymfonyIcons.TwigBlockOverwrite"
                 id="SymfonyTwigBlockOverwrite"
                 class="fr.adrienbrault.idea.symfony2plugin.templating.action.TwigBlockOverwriteGenerator"

From f4d225882a677977428e6e8d4342c42e1a93c2ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=91=E5=AE=87?= <738576547@qq.com>
Date: Fri, 9 Aug 2024 07:43:06 +0800
Subject: [PATCH 2/2] symbol-search restore

---
 .../action/SymfonySymbolSearchAction.java     | 285 ++++++++++++++++++
 1 file changed, 285 insertions(+)
 create mode 100644 src/main/java/fr/adrienbrault/idea/symfony2plugin/action/SymfonySymbolSearchAction.java

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 <daniel@espendiller.net>
+ */
+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<String, Set<VirtualFile>> templateMap;
+        private Map<String, Route> routes;
+        private Set<String> twigMacroSet;
+        private Map<String, LookupElement> 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<String, Set<VirtualFile>> getTemplateMap() {
+            if(this.templateMap == null) {
+                this.templateMap = TwigUtil.getTemplateMap(this.project, true);
+            }
+
+            return this.templateMap;
+        }
+
+        private Map<String, Route> getRoutes() {
+            if(this.routes == null) {
+                this.routes = RouteHelper.getAllRoutes(project);
+            }
+
+            return this.routes;
+        }
+
+        private Set<String> getTwigMacroSet() {
+            if(this.twigMacroSet == null) {
+                this.twigMacroSet = TwigUtil.getTwigMacroSet(this.project);
+            }
+
+            return this.twigMacroSet;
+        }
+
+        private Map<String, LookupElement> getModelLookupElements() {
+
+            if(this.lookupElements == null) {
+                List<LookupElement> 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<? super String> 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<String, Route> 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<String, TwigExtension> 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<? super NavigationItem> 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<String> 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<String, TwigExtension> extensionMap : Arrays.asList(TwigExtensionParser.getFilters(project), TwigExtensionParser.getFunctions(project))) {
+                for(Map.Entry<String, TwigExtension> 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<FileType> {
+        @Override
+        public void elementChosen(ChooseByNamePopup popup, Object element) {
+            if(element instanceof NavigationItem) {
+                ((NavigationItem) element).navigate(true);
+            }
+        }
+    }
+}
+