Goto defintion for releases

This commit is contained in:
Kris De Volder
2017-07-19 15:33:02 -07:00
parent 9aa5ed36bb
commit 47dcb094c5
13 changed files with 317 additions and 60 deletions

View File

@@ -0,0 +1,127 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.bosh;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.Location;
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.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.Node;
import reactor.core.publisher.Flux;
public class BoshDefintionFinder extends SimpleDefinitionFinder<BoshLanguageServer> {
//TODO: lots of common code between BoshDefintionFinder and ConcourseDefinitionFinder.
// should be possible to pull up into a common super class.
private final YamlAstCache asts;
private final ASTTypeCache astTypes;
private final BoshDeploymentManifestSchema schema;
private Map<YType, Handler> handlers = new HashMap<>();
@FunctionalInterface
private interface Handler {
Flux<Location> handle(Node refNode, TextDocument doc, YamlFileAST ast);
}
public BoshDefintionFinder(BoshLanguageServer server, BoshDeploymentManifestSchema schema, YamlAstCache asts, ASTTypeCache astTypes) {
super(server);
this.schema = schema;
this.asts = asts;
this.astTypes = astTypes;
for (Pair<YType, YType> defAndRef : schema.getDefAndRefTypes()) {
YType def = defAndRef.getLeft();
if (def!=null) {
YType ref = defAndRef.getRight();
if (ref!=null) {
findByType(def, ref);
}
}
}
}
@Override
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
try {
TextDocument doc = server.getTextDocumentService().get(params);
if (doc!=null) {
YamlFileAST ast = asts.getSafeAst(doc, false);
if (ast!=null) {
Node refNode = ast.findNode(doc.toOffset(params.getPosition()));
if (refNode!=null) {
YType type = astTypes.getType(ast, refNode);
if (type!=null) {
Handler handler = handlers.get(type);
if (handler!=null) {
return handler.handle(refNode, doc, ast);
}
}
}
}
}
} catch (Exception e) {
Log.log(e);
}
return Flux.empty();
}
/**
* Add a handler that finds the definitions for a target node within the same document
* by retrieving nodes of a given type as candidates.
*/
protected void findByType(YType def, YType ref) {
astTypes.addInterestingType(def);
astTypes.addInterestingType(ref);
Handler handler = (Node refNode, TextDocument doc, YamlFileAST ast) -> {
String uri = doc.getUri();
if (uri!=null) {
String name = NodeUtil.asScalar(refNode);
if (name!=null) {
Collection<Node> candidates = astTypes.getNodes(uri, def);
return Flux.fromIterable(candidates)
.filter((node) -> name.equals(NodeUtil.asScalar(node)))
.map((node) -> toLocation(doc, node))
.filter(Optional::isPresent)
.map(Optional::get);
}
}
return Flux.empty();
};
handlers.put(ref, handler);
}
protected Optional<Location> toLocation(TextDocument doc, Node node) {
int start = node.getStartMark().getIndex();
int end = node.getEndMark().getIndex();
try {
return Optional.of(new Location(doc.getUri(), doc.toRange(start, end-start)));
} catch (BadLocationException e) {
Log.log(e);
return Optional.empty();
}
}
}

View File

@@ -11,13 +11,16 @@
package org.springframework.ide.vscode.bosh;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.ide.vscode.bosh.models.CachingModelProvider;
import org.springframework.ide.vscode.bosh.models.CloudConfigModel;
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParsers;
@@ -81,6 +84,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
private YType t_var_name_def;
private final ASTTypeCache astTypes;
private DynamicModelProvider<CloudConfigModel> cloudConfigProvider;
private List<Pair<YType, YType>> defAndRefTypes;
public BoshDeploymentManifestSchema(ASTTypeCache astTypes, DynamicModelProvider<CloudConfigModel> cloudConfigProvider) {
this.astTypes = astTypes;
@@ -280,14 +284,28 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
public Collection<YType> getDefinitionTypes() {
if (definitionTypes==null) {
definitionTypes = ImmutableList.of(
t_instance_group_name_def,
t_stemcell_alias_def,
t_release_name_def,
t_var_name_def
);
definitionTypes = getDefAndRefTypes().stream()
.map(pair -> pair.getLeft())
.collect(CollectorUtil.toImmutableList());
}
return definitionTypes;
}
/**
* @return Pairs of types. Each pair contains a 'def' type and a 'ref' type. Nodes with the ref-type
* shall be interpreted as reference to a corresponding node with the 'def' type if the def and ref node
* contain the same scalar value.
*/
public Collection<Pair<YType, YType>> getDefAndRefTypes() {
if (defAndRefTypes==null) {
defAndRefTypes = ImmutableList.of(
Pair.of(t_instance_group_name_def, null),
Pair.of(t_stemcell_alias_def, null),
Pair.of(t_release_name_def, t_release_name_ref),
Pair.of(t_var_name_def, null)
);
}
return defAndRefTypes;
}
}

