This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git


The following commit(s) were added to refs/heads/main by this push:
     new 2aa08f1  Improve toolbar artefact navigation for multi-project builds
2aa08f1 is described below

commit 2aa08f1c87e61eb56bf2e747db4ad86a14356fe9
Author: James Daugherty <[email protected]>
AuthorDate: Mon Jul 20 14:38:01 2026 -0500

    Improve toolbar artefact navigation for multi-project builds
    
    - Deduplicate navigate targets: collectFromPluginXmls ran once per 
dependency
      module, adding each descriptor-declared artefact N times; run it once and
      dedupe files.
    - Navigate to artefacts in downstream (dependent) modules, not just
      dependencies, so a shared upstream artefact can reach its consumers.
    - Fall back to name-only matching when no same-package artefact exists, for
      shared artefacts living in a different package than the referencing class.
---
 .../editor/toolbar/GrailsGoToArtefactActionBase.kt | 10 +++++--
 .../plugins/grails/util/GrailsArtifact.java        | 35 ++++++++++++++++++++--
 2 files changed, 39 insertions(+), 6 deletions(-)

diff --git 
a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
 
b/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
index f043215..092d4d0 100644
--- 
a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
+++ 
b/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
@@ -31,9 +31,13 @@ abstract class GrailsGoToArtefactActionBase(private val 
artefactType: GrailsArti
     return artefactData.artefactName.replaceFirstChar { it.uppercaseChar() } + 
artefactType.suffix
   }
 
-  override fun getNavigateTargets(artefactData: ArtefactData): 
MutableCollection<GrClassDefinition> = artefactType.getInstances(
-      artefactData.module, artefactData.packageName, artefactData.artefactName
-  )
+  override fun getNavigateTargets(artefactData: ArtefactData): 
Collection<GrClassDefinition> {
+    // Prefer an artefact in the same package as the current one, but fall 
back to matching by name
+    // alone: in multi-project builds a shared artefact (e.g. a domain in an 
upstream project) often
+    // lives in a different package than the controller/service that uses it.
+    val samePackage = artefactType.getInstances(artefactData.module, 
artefactData.packageName, artefactData.artefactName)
+    return samePackage.ifEmpty { 
artefactType.getInstances(artefactData.module, artefactData.artefactName) }
+  }
 
 
   @ActionText override fun getNavigateTitle(target: PsiClass): String =
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsArtifact.java 
b/src/org/jetbrains/plugins/grails/util/GrailsArtifact.java
index d666bba..06f2123 100644
--- a/src/org/jetbrains/plugins/grails/util/GrailsArtifact.java
+++ b/src/org/jetbrains/plugins/grails/util/GrailsArtifact.java
@@ -18,6 +18,7 @@ package org.jetbrains.plugins.grails.util;
 
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
 import com.intellij.openapi.module.ModuleUtilCore;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ModuleRootManager;
@@ -63,7 +64,10 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -310,7 +314,8 @@ public enum GrailsArtifact {
     ProjectFileIndex fileIndex = 
ProjectRootManager.getInstance(module.getProject()).getFileIndex();
 
     Set<Module> modules = new HashSet<>();
-    ModuleUtilCore.getDependencies(module, modules);
+    ModuleUtilCore.getDependencies(module, modules); // module + its 
(transitive) dependencies
+    collectDependentModules(module, modules);        // + modules that 
(transitively) depend on it
 
     for (Module m : modules) {
       if (GrailsFramework.isCommonPluginsModule(m)) {
@@ -329,13 +334,35 @@ public enum GrailsArtifact {
         if (directory != null) {
           calculateInstancesInDirectory(directory, res, fileIndex);
         }
-        collectFromPluginXmls(module, res);
       }
     }
 
+    // Grails plugin descriptors are resolved against the module's whole 
dependency+libraries scope,
+    // so this must run once for the module - not once per dependency, which 
would add every declared
+    // artefact as many times as there are modules in the dependency graph 
(duplicate navigate targets).
+    collectFromPluginXmls(module, res);
+
     return res;
   }
 
+  /**
+   * Adds every module that (transitively) depends on {@code module} to {@code 
result}. This lets a
+   * "Go To ..." action on an artefact in an upstream project (e.g. a shared 
plugin) resolve artefacts
+   * declared in downstream projects that depend on it - the reverse of {@link 
ModuleUtilCore#getDependencies}.
+   */
+  private static void collectDependentModules(@NotNull Module module, @NotNull 
Set<Module> result) {
+    ModuleManager moduleManager = 
ModuleManager.getInstance(module.getProject());
+    Deque<Module> queue = new ArrayDeque<>();
+    queue.add(module);
+    while (!queue.isEmpty()) {
+      for (Module dependent : 
moduleManager.getModuleDependentModules(queue.poll())) {
+        if (result.add(dependent)) {
+          queue.add(dependent);
+        }
+      }
+    }
+  }
+
   private void collectFromPluginXmls(Module module, MultiMap<String, 
VirtualFile> result) {
     Project project = module.getProject();
     JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
@@ -446,9 +473,11 @@ class GrailsArtifactCache {
       return Collections.singletonList(classDefinition);
     }
 
+    // The same artefact can be discovered through more than one path (e.g. a 
source directory and a
+    // grails-plugin.xml descriptor), so dedupe files to avoid duplicate 
navigate targets.
     List<GrClassDefinition> list = new ArrayList<>(size);
 
-    for (VirtualFile virtualFile : vfList) {
+    for (VirtualFile virtualFile : new LinkedHashSet<>(vfList)) {
       ContainerUtil.addIfNotNull(list, getClassDefinition(virtualFile));
     }
 

Reply via email to