Hierchical document symbols for concourse pipeline

This commit is contained in:
Kris De Volder
2019-02-12 13:44:27 -08:00
parent adf238f61d
commit bc86170ae0
8 changed files with 270 additions and 34 deletions

View File

@@ -144,4 +144,8 @@ public class NodeUtil {
return new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex());
}
public static DocumentRegion region(IDocument doc, NodeTuple tup) {
return new DocumentRegion(doc, tup.getKeyNode().getStartMark().getIndex(), tup.getValueNode().getEndMark().getIndex());
}
}

View File

@@ -310,4 +310,34 @@ public class YamlPath extends AbstractYamlTraversal {
return isEmpty();
}
public boolean startsWith(YamlPath prefix) {
if (this.size()>=prefix.size()) {
YamlPath chopped = this.dropLast(this.size() - prefix.size());
return chopped.equals(prefix);
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(segments);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
YamlPath other = (YamlPath) obj;
if (!Arrays.equals(segments, other.segments))
return false;
return true;
}
}

View File

@@ -231,7 +231,6 @@ public abstract class YamlPathSegment extends AbstractYamlTraversal {
protected char getTypeCode() {
return '&';
}
}
@Override

View File

@@ -0,0 +1,202 @@
/*******************************************************************************
* Copyright (c) 2019 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.reconcile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.languageserver.util.HierarchicalDocumentSymbolHandler;
import org.springframework.ide.vscode.commons.util.Assert;
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.YamlFileAST;
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.path.YamlPathSegment.YamlPathSegmentType;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
public class TypeBasedYamlHierarchicalSymbolHandler implements HierarchicalDocumentSymbolHandler, ITypeCollector {
private static final Logger log = LoggerFactory.getLogger(TypeBasedYamlHierarchicalSymbolHandler.class);
public static class HierarchicalDefType {
/**
* A yaml node of this type constitutes a definion. This should identify the 'whole' definition not just the
* part of the node that contains the name of the defined entity.
*/
public final YType defType;
/**
* A yaml optional path that points to the part of the node where the defined entity's name can be found.
*/
public final YamlPath namePath;
public final SymbolKind kind;
public final String detail;
public HierarchicalDefType(YType defType, YamlPath namePath, SymbolKind kind, String detail) {
super();
this.defType = defType;
this.namePath = namePath;
this.kind = kind;
this.detail = detail;
}
@Override
public String toString() {
return "HierarchicalDefType [defType=" + defType + ", namePath=" + namePath + "]";
}
public DocumentSymbol createSymbol(YamlFileAST currentAst, Node node, YType type, YamlPath path) {
try {
IDocument doc = currentAst.getDocument();
if (namePath!=null) {
Node nameNode = namePath.traverseNode(node);
return new DocumentSymbol(NodeUtil.asScalar(nameNode), kind,
NodeUtil.region(doc, node).asRange(),
NodeUtil.region(doc, nameNode).asRange(),
detail
);
} else {
//If there's no 'namePath' then we will assume the node we found is the value of a map entry...
//and use the map's key as the symbol's name
MappingNode map = NodeUtil.asMapping(path.dropLast().traverseToNode(currentAst));
if (map!=null) {
YamlPathSegment segment = path.getLastSegment();
if (segment.getType()==YamlPathSegmentType.VAL_AT_KEY) {
String key = segment.toPropString();
for (NodeTuple entry : map.getValue()) {
if (key.equals(NodeUtil.asScalar(entry.getKeyNode()))) {
return new DocumentSymbol(key, kind,
NodeUtil.region(doc, entry).asRange(),
NodeUtil.region(doc, entry.getKeyNode()).asRange(),
detail
);
}
}
}
}
}
} catch (Exception e) {
log.error("", e);
}
return null;
}
}
private static class Item {
YamlPath path;
DocumentSymbol symbol;
Item(YamlPath path, DocumentSymbol symbol) {
super();
this.path = path;
this.symbol = symbol;
}
public void addChild(DocumentSymbol sym) {
if (symbol.getChildren()==null) {
symbol.setChildren(new ArrayList<DocumentSymbol>());
}
symbol.getChildren().add(sym);
}
}
private TypeBasedYamlSymbolHandler baseHandler;
Map<YType, HierarchicalDefType> hierarchicalDefinitionTypes;
Map<String, List<DocumentSymbol>> outlineByUri = new HashMap<>();
private YamlFileAST currentAst;
private Stack<Item> stack = new Stack<>();
private ImmutableList.Builder<DocumentSymbol> rootSymbols;
public TypeBasedYamlHierarchicalSymbolHandler(TypeBasedYamlSymbolHandler baseHandler,
List<HierarchicalDefType> hierarchicalDefinitionTypes) {
this.baseHandler = baseHandler;
Builder<YType, HierarchicalDefType> builder = ImmutableMap.builder();
for (HierarchicalDefType hdt : hierarchicalDefinitionTypes) {
builder.put(hdt.defType, hdt);
}
this.hierarchicalDefinitionTypes = builder.build();
}
@Override
public List<? extends SymbolInformation> handle(DocumentSymbolParams params) {
return baseHandler.handle(params);
}
@Override
public List<? extends DocumentSymbol> handleHierarchic(DocumentSymbolParams params) {
return outlineByUri.get(params.getTextDocument().getUri());
}
@Override
public void beginCollecting(YamlFileAST ast) {
Assert.isNull("Session already active", currentAst);
this.currentAst = ast;
this.rootSymbols = ImmutableList.builder();
this.stack = new Stack<>();
}
@Override
public void accept(Node node, YType type, YamlPath path) {
HierarchicalDefType def = hierarchicalDefinitionTypes.get(type);
if (def!=null) {
Item parent = findParent(path);
DocumentSymbol sym = def.createSymbol(currentAst, node, type, path);
if (parent!=null) {
parent.addChild(sym);
} else {
rootSymbols.add(sym);
}
stack.push(new Item(path, sym));
}
}
private Item findParent(YamlPath path) {
if (stack.isEmpty()) {
return null;
}
Item item = stack.peek();
while (!path.startsWith(item.path)) {
stack.pop();
if (stack.isEmpty()) {
return null;
}
item = stack.peek();
}
return item;
}
@Override
public void endCollecting(YamlFileAST ast) {
Assert.isLegal(this.currentAst == ast);
String uri = currentAst.getDocument().getUri();
this.outlineByUri.put(uri, rootSymbols.build());
this.rootSymbols = null;
this.currentAst = null;
this.stack = null;
}
}

