Goto Defintion working for Job Names
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ConcourseLanguageServer> {
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Handler {
|
||||
Flux<Location> handle(Node refNode, TextDocument doc, YamlFileAST ast);
|
||||
}
|
||||
|
||||
private final ConcourseModel models;
|
||||
private final PipelineYmlSchema schema;
|
||||
private ASTTypeCache astTypes;
|
||||
private Map<YType, Handler> 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<ConcourseL
|
||||
Node refNode = ast.findNode(doc.toOffset(params.getPosition()));
|
||||
if (refNode!=null) {
|
||||
YType type = astTypes.getType(ast, refNode);
|
||||
if (schema.t_resource_name==type) {
|
||||
String name = NodeUtil.asScalar(refNode);
|
||||
return Flux.fromStream(models.getResourceDefinitionNodes(ast, name))
|
||||
.map((node) -> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, YamlFileAST> asts = new StaleFallbackCache<>();
|
||||
|
||||
@@ -150,9 +141,8 @@ public class ConcourseModel {
|
||||
|
||||
private Multiset<String> 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<Node> getResourceDefinitionNodes(YamlFileAST ast, String name) {
|
||||
return RESOURCE_NAMES_PATH.prepend(YamlPathSegment.anyChild())
|
||||
.traverseAmbiguously(ast)
|
||||
.filter(node -> name.equals(NodeUtil.asScalar(node)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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" +
|
||||
|
||||
Reference in New Issue
Block a user