diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java index 74fe9586a..4ace44457 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java @@ -80,7 +80,7 @@ public class ASTTypeCache implements ITypeCollector { * Declares a given YType as 'interesting'. This means that nodes of this type will be * added to the index. */ - public void addInterestingType(YAtomicType type) { + public void addInterestingType(YType type) { this.interestingTypes.add(type); } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java index 6fc95aa2e..a1183d939 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.concourse; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.eclipse.lsp4j.Location; @@ -17,9 +19,11 @@ import org.eclipse.lsp4j.TextDocumentPositionParams; import org.springframework.ide.vscode.commons.languageserver.definition.SimpleDefinitionFinder; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; +import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.yaml.snakeyaml.nodes.Node; @@ -27,16 +31,45 @@ import reactor.core.publisher.Flux; public class ConcourseDefinitionFinder extends SimpleDefinitionFinder { + @FunctionalInterface + private interface Handler { + Flux handle(Node refNode, TextDocument doc, YamlFileAST ast); + } + private final ConcourseModel models; - private final PipelineYmlSchema schema; private ASTTypeCache astTypes; + private Map findersByType = new HashMap<>(); public ConcourseDefinitionFinder(ConcourseLanguageServer server, ConcourseModel models, PipelineYmlSchema schema) { super(server); this.models = models; - this.schema = schema; this.astTypes = models.getAstTypeCache(); - astTypes.addInterestingType(schema.t_resource_name); + findByPath(schema.t_resource_name, ConcourseModel.RESOURCE_NAMES_PATH); + findByPath(schema.t_job_name, ConcourseModel.JOB_NAMES_PATH); + } + + /** + * Add a handler that finds the definitions for a target node within the same document + * by following a {@link YamlPath} to find candidate nodes. + * + * @param refType the type inferred by the reconciler for the target node. + * @param definitionsPath Path that points to all nodes within the same file corresponding + * to definitions of nodes of the given type. + */ + private void findByPath(YType refType, YamlPath definitionsPath) { + astTypes.addInterestingType(refType); + Handler handler = (Node refNode, TextDocument doc, YamlFileAST ast) -> { + String name = NodeUtil.asScalar(refNode); + if (name!=null) { + return Flux.fromStream(definitionsPath.traverseAmbiguously(ast)) + .filter((node) -> name.equals(NodeUtil.asScalar(node))) + .map((node) -> toLocation(doc, node)) + .filter(Optional::isPresent) + .map(Optional::get); + } + return Flux.empty(); + }; + findersByType.put(refType, handler); } @Override @@ -48,12 +81,11 @@ public class ConcourseDefinitionFinder extends SimpleDefinitionFinder toLocation(doc, node)) - .filter(Optional::isPresent) - .map(Optional::get); + if (type!=null) { + Handler handler = findersByType.get(type); + if (handler!=null) { + return handler.handle(refNode, doc, ast); + } } } } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index 108ab541d..0c43abe85 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -15,11 +15,7 @@ import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.v import java.util.Arrays; import java.util.Collection; -import java.util.List; -import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.languageserver.util.TextDocumentContentChange; @@ -32,7 +28,6 @@ import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; import org.springframework.ide.vscode.commons.yaml.path.ASTRootCursor; import org.springframework.ide.vscode.commons.yaml.path.NodeCursor; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; -import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory; import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.concourse.util.CollectorUtil; @@ -41,14 +36,10 @@ import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.nodes.Node; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.ImmutableMultiset.Builder; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multiset; -import reactor.core.publisher.Flux; - /** * ConcourseModels is responsible for extracting various bits of information * out of .yml documents and caching them for use by various tools (reconcile engine @@ -56,30 +47,30 @@ import reactor.core.publisher.Flux; */ public class ConcourseModel { - private static final YamlPath RESOURCE_NAMES_PATH = new YamlPath( - valueAt("resources"), + public static final YamlPath JOB_NAMES_PATH = new YamlPath( anyChild(), - valueAt("name") - ); - - private static final YamlPath JOB_NAMES_PATH = new YamlPath( valueAt("jobs"), anyChild(), valueAt("name") ); - private static final YamlPath RESOURCE_TYPE_NAMES_PATH = new YamlPath( - valueAt("resource_types"), - anyChild(), - valueAt("name") + public static final YamlPath RESOURCE_TYPE_NAMES_PATH = new YamlPath( + anyChild(), + valueAt("resource_types"), + anyChild(), + valueAt("name") ); - private static final YamlPath RESOURCES_PATH = new YamlPath( + public static final YamlPath RESOURCES_PATH = new YamlPath( anyChild(), // skip over the root node which contains multiple doces valueAt("resources"), anyChild() ); + public static final YamlPath RESOURCE_NAMES_PATH = RESOURCES_PATH.append( + valueAt("name") + ); + private final YamlParser parser; private final StaleFallbackCache asts = new StaleFallbackCache<>(); @@ -150,9 +141,8 @@ public class ConcourseModel { private Multiset getStringsFromAst(IDocument doc, YamlPath path) { return getFromAst(doc, (ast) -> { - Node root = ast.get(0); return path - .traverseAmbiguously(root) + .traverseAmbiguously(ast) .map(NodeUtil::asScalar) .filter((string) -> string!=null) .collect(CollectorUtil.toMultiset()); @@ -231,10 +221,4 @@ public class ConcourseModel { return astTypes; } - public Stream getResourceDefinitionNodes(YamlFileAST ast, String name) { - return RESOURCE_NAMES_PATH.prepend(YamlPathSegment.anyChild()) - .traverseAmbiguously(ast) - .filter(node -> name.equals(NodeUtil.asScalar(node))); - } - } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index 9c3542d5b..75d45bf0c 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -92,9 +92,11 @@ public class PipelineYmlSchema implements YamlSchema { .parseWith(ValueParsers.integerAtLeast(1)); public final YAtomicType t_resource_name; + public final YAtomicType t_job_name; private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry(); + public PipelineYmlSchema(ConcourseModel models) { TYPE_UTIL = f.TYPE_UTIL; @@ -119,7 +121,7 @@ public class PipelineYmlSchema implements YamlSchema { } ); - this.t_resource_name = f.yenum("Resource Name", + t_resource_name = f.yenum("Resource Name", (parseString, validValues) -> { return "The '"+parseString+"' resource does not exist. Existing resources: "+validValues; }, @@ -128,7 +130,7 @@ public class PipelineYmlSchema implements YamlSchema { } ); - YType jobName = f.yenum("Job Name", + t_job_name = f.yenum("Job Name", (parseString, validValues) -> { return "The '"+parseString+"' Job does not exist. Existing jobs: "+validValues; }, @@ -146,7 +148,7 @@ public class PipelineYmlSchema implements YamlSchema { addProp(getStep, "get", t_resource_name); addProp(getStep, "resource", t_string); addProp(getStep, "version", t_version); - addProp(getStep, "passed", f.yseq(jobName)); + addProp(getStep, "passed", f.yseq(t_job_name)); addProp(getStep, "params", f.contextAware("GetParams", (dc) -> resourceTypes.getInParamsType(getResourceType("get", models, dc)) )); @@ -154,7 +156,7 @@ public class PipelineYmlSchema implements YamlSchema { YBeanType putStep = f.ybean("PutStep"); addProp(putStep, "put", t_resource_name); - addProp(putStep, "resource", jobName); + addProp(putStep, "resource", t_job_name); addProp(putStep, "params", f.contextAware("PutParams", (dc) -> resourceTypes.getOutParamsType(getResourceType("put", models, dc)) )); @@ -226,7 +228,7 @@ public class PipelineYmlSchema implements YamlSchema { YBeanType group = f.ybean("Group"); addProp(group, "name", t_ne_string).isRequired(true); addProp(group, "resources", f.yseq(t_resource_name)); - addProp(group, "jobs", f.yseq(jobName)); + addProp(group, "jobs", f.yseq(t_job_name)); addProp(TOPLEVEL_TYPE, "resources", f.yseq(resource)); addProp(TOPLEVEL_TYPE, "jobs", f.yseq(job)); diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java index 17a5ee6ba..3de318f51 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java @@ -1315,6 +1315,29 @@ public class PipelineYamlEditorTest { ); } + @Test + public void gotoJobDefinition() throws Exception { + Editor editor = harness.newEditor( + "resources:\n" + + "- name: my-git\n" + + " type: git\n" + + "jobs:\n" + + "- name: prepare-stuff\n" + + " plan:\n" + + " - get: my-git\n" + + " - task: preparations\n" + + " file: my-git/ci/tasks/preparations.yml\n" + + "- name: do-stuff\n" + + " plan:\n" + + " - get: my-git\n" + + " passed:\n" + + " - prepare-stuff\n" + ); + editor.assertGotoDefinition(editor.positionOf("- prepare-stuff", "prepare-stuff"), + editor.rangeOf("- name: prepare-stuff", "prepare-stuff") + ); + } + @Test public void reconcileResourceTypeNames() throws Exception { String userDefinedResourceTypesSnippet = "resource_types:\n" +