Goto definition iin pipeline editor, working for resources

This commit is contained in:
Kris De Volder
2017-01-24 12:05:15 -08:00
parent 46818d3462
commit 099c19bba3
20 changed files with 515 additions and 69 deletions

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* 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.concourse;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.reconcile.ITypeCollector;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableMap;
/**
* An implementation of {@link ITypeCollector} which keeps track of the
* types of 'interesting' nodes in the ast.
*
* @author Kris De Volder
*/
public class ASTTypeCache implements ITypeCollector {
/**
* Set upon commencing a reconciler session.
*/
private YamlFileAST currentAst = null;
/**
* Collects types for the current session.
*/
private ImmutableMap.Builder<Node, YType> currentTypes = null;
private final Set<YType> interestingTypes = new HashSet<>();
private final Map<String, ImmutableMap<Node, YType>> typeIndex = new HashMap<>();
@Override
public void beginCollecting(YamlFileAST ast) {
Assert.isNull("A session is already active. Concurrency isn't supported by ITypeCollector protocol", currentTypes);
this.currentAst = ast;
this.currentTypes = ImmutableMap.builder();
}
@Override
public void endCollecting(YamlFileAST ast) {
Assert.isLegal(currentAst==ast);
String uri = ast.getDocument().getUri();
typeIndex.put(uri, currentTypes.build());
this.currentAst = null;
this.currentTypes = null;
}
@Override
public void accept(Node node, YType type) {
if (interestingTypes.contains(type)) {
currentTypes.put(node, type);
}
}
public YType getType(YamlFileAST ast, Node node) {
ImmutableMap<Node, YType> types = typeIndex.get(ast.getDocument().getUri());
if (types!=null) {
return types.get(node);
}
return null;
}
/**
* Declares a given YType as 'interesting'. This means that nodes of this type will be
* added to the index.
*/
public void addInterestingType(YAtomicType type) {
this.interestingTypes.add(type);
}
}

View File