View File

@@ -23,7 +23,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocu
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine;
@@ -34,18 +34,18 @@ import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlSymbolHandler;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.Yaml;
public class BoshLanguageServer extends SimpleLanguageServer {
private Yaml yaml = new Yaml();
private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving
private final VscodeCompletionEngineAdapter completionEngine;
public BoshLanguageServer(DynamicModelProvider<CloudConfigModel> cloudConfigProvider) {
super("vscode-bosh");
YamlASTProvider parser = new YamlParser(yaml);
YamlAstCache asts = new YamlAstCache();
YamlASTProvider parser = asts.getAstProvider(false);
SimpleTextDocumentService documents = getTextDocumentService();
ASTTypeCache astTypeCache = new ASTTypeCache();
BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache, cloudConfigProvider);
@@ -67,6 +67,7 @@ public class BoshLanguageServer extends SimpleLanguageServer {
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
documents.onHover(hoverEngine ::getHover);
documents.onDefinition(new BoshDefintionFinder(this, schema, asts, astTypeCache));
}
private void validateOnDocumentChange(IReconcileEngine engine, TextDocument doc) {

View File

@@ -26,7 +26,6 @@ import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
public class BoshEditorTest {
LanguageServerHarness harness;
@@ -920,4 +919,34 @@ public class BoshEditorTest {
assertContains("Reading cloud config timed out", completion.getDocumentation());
}
@Test public void gotoReleaseDefinition() throws Exception {
Editor editor = harness.newEditor(
"name: foo\n" +
"instance_groups: \n" +
"- name: some-server\n" +
" jobs:\n" +
" - release: some-release\n" +
"- name: some-other-server\n" +
" jobs:\n" +
" - release: bogus-release\n" +
"releases:\n" +
"- name: some-release\n" +
" url: https://release-hub.info/some-release.tar.gz?version=99.3.2\n" +
" sha1: asddsfsd\n" +
"- name: other-release\n" +
" url: https://release-hub.info/other-release.tar.gz?version=99.3.2\n" +
" sha1: asddsfsd\n"
);
editor.assertGotoDefinition(editor.positionOf("some-release"),
editor.rangeOf(
"releases:\n" +
"- name: some-release\n"
,
"some-release"
)
);
}
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
@@ -19,6 +21,7 @@ import java.util.stream.Collector;
import java.util.stream.Collectors;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
@@ -64,4 +67,36 @@ public class CollectorUtil {
};
}
public static <T> Collector<T, ArrayList<T>, ImmutableList<T>> toImmutableList() {
return new Collector<T, ArrayList<T>, ImmutableList<T>>() {
@Override
public Supplier<ArrayList<T>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<ArrayList<T>, T> accumulator() {
return (a, e) -> a.add(e);
}
@Override
public BinaryOperator<ArrayList<T>> combiner() {
return (a1, a2) -> {
a1.addAll(a2);
return a1;
};
}
@Override
public Function<ArrayList<T>, ImmutableList<T>> finisher() {
return ImmutableList::copyOf;
}
@Override
public Set<Collector.Characteristics> characteristics() {
return ImmutableSet.of(Collector.Characteristics.UNORDERED);
}
};
}
}

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.ast;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.util.StaleFallbackCache;
import org.yaml.snakeyaml.Yaml;
public class YamlAstCache {
private final StaleFallbackCache<String, YamlFileAST> asts = new StaleFallbackCache<>();
private final YamlParser parser;
public YamlAstCache() {
Yaml yaml = new Yaml();
this.parser = new YamlParser(yaml);
}
public YamlASTProvider getAstProvider(boolean allowStaleAsts) {
return (IDocument doc) -> {
String uri = doc.getUri();
if (uri!=null) {
return asts.get(uri, doc.getVersion(), allowStaleAsts, () -> {
return parser.getAST(doc);
});
}
return null;
};
}
public YamlFileAST getSafeAst(IDocument doc) {
return getSafeAst(doc, true);
}
public YamlFileAST getAst(IDocument doc, boolean allowStaleAst) throws Exception {
return getAstProvider(allowStaleAst).getAST(doc);
}
public YamlFileAST getSafeAst(IDocument doc, boolean allowStaleAst) {
if (doc!=null) {
try {
return getAst(doc, allowStaleAst);
} catch (Exception e) {
//ignored
}
}
return null;
}
}

View File

@@ -164,5 +164,15 @@ public class ASTTypeCache implements ITypeCollector {
return null;
}
public Collection<Node> getNodes(String uri, YType type) {
NodeTypes nodeMap = getNodeTypes(uri);
if (nodeMap!=null) {
Collection<Node> nodes = nodeMap.getNodes(type);
if (nodes!=null) {
return nodes;
}
}
return ImmutableList.of();
}
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.concourse.util;
package org.springframework.ide.vscode.commons.yaml.util;
import java.util.HashMap;
import java.util.Map;

View File

@@ -712,7 +712,7 @@ public class Editor {
}
/**
* Determines the position of (the middle of) a snippet of text in the document.
* Determines the position of a snippet of text in the document.
*
* @param contextSnippet A larger snippet containing the actual snippet to look for.
* This larger snippet is used to narrow the section of the document
@@ -725,6 +725,10 @@ public class Editor {
return r==null?null:r.getStart();
}
public Position positionOf(String snippet) throws Exception {
return positionOf(snippet, snippet);
}
/**
* Determines the range of a snippet of text in the document.
*
@@ -795,4 +799,5 @@ public class Editor {
this.selectionStart = this.selectionEnd = doc.toOffset(position);
}
}

View File

@@ -21,6 +21,7 @@ import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Log;
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.YamlAstCache;
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.reconcile.ASTTypeCache;
@@ -36,14 +37,14 @@ public class ConcourseDefinitionFinder extends SimpleDefinitionFinder<ConcourseL
Flux<Location> handle(Node refNode, TextDocument doc, YamlFileAST ast);
}
private final ConcourseModel models;
private ASTTypeCache astTypes;
private final ASTTypeCache astTypes;
private Map<YType, Handler> handlers = new HashMap<>();
private final YamlAstCache asts;
public ConcourseDefinitionFinder(ConcourseLanguageServer server, ConcourseModel models, PipelineYmlSchema schema) {
super(server);
this.models = models;
this.astTypes = models.getAstTypeCache();
this.asts = models.getAstCache();
findByPath(schema.t_resource_name, ConcourseModel.RESOURCE_NAMES_PATH);
findByPath(schema.t_maybe_resource_name, ConcourseModel.RESOURCE_NAMES_PATH);
findByPath(schema.t_job_name, ConcourseModel.JOB_NAMES_PATH);
@@ -58,7 +59,7 @@ public class ConcourseDefinitionFinder extends SimpleDefinitionFinder<ConcourseL
* @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) {
protected void findByPath(YType refType, YamlPath definitionsPath) {
astTypes.addInterestingType(refType);
Handler handler = (Node refNode, TextDocument doc, YamlFileAST ast) -> {
String name = NodeUtil.asScalar(refNode);
@@ -79,7 +80,7 @@ public class ConcourseDefinitionFinder extends SimpleDefinitionFinder<ConcourseL
try {
TextDocument doc = server.getTextDocumentService().get(params);
if (doc!=null) {
YamlFileAST ast = models.getSafeAst(doc, false);
YamlFileAST ast = asts.getSafeAst(doc, false);
if (ast!=null) {
Node refNode = ast.findNode(doc.toOffset(params.getPosition()));
if (refNode!=null) {

View File

@@ -49,7 +49,7 @@ public class ConcourseLanguageServer extends SimpleLanguageServer {
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
SimpleTextDocumentService documents = getTextDocumentService();
ConcourseModel models = new ConcourseModel(this);
YamlASTProvider currentAsts = models.getAstProvider(false);
YamlASTProvider currentAsts = models.getAstCache().getAstProvider(false);
private SchemaSpecificPieces forPipelines;
private SchemaSpecificPieces forTasks;
private final YamlQuickfixes yamlQuickfixes;

View File

@@ -33,6 +33,7 @@ import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
import org.springframework.ide.vscode.commons.yaml.path.ASTRootCursor;
@@ -52,8 +53,8 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanUnio
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import org.springframework.ide.vscode.commons.yaml.util.StaleFallbackCache;
import org.springframework.ide.vscode.commons.yaml.util.Streams;
import org.springframework.ide.vscode.concourse.util.StaleFallbackCache;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.MappingNode;
@@ -112,7 +113,7 @@ public class ConcourseModel {
if (job!=null) {
//Only check if the job exists. Otherwise the extra checks will show 'redundant' errors (e.g.
// complaining that 'some-job' doesn't ineract with a resource (because the resource doesn't exist).
YamlFileAST root = this.getSafeAst(dc.getDocument());
YamlFileAST root = asts.getSafeAst(dc.getDocument());
if (root!=null) {
Node stepNode = path.dropLast().traverseToNode(root);
if (stepNode!=null) {
@@ -256,9 +257,7 @@ public class ConcourseModel {
valueAt("name")
);
private final YamlParser parser;
private final StaleFallbackCache<String, YamlFileAST> asts = new StaleFallbackCache<>();
private final YamlAstCache asts = new YamlAstCache();
private final ASTTypeCache astTypes = new ASTTypeCache();
private ResourceTypeRegistry resourceTypes;
@@ -268,8 +267,6 @@ public class ConcourseModel {
private YBeanUnionType stepType;
public ConcourseModel(SimpleLanguageServer languageServer) {
Yaml yaml = new Yaml();
this.parser = new YamlParser(yaml);
this.snippetBuilderFactory = languageServer::createSnippetBuilder;
}
@@ -368,7 +365,7 @@ public class ConcourseModel {
public Node getParentPropertyNode(String propName, DynamicSchemaContext dc) {
YamlPath path = dc.getPath();
if (path!=null) {
YamlFileAST root = this.getSafeAst(dc.getDocument());
YamlFileAST root = asts.getSafeAst(dc.getDocument());
if (root!=null) {
return path.dropLast().append(YamlPathSegment.valueAt(propName)).traverseToNode(root);
}
@@ -402,7 +399,7 @@ public class ConcourseModel {
if (doc!=null) {
String uri = doc.getUri();
if (uri!=null) {
YamlFileAST ast = getAst(doc, true);
YamlFileAST ast = asts.getAst(doc, true);
return astFunction.apply(ast);
}
}
@@ -414,36 +411,6 @@ public class ConcourseModel {
return null;
}
public YamlFileAST getSafeAst(IDocument doc) {
return getSafeAst(doc, true);
}
public YamlFileAST getAst(IDocument doc, boolean allowStaleAst) throws Exception {
return getAstProvider(allowStaleAst).getAST(doc);
}
public YamlASTProvider getAstProvider(boolean allowStaleAsts) {
return (IDocument doc) -> {
String uri = doc.getUri();
if (uri!=null) {
return asts.get(uri, doc.getVersion(), allowStaleAsts, () -> {
return parser.getAST(doc);
});
}
return null;
};
}
public YamlFileAST getSafeAst(IDocument doc, boolean allowStaleAst) {
if (doc!=null) {
try {
return getAst(doc, allowStaleAst);
} catch (Exception e) {
//ignored
}
}
return null;
}
public ASTTypeCache getAstTypeCache() {
return astTypes;
@@ -458,4 +425,8 @@ public class ConcourseModel {
this.stepType = step;
}
public YamlAstCache getAstCache() {
return this.asts;
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
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.path.YamlPathSegment;
@@ -134,6 +135,7 @@ public class PipelineYmlSchema implements YamlSchema {
private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry();
private final ConcourseModel models;
private final YamlAstCache asts;
public final YType t_semver = f.yatomic("Semver")
.parseWith(ValueParsers.NE_STRING); //TODO: use real semver parser.
@@ -155,9 +157,9 @@ public class PipelineYmlSchema implements YamlSchema {
private List<YType> definitionTypes = new ArrayList<>();
public PipelineYmlSchema(ConcourseModel models) {
this.models = models;
this.asts = models.getAstCache();
models.setResourceTypeRegistry(resourceTypes);
TYPE_UTIL = f.TYPE_UTIL;
@@ -659,7 +661,7 @@ public class PipelineYmlSchema implements YamlSchema {
private String getSiblingPropertyValue(DynamicSchemaContext dc, String propName) {
YamlPath path = dc.getPath();
if (path!=null) {
YamlFileAST root = models.getSafeAst(dc.getDocument());
YamlFileAST root = asts.getSafeAst(dc.getDocument());
if (root!=null) {
return NodeUtil.asScalar(path.append(YamlPathSegment.valueAt(propName)).traverseToNode(root));
}