diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentSymbolHandler.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentSymbolHandler.java new file mode 100644 index 000000000..b46eadb30 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/DocumentSymbolHandler.java @@ -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.languageserver.util; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.DocumentSymbolParams; +import org.eclipse.lsp4j.SymbolInformation; + +import com.google.common.collect.ImmutableList; + +@FunctionalInterface +public interface DocumentSymbolHandler { + + DocumentSymbolHandler NO_SYMBOLS = (params) -> CompletableFuture.completedFuture(ImmutableList.of()); + + CompletableFuture> handle(DocumentSymbolParams params); + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index b827f173e..5e9ae3335 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -150,18 +150,23 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl if (hasQuickFixes()) { c.setCodeActionProvider(true); } - if (hasDefinitionHandler()) { c.setDefinitionProvider(true); } - if (hasReferencesHandler()) { c.setReferencesProvider(true); } + if (hasDocumentSymbolHandler()) { + c.setDocumentSymbolProvider(true); + } return c; } + private boolean hasDocumentSymbolHandler() { + return getTextDocumentService().hasDocumentSymbolHandler(); + } + private boolean hasReferencesHandler() { return getTextDocumentService().hasReferencesHandler(); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 6084c7153..d9b3fd727 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -79,6 +79,8 @@ public class SimpleTextDocumentService implements TextDocumentService { private DefinitionHandler definitionHandler; private ReferencesHandler referencesHandler; + private DocumentSymbolHandler documentSymbolHandler; + public SimpleTextDocumentService(SimpleLanguageServer server) { this.server = server; } @@ -88,6 +90,11 @@ public class SimpleTextDocumentService implements TextDocumentService { this.hoverHandler = h; } + public synchronized void onDocumentSymbol(DocumentSymbolHandler h) { + Assert.isNull("A DocumentSymbolHandler is already set, multiple handlers not supported yet", documentSymbolHandler); + this.documentSymbolHandler = h; + } + public synchronized void onCompletion(CompletionHandler h) { Assert.isNull("A completion handler is already set, multiple handlers not supported yet", completionHandler); this.completionHandler = h; @@ -273,6 +280,9 @@ public class SimpleTextDocumentService implements TextDocumentService { @Override public CompletableFuture> documentSymbol(DocumentSymbolParams params) { + if (documentSymbolHandler!=null) { + return documentSymbolHandler.handle(params); + } return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -360,4 +370,8 @@ public class SimpleTextDocumentService implements TextDocumentService { return this.referencesHandler!=null; } + public boolean hasDocumentSymbolHandler() { + return this.documentSymbolHandler!=null; + } + } diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java index 4ace44457..0b6ab20fe 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java @@ -53,7 +53,7 @@ public class ASTTypeCache implements ITypeCollector { } @Override - public void endCollecting(YamlFileAST ast) { + public synchronized void endCollecting(YamlFileAST ast) { Assert.isLegal(currentAst==ast); String uri = ast.getDocument().getUri(); typeIndex.put(uri, currentTypes.build()); @@ -68,7 +68,7 @@ public class ASTTypeCache implements ITypeCollector { } } - public YType getType(YamlFileAST ast, Node node) { + public synchronized YType getType(YamlFileAST ast, Node node) { ImmutableMap types = typeIndex.get(ast.getDocument().getUri()); if (types!=null) { return types.get(node); @@ -84,5 +84,9 @@ public class ASTTypeCache implements ITypeCollector { this.interestingTypes.add(type); } + public synchronized ImmutableMap getNodes(String uri) { + return typeIndex.get(uri); + } + } diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDocumentSymbolHandler.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDocumentSymbolHandler.java new file mode 100644 index 000000000..e43150b72 --- /dev/null +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDocumentSymbolHandler.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.Collection; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.DocumentSymbolParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.util.Assert; +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.schema.YType; +import org.yaml.snakeyaml.nodes.Node; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableSet; + +/** + * Finds symbols in a concourse document. This relies on type information cached + * during reconcile and stored in the {@link ConcourseModel}. Therefore, + * this handler only works if invoked after a reconcile. + * + * @author Kris De Volder + */ +public class ConcourseDocumentSymbolHandler implements DocumentSymbolHandler { + + private ASTTypeCache astTypeCache; + private Set definitionTypes; + private SimpleTextDocumentService documents; + + public ConcourseDocumentSymbolHandler(SimpleTextDocumentService documents, ASTTypeCache astTypeCache, Collection definitionTypes) { + Assert.isTrue(!definitionTypes.isEmpty()); // If there's no interesting types then you are better of using DocumentSymbolHandler.NO_SYMBOLS + this.documents = documents; + this.astTypeCache = astTypeCache; + this.definitionTypes = ImmutableSet.copyOf(definitionTypes); + for (YType yType : definitionTypes) { + astTypeCache.addInterestingType(yType); + } + } + + @Override + public CompletableFuture> handle(DocumentSymbolParams params) { + Builder builder = ImmutableList.builder(); + TextDocument doc = documents.getDocument(params.getTextDocument().getUri()); + for (Entry entry : astTypeCache.getNodes(params.getTextDocument().getUri()).entrySet()) { + if (definitionTypes.contains(entry.getValue())) { + try { + builder.add(createSymbol(doc, entry.getKey(), entry.getValue())); + } catch (Exception e) { + Log.log(e); + } + } + } + return CompletableFuture.completedFuture(builder.build()); + } + + protected SymbolInformation createSymbol(TextDocument doc, Node node, YType type) throws BadLocationException { + DocumentRegion region = NodeUtil.region(doc, node); + Location location = new Location(doc.getUri(), doc.toRange(region.getStart(), region.getLength())); + SymbolInformation symbol = new SymbolInformation(); + symbol.setName(region.toString()); + symbol.setKind(symbolKind(type)); + symbol.setLocation(location); + symbol.setContainerName(containerName(type)); + return symbol; + } + + protected String containerName(YType type) { + return type.toString().replaceAll("(\\s)*[Nn]ame", ""); + } + + protected SymbolKind symbolKind(YType type) { + return SymbolKind.String; //TODO: try to return something different for different types of symbols + } + +} diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java index cdab51a79..136cc9c6c 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java @@ -10,13 +10,11 @@ *******************************************************************************/ package org.springframework.ide.vscode.concourse; +import java.util.List; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.CompletionList; -import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.DiagnosticSeverity; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.TextDocumentSyncKind; import org.springframework.ide.vscode.commons.languageserver.LanguageIds; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; @@ -24,8 +22,10 @@ import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEn import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler; 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.CollectionUtil; 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.completion.SchemaBasedYamlAssistContextProvider; @@ -34,6 +34,7 @@ import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlQuickfixes; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; +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.structure.YamlStructureProvider; @@ -54,8 +55,9 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { final VscodeCompletionEngineAdapter completionEngine; final VscodeHoverEngineAdapter hoverEngine; final YamlSchemaBasedReconcileEngine reconcileEngine; + final DocumentSymbolHandler symbolHandler; - SchemaSpecificPieces(YamlSchema schema) { + SchemaSpecificPieces(YamlSchema schema, List definitionTypes) { SchemaBasedYamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider); this.completionEngine = new VscodeCompletionEngineAdapter(ConcourseLanguageServer.this, yamlCompletionEngine); @@ -65,6 +67,10 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { this.reconcileEngine = new YamlSchemaBasedReconcileEngine(currentAsts, schema, yamlQuickfixes); reconcileEngine.setTypeCollector(models.getAstTypeCache()); + + this.symbolHandler = CollectionUtil.hasElements(definitionTypes) + ? new ConcourseDocumentSymbolHandler(documents, models.getAstTypeCache(), definitionTypes) + : DocumentSymbolHandler.NO_SYMBOLS; } public void setMaxCompletions(int max) { @@ -77,8 +83,8 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { PipelineYmlSchema pipelineSchema = new PipelineYmlSchema(models); this.yamlQuickfixes = new YamlQuickfixes(getQuickfixRegistry(), documents, structureProvider); - this.forPipelines = new SchemaSpecificPieces(pipelineSchema); - this.forTasks = new SchemaSpecificPieces(pipelineSchema.getTaskSchema()); + this.forPipelines = new SchemaSpecificPieces(pipelineSchema, pipelineSchema.getDefinitionTypes()); + this.forTasks = new SchemaSpecificPieces(pipelineSchema.getTaskSchema(), null); ConcourseDefinitionFinder definitionFinder = new ConcourseDefinitionFinder(this, models, pipelineSchema); // SimpleWorkspaceService workspace = getWorkspaceService(); @@ -132,6 +138,18 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { return SimpleTextDocumentService.NO_HOVER; }); documents.onDefinition(definitionFinder); + documents.onDocumentSymbol((params) -> { + DocumentSymbolHandler handler = DocumentSymbolHandler.NO_SYMBOLS; + TextDocument doc = documents.getDocument(params.getTextDocument().getUri()); + if (doc!=null) { + if (LanguageIds.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) { + handler = forPipelines.symbolHandler; + } else if (LanguageIds.CONCOURSE_TASK.equals(doc.getLanguageId())) { + handler = forTasks.symbolHandler; + } + } + return handler.handle(params); + }); } @Override diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index d2f226d63..dbe15c375 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -11,6 +11,8 @@ package org.springframework.ide.vscode.concourse; import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -47,6 +49,8 @@ import org.springframework.ide.vscode.concourse.ConcourseModel.StepModel; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; +import com.google.common.collect.ImmutableList; + import reactor.core.publisher.Flux; /** @@ -155,6 +159,8 @@ public class PipelineYmlSchema implements YamlSchema { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ); + private List definitionTypes = new ArrayList<>(); + public PipelineYmlSchema(ConcourseModel models) { this.models = models; TYPE_UTIL = f.TYPE_UTIL; @@ -366,6 +372,12 @@ public class PipelineYmlSchema implements YamlSchema { addProp(TOPLEVEL_TYPE, "resource_types", f.yseq(resourceType)); addProp(TOPLEVEL_TYPE, "groups", f.yseq(group)); + definitionTypes = ImmutableList.of( + jobNameDef, + resourceTypeNameDef, + resourceNameDef + ); + initializeDefaultResourceTypes(); } @@ -700,4 +712,8 @@ public class PipelineYmlSchema implements YamlSchema { } }; } + + public List getDefinitionTypes() { + return definitionTypes; + } }