@@ -0,0 +1,76 @@
/*******************************************************************************
* 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.concourse;
import java.util.Optional;
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.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.Node;
import reactor.core.publisher.Flux;
public class ConcourseDefinitionFinder extends SimpleDefinitionFinder<ConcourseLanguageServer> {
private final ConcourseModel models;
private final PipelineYmlSchema schema;
private ASTTypeCache astTypes;
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);
}
@Override
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
try {
TextDocument doc = server.getTextDocumentService().get(params);
if (doc!=null) {
YamlFileAST ast = models.getSafeAst(doc, false);
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);
}
}
}
} catch (Exception e) {
return Flux.error(e);
}
return Flux.empty();
}
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

@@ -18,20 +18,16 @@ import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCo
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngine;
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
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.completion.SchemaBasedYamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine;
import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.Yaml;
public class ConcourseLanguageServer extends SimpleLanguageServer {
@@ -42,20 +38,22 @@ public class ConcourseLanguageServer extends SimpleLanguageServer {
YamlASTProvider currentAsts = models.getAstProvider(false);
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
YamlSchema schema = new PipelineYmlSchema(models);
PipelineYmlSchema schema = new PipelineYmlSchema(models);
YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);
YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider);
VscodeCompletionEngine completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine);
HoverInfoProvider infoProvider = new YamlHoverInfoProvider(currentAsts, structureProvider, contextProvider);
VscodeHoverEngine hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider);
IReconcileEngine engine = new YamlSchemaBasedReconcileEngine(currentAsts, schema);
YamlSchemaBasedReconcileEngine reconcileEngine = new YamlSchemaBasedReconcileEngine(currentAsts, schema);
ConcourseDefinitionFinder definitionFinder = new ConcourseDefinitionFinder(this, models, schema);
reconcileEngine.setTypeCollector(models.getAstTypeCache());
// SimpleWorkspaceService workspace = getWorkspaceService();
documents.onDidChangeContent(params -> {
TextDocument doc = params.getDocument();
validateWith(doc, engine);
validateWith(doc, reconcileEngine);
});
// workspace.onDidChangeConfiguraton(settings -> {
// System.out.println("Config changed: "+params);
// Integer val = settings.getInt("languageServerExample", "maxNumberOfProblems");
@@ -66,24 +64,27 @@ public class ConcourseLanguageServer extends SimpleLanguageServer {
// }
// }
// });
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
documents.onHover(hoverEngine ::getHover);
documents.onHover(hoverEngine::getHover);
documents.onDefinition(definitionFinder);
}
@Override
protected ServerCapabilities getServerCapabilities() {
ServerCapabilities c = new ServerCapabilities();
c.setTextDocumentSync(TextDocumentSyncKind.Incremental);
c.setHoverProvider(true);
CompletionOptions completionProvider = new CompletionOptions();
completionProvider.setResolveProvider(false);
c.setCompletionProvider(completionProvider);
c.setDefinitionProvider(true);
return c;
}
}

View File

@@ -14,6 +14,7 @@ import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.a
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.valueAt;
import java.util.function.Function;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.TextDocumentContentChange;
@@ -54,7 +55,7 @@ public class ConcourseModel {
valueAt("name")
);
private static final YamlPath RESOURCES_FROM_ROOT_PATH = new YamlPath(
private static final YamlPath RESOURCES_PATH = new YamlPath(
anyChild(), // skip over the root node which contains multiple doces
valueAt("resources"),
anyChild()
@@ -63,6 +64,9 @@ public class ConcourseModel {
private final YamlParser parser;
private final StaleFallbackCache<String, YamlFileAST> asts = new StaleFallbackCache<>();
private final ASTTypeCache astTypes = new ASTTypeCache();
public ConcourseModel(SimpleTextDocumentService documents) {
Yaml yaml = new Yaml();
this.parser = new YamlParser(yaml);
@@ -101,7 +105,7 @@ public class ConcourseModel {
*/
public String getResourceType(IDocument doc, String resourceName) {
return getFromAst(doc, (ast) -> {
Node resource = RESOURCES_FROM_ROOT_PATH.traverseAmbiguously(new ASTRootCursor(ast))
Node resource = RESOURCES_PATH.traverseAmbiguously(new ASTRootCursor(ast))
.map((cursor) -> ((NodeCursor)cursor).getNode())
.filter((resourceNode) -> resourceName.equals(NodeUtil.getScalarProperty(resourceNode, "name")))
.findFirst().orElse(null);
@@ -141,7 +145,7 @@ public class ConcourseModel {
if (doc!=null) {
String uri = doc.getUri();
if (uri!=null) {
YamlFileAST ast = getAst(doc);
YamlFileAST ast = getAst(doc, true);
return astFunction.apply(ast);
}
}
@@ -154,15 +158,11 @@ public class ConcourseModel {
}
public YamlFileAST getSafeAst(IDocument doc) {
try {
return getAst(doc);
} catch (Exception e) {
return null;
}
return getSafeAst(doc, true);
}
public YamlFileAST getAst(IDocument doc) throws Exception {
return getAstProvider(true).getAST(doc);
public YamlFileAST getAst(IDocument doc, boolean allowStaleAst) throws Exception {
return getAstProvider(allowStaleAst).getAST(doc);
}
public YamlASTProvider getAstProvider(boolean allowStaleAsts) {
@@ -177,4 +177,25 @@ public class ConcourseModel {
};
}
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;
}
public Stream<Node> getResourceDefinitionNodes(YamlFileAST ast, String name) {
return RESOURCE_NAMES_PATH.prepend(YamlPathSegment.anyChild())
.traverseAmbiguously(ast)
.filter(node -> name.equals(NodeUtil.asScalar(node)));
}
}

View File

@@ -57,6 +57,8 @@ public class PipelineYmlSchema implements YamlSchema {
public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer")
.parseWith(ValueParsers.integerAtLeast(1));
public final YAtomicType t_resource_name;
private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry();
public PipelineYmlSchema(ConcourseModel models) {
@@ -102,7 +104,7 @@ public class PipelineYmlSchema implements YamlSchema {
// The vagrant-cloud r
);
YType resourceName = f.yenum("Resource Name",
this.t_resource_name = f.yenum("Resource Name",
(parseString, validValues) -> {
return "The '"+parseString+"' resource does not exist. Existing resources: "+validValues;
},
@@ -126,7 +128,7 @@ public class PipelineYmlSchema implements YamlSchema {
jobNameDef.parseWith(ConcourseValueParsers.jobNameDef(models));
YBeanType getStep = f.ybean("GetStep");
addProp(getStep, "get", resourceName);
addProp(getStep, "get", t_resource_name);
addProp(getStep, "resource", t_string);
addProp(getStep, "version", t_version);
addProp(getStep, "passed", f.yseq(jobName));
@@ -136,7 +138,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(getStep, "trigger", t_boolean);
YBeanType putStep = f.ybean("PutStep");
addProp(putStep, "put", resourceName);
addProp(putStep, "put", t_resource_name);
addProp(putStep, "resource", jobName);
addProp(putStep, "params", f.contextAware("PutParams", (dc) ->
resourceTypes.getOutParamsType(getResourceType("put", models, dc))
@@ -151,7 +153,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(taskStep, "privileged", t_boolean);
addProp(taskStep, "params", t_params);
addProp(taskStep, "image", t_ne_string);
addProp(taskStep, "input_mapping", f.ymap(t_ne_string, resourceName));
addProp(taskStep, "input_mapping", f.ymap(t_ne_string, t_resource_name));
addProp(taskStep, "output_mapping", t_string_params);
YBeanType aggregateStep = f.ybean("AggregateStep");
@@ -208,7 +210,7 @@ public class PipelineYmlSchema implements YamlSchema {
YBeanType group = f.ybean("Group");
addProp(group, "name", t_ne_string).isRequired(true);
addProp(group, "resources", f.yseq(resourceName));
addProp(group, "resources", f.yseq(t_resource_name));
addProp(group, "jobs", f.yseq(jobName));
addProp(TOPLEVEL_TYPE, "resources", f.yseq(resource));

View File

@@ -1281,6 +1281,31 @@ public class PipelineYamlEditorTest {
editor.assertHoverContains("skip_download", "Skip `docker pull`");
}
@Test
public void gotoResourceDefinition() throws Exception {
Editor editor = harness.newEditor(
"resources:\n" +
"- name: my-git\n" +
" type: git\n" +
"- name: build-env\n" +
" type: docker-image\n" +
"jobs:\n" +
"- name: do-stuff\n" +
" plan:\n" +
" - get: my-git\n" +
" params:\n" +
" rootfs: true\n" +
" save: true\n" +
" - put: build-env\n" +
" build: my-git/docker\n"
);
editor.assertGotoDefinition(editor.positionOf("get: my-git", "my-git"),
editor.rangeOf("- name: my-git", "my-git")
);
}
//////////////////////////////////////////////////////////////////////////////
private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {