From 55557bfbc0e0d794dd220ac1f6cc4e3a9f5e9bcf Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Thu, 17 Feb 2022 13:19:26 +0100 Subject: Goto definition --- .../findUsages/FunctionDefinitionFinder.java | 60 +++++++++++++++++++ .../schema/findUsages/FunctionUsageFinder.java | 40 ++++--------- .../schema/findUsages/SdFindUsagesHandler.java | 7 +-- .../intellij/schema/findUsages/UsageFinder.java | 43 ++++++++++++++ .../intellij/findUsages/FindDefinitionTest.java | 24 ++++++++ .../vespa/intellij/findUsages/FindUsagesTest.java | 37 ------------ .../ai/vespa/intellij/findUsages/UsagesTester.java | 67 ++++++++++++++++++++++ 7 files changed, 208 insertions(+), 70 deletions(-) create mode 100644 integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java create mode 100644 integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/UsageFinder.java create mode 100644 integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindDefinitionTest.java create mode 100644 integration/intellij/src/test/java/ai/vespa/intellij/findUsages/UsagesTester.java (limited to 'integration/intellij') diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java new file mode 100644 index 00000000000..7d8aafc8f8b --- /dev/null +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java @@ -0,0 +1,60 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.intellij.schema.findUsages; + +import ai.vespa.intellij.schema.model.RankProfile; +import ai.vespa.intellij.schema.model.Schema; +import ai.vespa.intellij.schema.psi.SdRankProfileDefinition; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.progress.ProgressIndicatorProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.Processor; + +import java.util.HashSet; +import java.util.Set; + +/** + * Finds the definition of a function. + * + * @author bratseth + */ +public class FunctionDefinitionFinder extends UsageFinder { + + private final PsiElement referringElement; + private final String functionNameToFind; + private final Set visited = new HashSet<>(); + + public FunctionDefinitionFinder(PsiElement referringElement, SearchScope scope, Processor processor) { + super(scope, processor); + this.referringElement = referringElement; + this.functionNameToFind = ReadAction.compute(() -> referringElement.getText()); + } + + public void findDefinition() { + SdRankProfileDefinition profileDefinition = ReadAction.compute(() -> PsiTreeUtil.getParentOfType(referringElement, SdRankProfileDefinition.class)); + if (profileDefinition == null) return; + Schema schema = ReadAction.compute(() -> resolveSchema(profileDefinition)); + RankProfile profile = ReadAction.compute(() -> schema.rankProfiles().get(profileDefinition.getName())); + findDefinitionAbove(profile); + } + + private void findDefinitionAbove(RankProfile profile) { + ProgressIndicatorProvider.checkCanceled(); + if ( ! visited.add(profile)) return; + ReadAction.compute(() -> findDefinitionIn(profile)); + for (var parent : ReadAction.compute(() -> profile.parents().values())) + findDefinitionAbove(parent); + } + + private boolean findDefinitionIn(RankProfile profile) { + for (var entry : profile.definedFunctions().entrySet()) { + // TODO: Resolve the right function in the list by parameter count + if (entry.getKey().equals(functionNameToFind) && entry.getValue().size() > 0) + processor().process(new UsageInfo(entry.getValue().get(0).definition())); + } + return true; + } + +} diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionUsageFinder.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionUsageFinder.java index 0900caf3ecd..15aa936cdaa 100644 --- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionUsageFinder.java +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionUsageFinder.java @@ -6,12 +6,10 @@ import ai.vespa.intellij.schema.model.RankProfile; import ai.vespa.intellij.schema.model.Schema; import ai.vespa.intellij.schema.psi.SdFunctionDefinition; import ai.vespa.intellij.schema.psi.SdRankProfileDefinition; -import ai.vespa.intellij.schema.utils.Path; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiTreeUtil; @@ -28,19 +26,16 @@ import java.util.Set; * * @author bratseth */ -public class FunctionUsageFinder { +public class FunctionUsageFinder extends UsageFinder { private final SdFunctionDefinition functionToFind; private final String functionNameToFind; - private final SearchScope scope; - private final Processor processor; private final Set visited = new HashSet<>(); public FunctionUsageFinder(SdFunctionDefinition functionToFind, SearchScope scope, Processor processor) { + super(scope, processor); this.functionToFind = functionToFind; this.functionNameToFind = ReadAction.compute(functionToFind::getName); - this.scope = scope; - this.processor = processor; } /** @@ -54,41 +49,28 @@ public class FunctionUsageFinder { * on that. */ public void findUsages() { - Schema schema = ReadAction.compute(this::resolveSchema); + Schema schema = ReadAction.compute(() -> resolveSchema(functionToFind)); var rankProfile = ReadAction.compute(() -> schema.rankProfiles() .get(PsiTreeUtil.getParentOfType(functionToFind, SdRankProfileDefinition.class).getName())); findUsagesBelow(rankProfile); } - private Schema resolveSchema() { - PsiFile file = functionToFind.getContainingFile(); - if (file.getVirtualFile().getPath().endsWith(".profile")) { - Path schemaFile = Path.fromString(file.getVirtualFile().getParent().getPath() + ".sd"); - return ReadAction.compute(() -> Schema.fromProjectFile(functionToFind.getProject(), schemaFile)); - } - else { // schema - return ReadAction.compute(() -> Schema.fromProjectFile(functionToFind.getProject(), Path.fromString(file.getVirtualFile().getPath()))); - } - } - - private void findUsagesBelow(RankProfile rankProfile) { + private void findUsagesBelow(RankProfile profile) { ProgressIndicatorProvider.checkCanceled(); - if (visited.contains(rankProfile)) return; - visited.add(rankProfile); - ReadAction.compute(() -> findUsagesIn(rankProfile)); - Collection children = ReadAction.compute(() -> rankProfile.children()); - for (var child : children) + if ( ! visited.add(profile)) return; + ReadAction.compute(() -> findUsagesIn(profile)); + for (var child : ReadAction.compute(() -> profile.children())) findUsagesBelow(child); } - private boolean findUsagesIn(RankProfile rankProfile) { - if ( ! scope.contains(rankProfile.definition().getContainingFile().getVirtualFile())) return false; - Collection> functions = ReadAction.compute(() -> rankProfile.definedFunctions().values()); + private boolean findUsagesIn(RankProfile profile) { + if ( ! scope().contains(profile.definition().getContainingFile().getVirtualFile())) return false; + Collection> functions = ReadAction.compute(() -> profile.definedFunctions().values()); for (var functionList : functions) { for (var function : functionList) { var matchingVisitor = new MatchingVisitor(functionNameToFind, functionToFind == function.definition(), - processor); + processor()); ReadAction.compute(() -> { function.definition().accept(matchingVisitor); return null; } ); } } diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/SdFindUsagesHandler.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/SdFindUsagesHandler.java index 2a83cb8e681..cefa53addba 100644 --- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/SdFindUsagesHandler.java +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/SdFindUsagesHandler.java @@ -31,12 +31,10 @@ public class SdFindUsagesHandler extends FindUsagesHandler { if (elementToSearch instanceof SdFunctionDefinition) { new FunctionUsageFinder((SdFunctionDefinition) elementToSearch, options.searchScope, processor).findUsages(); } else { - boolean success = - ReferencesSearch.search(createSearchParameters(elementToSearch, options.searchScope, options)) - .forEach((PsiReference ref) -> processor.process(new UsageInfo(ref))); - if (!success) return false; + new FunctionDefinitionFinder(elementToSearch, options.searchScope, processor).findDefinition(); } } + if (options.isSearchForTextOccurrences && options.searchScope instanceof GlobalSearchScope) { if (options.fastTrack != null) options.fastTrack.searchCustom(consumer -> processUsagesInText(elementToSearch, @@ -45,6 +43,7 @@ public class SdFindUsagesHandler extends FindUsagesHandler { else return processUsagesInText(elementToSearch, processor, (GlobalSearchScope)options.searchScope); } + return true; } diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/UsageFinder.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/UsageFinder.java new file mode 100644 index 00000000000..46942c439ed --- /dev/null +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/UsageFinder.java @@ -0,0 +1,43 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.intellij.schema.findUsages; + +import ai.vespa.intellij.schema.model.Schema; +import ai.vespa.intellij.schema.utils.Path; +import com.intellij.openapi.application.ReadAction; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.SearchScope; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.Processor; + +/** + * Usage finder base class. + * + * @author bratseth + */ +public abstract class UsageFinder { + + private final SearchScope scope; + private final Processor processor; + + protected UsageFinder(SearchScope scope, Processor processor) { + this.scope = scope; + this.processor = processor; + } + + protected SearchScope scope() { return scope; } + protected Processor processor() { return processor; } + + /** Returns the schema logically containing this element. */ + protected Schema resolveSchema(PsiElement element) { + PsiFile file = element.getContainingFile(); + if (file.getVirtualFile().getPath().endsWith(".profile")) { + Path schemaFile = Path.fromString(file.getVirtualFile().getParent().getPath() + ".sd"); + return ReadAction.compute(() -> Schema.fromProjectFile(element.getProject(), schemaFile)); + } + else { // schema + return ReadAction.compute(() -> Schema.fromProjectFile(element.getProject(), Path.fromString(file.getVirtualFile().getPath()))); + } + } + +} diff --git a/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindDefinitionTest.java b/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindDefinitionTest.java new file mode 100644 index 00000000000..36fb842ce24 --- /dev/null +++ b/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindDefinitionTest.java @@ -0,0 +1,24 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.intellij.findUsages; + +import ai.vespa.intellij.PluginTestBase; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.Processor; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author bratseth + */ +public class FindDefinitionTest extends PluginTestBase { + + @Test + public void testFindUsagesInRankProfileModularity() { + useDir("src/test/applications/rankprofilemodularity"); + var tester = new UsagesTester("test.sd", getProject()); + UsageInfo usage = tester.findFunctionDefinition("outside_schema1", 93); + } + +} diff --git a/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindUsagesTest.java b/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindUsagesTest.java index 28c0b4f8029..141e5dd4b59 100644 --- a/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindUsagesTest.java +++ b/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/FindUsagesTest.java @@ -48,41 +48,4 @@ public class FindUsagesTest extends PluginTestBase { assertEquals(93 + 6, usage.getNavigationRange().getEndOffset()); } - private static class UsagesTester { - - final Project project; - final Schema schema; - final SdFindUsagesHandler handler; - final MockUsageProcessor usageProcessor = new MockUsageProcessor(); - - UsagesTester(String schemaName, Project project) { - this.project = project; - this.schema = Schema.fromProjectFile(project, Path.fromString(schemaName)); - this.handler = new SdFindUsagesHandler(schema.definition()); - } - - List assertFunctionUsages(String explanation, int expectedUsages, String profileName, String functionName) { - var function = schema.rankProfiles().get(profileName).definedFunctions().get(functionName).get(0).definition(); - var options = new FindUsagesOptions(project); - usageProcessor.usages.clear(); - options.isUsages = true; - handler.processElementUsages(function, usageProcessor, options); - assertEquals(explanation, expectedUsages, usageProcessor.usages.size()); - return usageProcessor.usages; - } - - } - - private static class MockUsageProcessor implements Processor { - - List usages = new ArrayList<>(); - - @Override - public boolean process(UsageInfo usageInfo) { - usages.add(usageInfo); - return true; - } - - } - } diff --git a/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/UsagesTester.java b/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/UsagesTester.java new file mode 100644 index 00000000000..f48ef7f107a --- /dev/null +++ b/integration/intellij/src/test/java/ai/vespa/intellij/findUsages/UsagesTester.java @@ -0,0 +1,67 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.intellij.findUsages; + +import ai.vespa.intellij.schema.findUsages.SdFindUsagesHandler; +import ai.vespa.intellij.schema.model.Schema; +import ai.vespa.intellij.schema.utils.Path; +import com.intellij.find.findUsages.FindUsagesOptions; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.Processor; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +class UsagesTester { + + final Project project; + final Schema schema; + final SdFindUsagesHandler handler; + final MockUsageProcessor usageProcessor = new MockUsageProcessor(); + + UsagesTester(String schemaName, Project project) { + this.project = project; + this.schema = Schema.fromProjectFile(project, Path.fromString(schemaName)); + this.handler = new SdFindUsagesHandler(schema.definition()); + } + + List assertFunctionUsages(String explanation, int expectedUsages, String profileName, String functionName) { + var function = schema.rankProfiles().get(profileName).definedFunctions().get(functionName).get(0).definition(); + findUsages(function); + assertEquals(explanation, expectedUsages, usageProcessor.usages.size()); + return usageProcessor.usages; + } + + /** Finds the function referred at the given character offset in the given profile. */ + UsageInfo findFunctionDefinition(String profileName, int offset) { + PsiElement referringElement = schema.rankProfiles().get(profileName).definition().findElementAt(offset); + findUsages(referringElement); + assertEquals("Expected to find definition of " + referringElement.getText(), + 1, usageProcessor.usages.size()); + return usageProcessor.usages.get(0); + } + + private void findUsages(PsiElement element) { + var options = new FindUsagesOptions(project); + usageProcessor.usages.clear(); + options.isUsages = true; + handler.processElementUsages(element, usageProcessor, options); + } + + private static class MockUsageProcessor implements Processor { + + List usages = new ArrayList<>(); + + @Override + public boolean process(UsageInfo usageInfo) { + usages.add(usageInfo); + return true; + } + + } + +} -- cgit v1.2.3