11package fr .adrienbrault .idea .symfony2plugin .dic .container .util ;
22
33import com .intellij .openapi .project .Project ;
4+ import com .intellij .openapi .util .Key ;
5+ import com .intellij .openapi .util .ModificationTracker ;
6+ import com .intellij .openapi .vfs .VfsUtil ;
7+ import com .intellij .openapi .vfs .VirtualFile ;
48import com .intellij .patterns .PlatformPatterns ;
59import com .intellij .psi .PsiElement ;
610import com .intellij .psi .PsiFile ;
711import com .intellij .psi .search .GlobalSearchScope ;
8- import com .intellij .psi .util .PsiTreeUtil ;
12+ import com .intellij .psi .util .* ;
913import com .intellij .psi .xml .*;
1014import com .intellij .util .Consumer ;
1115import com .intellij .util .indexing .FileBasedIndex ;
3741import org .jetbrains .yaml .YAMLUtil ;
3842import org .jetbrains .yaml .psi .*;
3943
44+ import java .time .Instant ;
4045import java .util .*;
46+ import java .util .stream .Collectors ;
47+ import java .util .stream .Stream ;
4148
4249/**
4350 * @author Daniel Espendiller <daniel@espendiller.net>
@@ -55,6 +62,11 @@ public class ServiceContainerUtil {
5562 new MethodMatcher .CallToSignature ("\\ Symfony\\ Bundle\\ FrameworkBundle\\ Controller\\ AbstractController" , "get" ),
5663 };
5764
65+ private static ModificationTracker TIMED_MODIFICATION_TRACKER = new TimeSecondModificationTracker (60 );
66+
67+ private static final Key <CachedValue <Collection <String >>> SYMFONY_COMPILED_TIMED_SERVICE_WATCHER = new Key <>("SYMFONY_COMPILED_TIMED_SERVICE_WATCHER" );
68+ private static final Key <CachedValue <Collection <String >>> SYMFONY_COMPILED_SERVICE_WATCHER = new Key <>("SYMFONY_COMPILED_SERVICE_WATCHER" );
69+
5870 private static String [] LOWER_PRIORITY = new String [] {
5971 "debug" , "default" , "abstract" , "inner" , "chain" , "decorate" , "delegat"
6072 };
@@ -627,4 +639,100 @@ public static List<String> getSortedServiceId(@NotNull Project project, @NotNull
627639
628640 return myIds ;
629641 }
642+
643+ /**
644+ * Provide a modification on nearest second value
645+ */
646+ private static class TimeSecondModificationTracker implements ModificationTracker {
647+ private final int expiresAfter ;
648+
649+ public TimeSecondModificationTracker (int expiresAfter ) {
650+ this .expiresAfter = expiresAfter ;
651+ }
652+
653+ @ Override
654+ public long getModificationCount () {
655+ long unixTime = Instant .now ().getEpochSecond ();
656+ return roundNearest (unixTime );
657+ }
658+
659+ private long roundNearest (long n ) {
660+ // Smaller multiple
661+ long a = (n / this .expiresAfter ) * this .expiresAfter ;
662+
663+ // Larger multiple
664+ long b = a + this .expiresAfter ;
665+
666+ // Return of closest of two
667+ return (n - a > b - n ) ? b : a ;
668+ }
669+ }
670+
671+ /**
672+ * Find compiled and cache it until any psi change occur
673+ *
674+ * - "app/cache/dev/appDevDebugProjectContainer.xml"
675+ * - ...
676+ */
677+ public static Collection <String > getContainerFiles (@ NotNull Project project ) {
678+ return CachedValuesManager .getManager (project )
679+ .getCachedValue (
680+ project ,
681+ SYMFONY_COMPILED_SERVICE_WATCHER ,
682+ () -> CachedValueProvider .Result .create (getContainerFilesInner (project ), PsiModificationTracker .MODIFICATION_COUNT ),
683+ false
684+ );
685+ }
686+
687+ /**
688+ * Find possible compiled service file with seconds cache
689+ *
690+ * - "app/cache/dev/appDevDebugProjectContainer.xml"
691+ * - "var/cache/dev/appDevDebugProjectContainer.xml"
692+ * - "var/cache/dev/srcDevDebugProjectContainer.xml"
693+ * - "var/cache/dev/srcApp_KernelDevDebugContainer.xml"
694+ * - "var/cache/dev/App_KernelDevDebugContainer.xml" // Symfony => 4 + flex
695+ * - "app/cache/dev_392373729/appDevDebugProjectContainer.xml"
696+ */
697+ private static Collection <String > getContainerFilesInner (@ NotNull Project project ) {
698+ return CachedValuesManager .getManager (project ).getCachedValue (project , SYMFONY_COMPILED_TIMED_SERVICE_WATCHER , () -> {
699+ Set <String > files = new HashSet <>();
700+
701+ // several Symfony cache folder structures
702+ for (String root : new String [] {"var/cache" , "app/cache" }) {
703+ VirtualFile baseDir = project .getBaseDir ();
704+
705+ VirtualFile relativeFile = VfsUtil .findRelativeFile (root , baseDir );
706+ if (relativeFile == null ) {
707+ continue ;
708+ }
709+
710+ // find a dev folder eg: "dev_392373729" or just "dev"
711+ Set <VirtualFile > devFolders = Stream .of (relativeFile .getChildren ())
712+ .filter (virtualFile -> virtualFile .isDirectory () && virtualFile .getName ().toLowerCase ().startsWith ("dev" ))
713+ .collect (Collectors .toSet ());
714+
715+ for (VirtualFile devFolder : devFolders ) {
716+ Set <String > debugContainers = Stream .of (devFolder .getChildren ())
717+ .filter (virtualFile -> {
718+ if (!"xml" .equalsIgnoreCase (virtualFile .getExtension ())) {
719+ return false ;
720+ }
721+
722+ // Some examples: App_KernelDevDebugContainer, appDevDebugProjectContainer
723+ String filename = virtualFile .getName ().toLowerCase ();
724+ return filename .contains ("debugcontainer" )
725+ || (filename .contains ("debug" ) && filename .contains ("container" ))
726+ || (filename .contains ("kernel" ) && filename .contains ("container" ));
727+ })
728+ .map (virtualFile -> VfsUtil .getRelativePath (virtualFile , baseDir , '/' ))
729+ .collect (Collectors .toSet ());
730+
731+ files .addAll (debugContainers );
732+ }
733+ }
734+
735+ return CachedValueProvider .Result .create (files , TIMED_MODIFICATION_TRACKER );
736+ }, false );
737+ }
630738}
0 commit comments