View File

@@ -28,6 +28,8 @@ import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
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;

View File

@@ -13,10 +13,12 @@ package org.springframework.ide.vscode.concourse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ide.vscode.commons.languageserver.util.HierarchicalDocumentSymbolHandler;
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.LogRedirect;
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlHierarchicalSymbolHandler;
import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlSymbolHandler;
import org.springframework.ide.vscode.concourse.github.DefaultGithubInfoProvider;
import org.springframework.ide.vscode.concourse.github.GithubInfoProvider;
@@ -47,8 +49,9 @@ public class ConcourseLanguageServerBootApp {
return new ASTTypeCache();
}
@Bean TypeBasedYamlSymbolHandler documentSymbolHandler(SimpleTextDocumentService documents, ASTTypeCache astTypeCache, PipelineYmlSchema schema) {
return new TypeBasedYamlSymbolHandler(documents, astTypeCache, schema.getDefinitionTypes());
@Bean HierarchicalDocumentSymbolHandler documentSymbolHandler(SimpleTextDocumentService documents, ASTTypeCache astTypeCache, PipelineYmlSchema schema) {
TypeBasedYamlSymbolHandler baseHandler = new TypeBasedYamlSymbolHandler(documents, astTypeCache, schema.getDefinitionTypes());
return new TypeBasedYamlHierarchicalSymbolHandler(baseHandler, schema.getHierarchicalDefinitionTypes());
}
@Bean PipelineYmlSchema pipelineYmlSchema(ConcourseModel models, GithubInfoProvider github) {

View File

@@ -37,11 +37,11 @@ import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes;
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.reconcile.TypeBasedYamlHierarchicalSymbolHandler.HierarchicalDefType;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.snippet.SchemaBasedSnippetGenerator;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.springframework.ide.vscode.concourse.PipelineYmlSchema.HierarchicalDefType;
import org.springframework.ide.vscode.concourse.github.GithubInfoProvider;
import org.springframework.stereotype.Component;

View File

@@ -16,6 +16,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.SymbolKind;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.MimeTypes;
import org.springframework.ide.vscode.commons.util.PartialCollection;
@@ -32,6 +33,7 @@ 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.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaValueParsers;
import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlHierarchicalSymbolHandler.HierarchicalDefType;
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
@@ -40,6 +42,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractT
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanUnionType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YSeqType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
@@ -61,30 +64,6 @@ import com.google.common.collect.ImmutableList;
*/
public class PipelineYmlSchema implements YamlSchema {
public static class HierarchicalDefType {
/**
* A yaml node of this type constitutes a definion. This should identify the 'whole' definition not just the
* part of the node that contains the name of the defined entity.
*/
public final YType defType;
/**
* A yaml path that points to the part of the node where the defined entity's name can be found.
*/
public final YamlPath nameNode;
public HierarchicalDefType(YType defType, YamlPath nameNode) {
super();
this.defType = defType;
this.nameNode = nameNode;
}
@Override
public String toString() {
return "HierarchicalDefType [defType=" + defType + ", nameNode=" + nameNode + "]";
}
}
//TODO: the infos for composing this should probably be integrated somehow in the ResourceTypeRegistry so
// we only have a list of built-in resource types in a single place.
public static final YValueHint[] BUILT_IN_RESOURCE_TYPES = {
@@ -189,6 +168,7 @@ public class PipelineYmlSchema implements YamlSchema {
);
private List<YType> definitionTypes = new ArrayList<>();
private List<HierarchicalDefType> hierarchicDefinitions;
private GithubInfoProvider github;
@@ -436,10 +416,14 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(group, "resources", f.yseq(t_resource_name));
addProp(group, "jobs", f.yseq(t_job_name));
addProp(TOPLEVEL_TYPE, "resources", f.yseq(t_resource));
addProp(TOPLEVEL_TYPE, "jobs", f.yseq(job));
addProp(TOPLEVEL_TYPE, "resource_types", f.yseq(resourceType));
addProp(TOPLEVEL_TYPE, "groups", f.yseq(group).require(models::jobAssignmentIsComplete));
YSeqType t_resources = f.yseq(t_resource);
YSeqType t_jobs = f.yseq(job);
YSeqType t_resourceTypes = f.yseq(resourceType);
AbstractType t_groups = f.yseq(group).require(models::jobAssignmentIsComplete);
addProp(TOPLEVEL_TYPE, "resources", t_resources);
addProp(TOPLEVEL_TYPE, "jobs", t_jobs);
addProp(TOPLEVEL_TYPE, "resource_types", t_resourceTypes);
addProp(TOPLEVEL_TYPE, "groups", t_groups);
definitionTypes = ImmutableList.of(
jobNameDef,
@@ -447,6 +431,19 @@ public class PipelineYmlSchema implements YamlSchema {
t_resource_name_def,
t_group_name_def
);
hierarchicDefinitions = ImmutableList.of(
new HierarchicalDefType(t_resources, null, SymbolKind.File, "Resources"),
new HierarchicalDefType(t_resource, YamlPath.fromSimpleProperty("name"), SymbolKind.File, "Resource"),
new HierarchicalDefType(t_jobs, null, SymbolKind.Method, "Jobs"),
new HierarchicalDefType(job, YamlPath.fromSimpleProperty("name"), SymbolKind.Method, "Job"),
new HierarchicalDefType(t_resourceTypes, null, SymbolKind.Interface, "Resource Types"),
new HierarchicalDefType(resourceType, YamlPath.fromSimpleProperty("name"), SymbolKind.Interface, "Resource Type"),
new HierarchicalDefType(t_groups, null, SymbolKind.Package, "Groups"),
new HierarchicalDefType(group, YamlPath.fromSimpleProperty("name"), SymbolKind.Package, "Groups")
);
initializeDefaultResourceTypes();
}
@@ -833,8 +830,7 @@ public class PipelineYmlSchema implements YamlSchema {
}
public List<HierarchicalDefType> getHierarchicalDefinitionTypes() {
// TODO Auto-generated method stub
return null;
return hierarchicDefinitions;
}
}