Goto definition iin pipeline editor, working for resources
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* 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.languageserver.definition;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* {@link SimpleDefinitionFinder} provides a 'dummy' implementation of
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class SimpleDefinitionFinder<T extends SimpleLanguageServer> implements DefinitionHandler {
|
||||
|
||||
protected final T server;
|
||||
|
||||
public SimpleDefinitionFinder(T server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<Location>> handle(TextDocumentPositionParams position) {
|
||||
return findDefinitions(position)
|
||||
.collect(Collectors.toList())
|
||||
.toFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is meant to be overridden by subclass. This method provides a simple implementation
|
||||
* of 'goto definition' (which is not one you probably want to use in practice, but it
|
||||
* might be usful just to test whether things are wired up correctly to make the
|
||||
* 'goto definition' action in vscode work.
|
||||
* <p>
|
||||
* The implementation provided here simply looks for the first occurrence of the word
|
||||
* currently pointed at in the current document using String.indexOf.
|
||||
*/
|
||||
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
|
||||
try {
|
||||
TextDocument doc = server.getTextDocumentService().get(params);
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
int start = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(start))) {
|
||||
start--;
|
||||
}
|
||||
start = start+1;
|
||||
int end = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(end))) {
|
||||
end++;
|
||||
}
|
||||
String word = doc.textBetween(start, end);
|
||||
Log.log("Looking for definition of '"+word+"'");
|
||||
String text = doc.get();
|
||||
int def = text.indexOf(word);
|
||||
if (def>=0) {
|
||||
return Flux.just(
|
||||
new Location(params.getTextDocument().getUri(),
|
||||
doc.toRange(def, word.length())
|
||||
)
|
||||
)
|
||||
.doOnNext((Location loc) -> {
|
||||
Log.log("definition: "+loc);
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* 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.languageserver.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DefinitionHandler {
|
||||
CompletableFuture<List<Location>> handle(TextDocumentPositionParams position);
|
||||
}
|
||||
@@ -65,6 +65,13 @@ public class DocumentRegion implements CharSequence {
|
||||
this.end = limitRange(end, start, doc.getLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link DocumentRegion} covering the whole document.
|
||||
*/
|
||||
public DocumentRegion(IDocument doc) {
|
||||
this(doc, 0, doc.getLength());
|
||||
}
|
||||
|
||||
private int limitRange(int offset, int min, int max) {
|
||||
if (offset<min) {
|
||||
return min;
|
||||
|
||||
@@ -72,6 +72,8 @@ public class SimpleTextDocumentService implements TextDocumentService {
|
||||
private CompletionResolveHandler completionResolveHandler = null;
|
||||
private HoverHandler hoverHandler = null;
|
||||
|
||||
private DefinitionHandler definitionHandler;
|
||||
|
||||
public SimpleTextDocumentService(SimpleLanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
@@ -91,6 +93,11 @@ public class SimpleTextDocumentService implements TextDocumentService {
|
||||
this.completionResolveHandler = h;
|
||||
}
|
||||
|
||||
public synchronized void onDefinition(DefinitionHandler h) {
|
||||
Assert.isNull("A defintion handler is already set, multiple handlers not supported yet", definitionHandler);
|
||||
this.definitionHandler = h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all documents this service is tracking, generally these are the documents that have been opened / changed,
|
||||
* and not yet closed.
|
||||
@@ -221,9 +228,15 @@ public class SimpleTextDocumentService implements TextDocumentService {
|
||||
return Futures.of(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked"})
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams position) {
|
||||
return Futures.of(Collections.emptyList());
|
||||
DefinitionHandler h = this.definitionHandler;
|
||||
if (h!=null) {
|
||||
Object r = h.handle(position); //YUCK!
|
||||
return (CompletableFuture<List<? extends Location>>) r;
|
||||
}
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,4 +307,9 @@ public class SimpleTextDocumentService implements TextDocumentService {
|
||||
return Futures.of(Collections.emptyList());
|
||||
}
|
||||
|
||||
public void onDefinition(TextDocumentPositionParams h) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,23 +15,21 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Deprecated, this class is here to make porting old STS code easier. Code should
|
||||
* avoid using this as much as possible and replaces calls to this by using
|
||||
* {@link java.util.logging.Logger} directly
|
||||
* This class is here to make porting old STS code easier. Instead of using this,
|
||||
* consider using {@link java.util.logging.Logger} directly
|
||||
*/
|
||||
@Deprecated
|
||||
public class Log {
|
||||
|
||||
|
||||
final static Logger logger = LoggerFactory.getLogger(Log.class);
|
||||
|
||||
public static void log(Throwable e) {
|
||||
logger.error("Error", e);
|
||||
}
|
||||
|
||||
|
||||
public static void log(String message, Throwable t) {
|
||||
logger.error(message, t);
|
||||
}
|
||||
|
||||
|
||||
public static void log(String message) {
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef;
|
||||
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.ast.NodeRef.RootRef;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.SeqRef;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef;
|
||||
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.YamlPathSegment.YamlPathSegmentType;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
@@ -142,6 +142,14 @@ public class YamlPath {
|
||||
return traverseAmbiguously(startNode).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Stream<Node> traverseAmbiguously(YamlFileAST ast) {
|
||||
if (ast!=null) {
|
||||
return traverseAmbiguously(new ASTRootCursor(ast))
|
||||
.map((ASTCursor cursor) -> (Node)cursor.getNode());
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
public Stream<Node> traverseAmbiguously(Node startNode) {
|
||||
if (startNode!=null) {
|
||||
return traverseAmbiguously(new NodeCursor(startNode))
|
||||
@@ -293,5 +301,4 @@ public class YamlPath {
|
||||
return new YamlPath(common);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* 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.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
/**
|
||||
* A type collector can optionally be added to a {@link YamlASTReconciler}.
|
||||
* It is notified of the types the reconciler infers for
|
||||
* any AST nodes it visits during reconciling.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface ITypeCollector {
|
||||
void beginCollecting(YamlFileAST ast);
|
||||
void accept(Node node, YType type);
|
||||
void endCollecting(YamlFileAST ast);
|
||||
}
|
||||
@@ -21,11 +21,9 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.IntegerRange;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
@@ -54,34 +52,43 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
private final IProblemCollector problems;
|
||||
private final YamlSchema schema;
|
||||
private final YTypeUtil typeUtil;
|
||||
private final ITypeCollector typeCollector;
|
||||
|
||||
public SchemaBasedYamlASTReconciler(IProblemCollector problems, YamlSchema schema) {
|
||||
public SchemaBasedYamlASTReconciler(IProblemCollector problems, YamlSchema schema, ITypeCollector typeCollector) {
|
||||
this.problems = problems;
|
||||
this.schema = schema;
|
||||
this.typeCollector = typeCollector;
|
||||
this.typeUtil = schema.getTypeUtil();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(YamlFileAST ast) {
|
||||
List<Node> nodes = ast.getNodes();
|
||||
IntegerRange expectedDocs = schema.expectedNumberOfDocuments();
|
||||
if (!expectedDocs.isInRange(nodes.size())) {
|
||||
//wrong number of documents in the file. Figure out a good error message.
|
||||
if (nodes.isEmpty()) {
|
||||
problem(allOf(ast.getDocument()), "'"+schema.getName()+"' must have at least some Yaml content");
|
||||
} else if (expectedDocs.isTooLarge(nodes.size())) {
|
||||
int upperBound = expectedDocs.getUpperBound();
|
||||
Node extraNode = nodes.get(upperBound);
|
||||
problem(dashesAtStartOf(ast, extraNode), "'"+schema.getName()+"' should not have more than "+upperBound+" Yaml Documents");
|
||||
} else if (expectedDocs.isTooSmall(nodes.size())) {
|
||||
int lowerBound = expectedDocs.getLowerBound();
|
||||
problem(endOf(ast.getDocument()), "'"+schema.getName()+"' should have at least "+lowerBound+" Yaml Documents");
|
||||
if (typeCollector!=null) typeCollector.beginCollecting(ast);
|
||||
try {
|
||||
List<Node> nodes = ast.getNodes();
|
||||
IntegerRange expectedDocs = schema.expectedNumberOfDocuments();
|
||||
if (!expectedDocs.isInRange(nodes.size())) {
|
||||
//wrong number of documents in the file. Figure out a good error message.
|
||||
if (nodes.isEmpty()) {
|
||||
problem(allOf(ast.getDocument()), "'"+schema.getName()+"' must have at least some Yaml content");
|
||||
} else if (expectedDocs.isTooLarge(nodes.size())) {
|
||||
int upperBound = expectedDocs.getUpperBound();
|
||||
Node extraNode = nodes.get(upperBound);
|
||||
problem(dashesAtStartOf(ast, extraNode), "'"+schema.getName()+"' should not have more than "+upperBound+" Yaml Documents");
|
||||
} else if (expectedDocs.isTooSmall(nodes.size())) {
|
||||
int lowerBound = expectedDocs.getLowerBound();
|
||||
problem(endOf(ast.getDocument()), "'"+schema.getName()+"' should have at least "+lowerBound+" Yaml Documents");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodes!=null && !nodes.isEmpty()) {
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Node node = nodes.get(i);
|
||||
reconcile(ast.getDocument(), new YamlPath(YamlPathSegment.valueAt(i)), node, schema.getTopLevelType());
|
||||
if (nodes!=null && !nodes.isEmpty()) {
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Node node = nodes.get(i);
|
||||
reconcile(ast.getDocument(), new YamlPath(YamlPathSegment.valueAt(i)), node, schema.getTopLevelType());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (typeCollector!=null) {
|
||||
typeCollector.endCollecting(ast);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,8 +96,6 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
private DocumentRegion dashesAtStartOf(YamlFileAST ast, Node node) {
|
||||
try {
|
||||
int start = node.getStartMark().getIndex();
|
||||
int end = node.getEndMark().getIndex();
|
||||
String text = ast.getDocument().textBetween(start, end);
|
||||
DocumentRegion textBefore = new DocumentRegion(ast.getDocument(), 0, start)
|
||||
.trimEnd(Pattern.compile("\\s*"));
|
||||
DocumentRegion dashes = textBefore.subSequence(textBefore.getLength()-3);
|
||||
@@ -108,6 +113,9 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
if (type!=null) {
|
||||
DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(doc, path, node);
|
||||
type = typeUtil.inferMoreSpecificType(type, schemaContext);
|
||||
if (typeCollector!=null) {
|
||||
typeCollector.accept(node, type);
|
||||
}
|
||||
switch (getNodeId(node)) {
|
||||
case mapping:
|
||||
MappingNode map = (MappingNode) node;
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.yaml.snakeyaml.scanner.ScannerException;
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class YamlReconcileEngine implements IReconcileEngine {
|
||||
|
||||
|
||||
final static Logger logger = LoggerFactory.getLogger(YamlReconcileEngine.class);
|
||||
|
||||
protected final YamlASTProvider parser;
|
||||
|
||||
@@ -23,6 +23,12 @@ import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
|
||||
public final class YamlSchemaBasedReconcileEngine extends YamlReconcileEngine {
|
||||
private final YamlSchema schema;
|
||||
|
||||
/**
|
||||
* An optional type collector can be added. It will notified about all the types
|
||||
* the reconciler infers when reconciling an AST.
|
||||
*/
|
||||
private ITypeCollector typeCollector;
|
||||
|
||||
public YamlSchemaBasedReconcileEngine(YamlASTProvider parser, YamlSchema schema) {
|
||||
super(parser);
|
||||
this.schema = schema;
|
||||
@@ -35,6 +41,14 @@ public final class YamlSchemaBasedReconcileEngine extends YamlReconcileEngine {
|
||||
|
||||
@Override
|
||||
protected YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problems) {
|
||||
return new SchemaBasedYamlASTReconciler(problems, schema);
|
||||
return new SchemaBasedYamlASTReconciler(problems, schema, typeCollector);
|
||||
}
|
||||
|
||||
public ITypeCollector getTypeCollector() {
|
||||
return typeCollector;
|
||||
}
|
||||
|
||||
public void setTypeCollector(ITypeCollector typeCollector) {
|
||||
this.typeCollector = typeCollector;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,6 @@ public class YTypeFactory {
|
||||
private ValueParser getParser(DynamicSchemaContext dc) {
|
||||
return parser == null ? null : parser.withContext(dc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,9 +31,12 @@ import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.junit.Assert;
|
||||
|
||||
@@ -498,4 +501,43 @@ public class Editor {
|
||||
ignoredTypes.add(type.toString());
|
||||
}
|
||||
|
||||
public void assertGotoDefinition(Position pos, Range expectedTarget) throws Exception {
|
||||
TextDocumentIdentifier textDocumentId = document.getId();
|
||||
TextDocumentPositionParams params = new TextDocumentPositionParams(textDocumentId, textDocumentId.getUri(), pos);
|
||||
List<? extends Location> defs = harness.getDefinitions(params);
|
||||
assertEquals(1, defs.size());
|
||||
assertEquals(new Location(textDocumentId.getUri(), expectedTarget), defs.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the position of (the middle 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
|
||||
* where we look for the actual snippet. This is useful when the snippet
|
||||
* occurs multiple times in the document.
|
||||
* @param focusSnippet The snippet to look for
|
||||
*/
|
||||
public Position positionOf(String longSnippet, String focusSnippet) throws Exception {
|
||||
Range r = rangeOf(longSnippet, focusSnippet);
|
||||
return r==null?null:r.getStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the range 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
|
||||
* where we look for the actual snippet. This is useful when the snippet
|
||||
* occurs multiple times in the document.
|
||||
* @param focusSnippet The snippet to look for
|
||||
*/
|
||||
public Range rangeOf(String longSnippet, String focusSnippet) throws Exception {
|
||||
int relativeOffset = longSnippet.indexOf(focusSnippet);
|
||||
int contextStart = getRawText().indexOf(longSnippet);
|
||||
Assert.assertTrue("'"+longSnippet+"' not found in editor", contextStart>=0);
|
||||
int start = contextStart+relativeOffset;
|
||||
return new Range(document.toPosition(start), document.toPosition(start+focusSnippet.length()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
@@ -38,6 +39,7 @@ import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
@@ -375,4 +377,8 @@ public class LanguageServerHarness {
|
||||
assertEquals(expected, completion.getLabel());
|
||||
}
|
||||
|
||||
public List<? extends Location> getDefinitions(TextDocumentPositionParams params) throws Exception {
|
||||
return server.getTextDocumentService().definition(params).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@ import * as ChildProcess from 'child_process';
|
||||
import {LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo} from 'vscode-languageclient';
|
||||
import {TextDocument, OutputChannel} from 'vscode';
|
||||
|
||||
var DEBUG = false;
|
||||
const DEBUG_ARG = '-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y';
|
||||
|
||||
var log_output : OutputChannel = null;
|
||||
|
||||
function log(msg : string) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user