From aee00fb487a25b20e48ad3dbe57cc76cbc65a4f9 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 11 Apr 2017 17:08:03 -0700 Subject: [PATCH] Add quickfix for missing properties in concourse editor --- .../boot/java/BootJavaLanguageServer.java | 2 +- .../test/PropertyReferenceFinderTest.java | 4 - .../metadata/hints/ValueHintHoverInfo.java | 7 +- .../ReplaceDeprecatedPropertyQuickfix.java | 9 +-- .../SpringPropertiesReconcileEngine.java | 18 ++--- .../reconcile/SpringPropertyProblem.java | 12 +-- .../ReplaceDeprecatedYamlQuickfix.java | 9 +-- .../ApplicationYamlASTReconciler.java | 9 +-- .../yaml/reconcile/SpringPropertyProblem.java | 9 +-- .../project/harness/ProjectsHarness.java | 33 ++++----- .../languageserver/quickfix/ProblemFixer.java | 37 ---------- .../languageserver/quickfix/Quickfix.java | 57 ++++++++++++++ ...ckfixContext.java => QuickfixHandler.java} | 20 ++--- .../quickfix/QuickfixRegistry.java | 63 ++++++++++++++++ .../quickfix/QuickfixResolveParams.java | 67 +++++++++++++++++ .../languageserver/quickfix/QuickfixType.java | 15 ++++ .../reconcile/ReconcileProblem.java | 5 ++ .../reconcile/ReconcileProblemImpl.java | 15 ++++ .../util/SimpleLanguageServer.java | 36 ++++++++- .../util/SimpleTextDocumentService.java | 48 +++++++++--- .../languageserver/util/TrackedDocument.java | 41 ++++++++++ .../ide/vscode/commons/util/Futures.java | 15 +++- .../commons/util/text/TextDocument.java | 5 ++ .../ide/vscode/commons/yaml/ast/NodeUtil.java | 6 ++ .../vscode/commons/yaml/path/YamlPath.java | 10 +++ .../commons/yaml/path/YamlPathSegment.java | 56 ++++++++++++++ .../yaml/reconcile/MissingPropertiesData.java | 54 ++++++++++++++ .../SchemaBasedYamlASTReconciler.java | 14 ++-- .../yaml/reconcile/YamlQuickfixes.java | 74 +++++++++++++++++++ .../YamlSchemaBasedReconcileEngine.java | 7 +- .../yaml/reconcile/YamlSchemaProblems.java | 41 ++++++++-- .../yaml/schema/ASTDynamicSchemaContext.java | 4 - .../commons/yaml/schema/YTypeFactory.java | 2 +- .../languageserver/testharness/Editor.java | 4 +- .../concourse/ConcourseLanguageServer.java | 8 +- .../concourse/ConcourseValueParsers.java | 17 ++++- .../commons-vscode/src/launch-util.ts | 15 ++-- .../vscode-concourse/.vscode/launch.json | 3 +- .../vscode-concourse/lib/Main.ts | 27 ++++++- .../vscode-concourse/package.json | 8 +- 40 files changed, 721 insertions(+), 165 deletions(-) delete mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/ProblemFixer.java create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/Quickfix.java rename headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/{QuickfixContext.java => QuickfixHandler.java} (51%) create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixResolveParams.java create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixType.java create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/TrackedDocument.java create mode 100644 headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/MissingPropertiesData.java create mode 100644 headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index ec90ff86a..bf8f00f2d 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -69,7 +69,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { documents.onHover(hoverInfoProvider); ReferencesHandler referencesHandler = new BootJavaReferencesHandler(this, javaProjectFinder); - documents.onRefeences(referencesHandler); + documents.onReferences(referencesHandler); } public void setMaxCompletionsNumber(int number) { diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java index 3cdcbc0ba..17be58ab4 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java @@ -20,14 +20,10 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.Location; -import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.boot.java.references.ValuePropertyReferencesProvider; import org.springframework.ide.vscode.project.harness.ProjectsHarness; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; - /** * @author Martin Lippert */ diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java index 2ce773fe9..6845724fc 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java @@ -11,12 +11,11 @@ package org.springframework.ide.vscode.boot.metadata.hints; -import java.util.List; +import static org.springframework.ide.vscode.commons.util.Renderables.bold; +import static org.springframework.ide.vscode.commons.util.Renderables.concat; +import static org.springframework.ide.vscode.commons.util.Renderables.paragraph; import org.springframework.ide.vscode.commons.util.Renderable; -import org.springframework.ide.vscode.commons.util.Renderables; - -import static org.springframework.ide.vscode.commons.util.Renderables.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/ReplaceDeprecatedPropertyQuickfix.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/ReplaceDeprecatedPropertyQuickfix.java index fe0a30dfd..993e907b2 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/ReplaceDeprecatedPropertyQuickfix.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/quickfix/ReplaceDeprecatedPropertyQuickfix.java @@ -13,13 +13,12 @@ package org.springframework.ide.vscode.boot.properties.quickfix; import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer; import org.springframework.ide.vscode.commons.util.Renderable; public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal { - public static ProblemFixer FIXER = (context, problem, proposals) -> { - throw new UnsupportedOperationException("Not yet implemented"); +// public static ProblemFixer FIXER = (context, problem, proposals) -> { +// throw new UnsupportedOperationException("Not yet implemented"); // PropertyInfo metadata = problem.getMetadata(); // if (metadata!=null) { // String replacement = metadata.getDeprecationReplacement(); @@ -28,7 +27,7 @@ public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal { // proposals.add(new ReplaceDeprecatedYamlQuickfix(context, problem)); // } // } - }; +// }; @Override public ICompletionProposal deemphasize() { @@ -57,7 +56,7 @@ public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal { // private final QuickfixContext context; // private final SpringPropertyProblem problem; -// +// // private LazyProposalApplier applier = new LazyProposalApplier() { // protected ProposalApplier create() throws Exception { // String newName = problem.getMetadata().getDeprecationReplacement(); diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertiesReconcileEngine.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertiesReconcileEngine.java index 7a19d1d46..3fe538e4b 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertiesReconcileEngine.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertiesReconcileEngine.java @@ -25,7 +25,6 @@ import org.springframework.ide.vscode.boot.metadata.types.Type; import org.springframework.ide.vscode.boot.metadata.types.TypeParser; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil; import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider; -import org.springframework.ide.vscode.boot.properties.quickfix.ReplaceDeprecatedPropertyQuickfix; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; @@ -72,26 +71,27 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine { this.fIndexProvider = provider; this.typeUtilProvider = typeUtilProvider; } - + + @Override public void reconcile(IDocument doc, IProblemCollector problemCollector) { FuzzyMap index = fIndexProvider.getIndex(doc); problemCollector.beginCollecting(); try { ParseResults results = parser.parse(doc.get()); DuplicateNameChecker duplicateNameChecker = new DuplicateNameChecker(problemCollector); - + results.syntaxErrors.forEach(syntaxError -> { problemCollector.accept(problem(PROP_SYNTAX_ERROR, syntaxError.getMessage(), syntaxError.getOffset(), syntaxError.getLength())); }); - + if (index==null || index.isEmpty()) { //don't report errors when index is empty, simply don't check (otherwise we will just reprot // all properties as errors, but this not really useful information since the cause is // some problem putting information about properties into the index. return; } - + results.ast.getNodes(KeyValuePair.class).forEach(pair -> { try { DocumentRegion propertyNameRegion = createRegion(doc, pair.getKey()); @@ -127,7 +127,7 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine { problemCollector.endCollecting(); } } - + protected SpringPropertyProblem problemDeprecated(DocumentRegion region, PropertyInfo property) { SpringPropertyProblem p = problem(PROP_DEPRECATED, TypeUtil.deprecatedPropertyMessage( @@ -139,7 +139,7 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine { ); p.setPropertyName(property.getId()); p.setMetadata(property); - p.setProblemFixer(ReplaceDeprecatedPropertyQuickfix.FIXER); +// p.setProblemFixer(ReplaceDeprecatedPropertyQuickfix.FIXER); return p; } @@ -167,10 +167,10 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine { length = doc.get(value.getOffset(), value.getLength()).length(); } catch (BadLocationException e) { // ignore - } + } return new DocumentRegion(doc, value.getOffset(), value.getOffset() + length); } - + private void reconcileType(DocumentRegion region, Type expectType, IProblemCollector problems) { TypeUtil typeUtil = typeUtilProvider.getTypeUtil(region.getDocument()); ValueParser parser = typeUtil.getValueParser(expectType); diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertyProblem.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertyProblem.java index 870305067..358483861 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertyProblem.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/SpringPropertyProblem.java @@ -12,7 +12,7 @@ package org.springframework.ide.vscode.boot.properties.reconcile; import org.springframework.ide.vscode.boot.metadata.PropertyInfo; -import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer; +//import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; @@ -22,7 +22,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion public class SpringPropertyProblem extends ReconcileProblemImpl { private PropertyInfo property = null; - private ProblemFixer fixer; +// private ProblemFixer fixer; private String propertyName; public SpringPropertyProblem(ProblemType type, String msg, int offset, int len) { @@ -39,14 +39,14 @@ public class SpringPropertyProblem extends ReconcileProblemImpl { } return new SpringPropertyProblem(type, msg, region.getStart(), region.getLength()); } - + public void setMetadata(PropertyInfo property) { this.property = property; } - public void setProblemFixer(ProblemFixer fixer) { - this.fixer = fixer; - } +// public void setProblemFixer(ProblemFixer fixer) { +// this.fixer = fixer; +// } public void setPropertyName(String name) { propertyName = name; diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/ReplaceDeprecatedYamlQuickfix.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/ReplaceDeprecatedYamlQuickfix.java index f44df7fd5..aaf7d1823 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/ReplaceDeprecatedYamlQuickfix.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/ReplaceDeprecatedYamlQuickfix.java @@ -13,13 +13,12 @@ package org.springframework.ide.vscode.boot.yaml.quickfix; import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer; import org.springframework.ide.vscode.commons.util.Renderable; public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal { - public static ProblemFixer FIXER = (context, problem, proposals) -> { - throw new UnsupportedOperationException("Not yet implemented"); +// public static ProblemFixer FIXER = (context, problem, proposals) -> { +// throw new UnsupportedOperationException("Not yet implemented"); // PropertyInfo metadata = problem.getMetadata(); // if (metadata!=null) { // String replacement = metadata.getDeprecationReplacement(); @@ -28,7 +27,7 @@ public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal { // proposals.add(new ReplaceDeprecatedYamlQuickfix(context, problem)); // } // } - }; +// }; @Override public ICompletionProposal deemphasize() { @@ -57,7 +56,7 @@ public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal { // private final QuickfixContext context; // private final SpringPropertyProblem problem; -// +// // private LazyProposalApplier applier = new LazyProposalApplier() { // protected ProposalApplier create() throws Exception { // String newName = problem.getMetadata().getDeprecationReplacement(); diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java index 9d815dca2..3e83117af 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java @@ -26,18 +26,17 @@ import org.springframework.ide.vscode.boot.metadata.PropertyInfo; import org.springframework.ide.vscode.boot.metadata.types.Type; import org.springframework.ide.vscode.boot.metadata.types.TypeParser; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil; -import org.springframework.ide.vscode.boot.metadata.types.TypedProperty; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode; -import org.springframework.ide.vscode.boot.yaml.quickfix.ReplaceDeprecatedYamlQuickfix; +import org.springframework.ide.vscode.boot.metadata.types.TypedProperty; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.ValueParser; 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.Kind; 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.reconcile.YamlASTReconciler; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; @@ -350,7 +349,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { SpringPropertyProblem problem = deprecatedPropertyProblem(property.getId(), null, keyNode, property.getDeprecationReplacement(), property.getDeprecationReason()); problem.setMetadata(property); - problem.setProblemFixer(ReplaceDeprecatedYamlQuickfix.FIXER); + //problem.setProblemFixer(ReplaceDeprecatedYamlQuickfix.FIXER); problems.accept(problem); } diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/SpringPropertyProblem.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/SpringPropertyProblem.java index d9ac6364c..128b67ef0 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/SpringPropertyProblem.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/SpringPropertyProblem.java @@ -12,14 +12,13 @@ package org.springframework.ide.vscode.boot.yaml.reconcile; import org.springframework.ide.vscode.boot.metadata.PropertyInfo; -import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; public class SpringPropertyProblem extends ReconcileProblemImpl { private PropertyInfo property = null; - private ProblemFixer fixer; +// private ProblemFixer fixer; private String propertyName; public SpringPropertyProblem(ProblemType type, String msg, int offset, int len) { @@ -34,9 +33,9 @@ public class SpringPropertyProblem extends ReconcileProblemImpl { this.property = property; } - public void setProblemFixer(ProblemFixer fixer) { - this.fixer = fixer; - } +// public void setProblemFixer(ProblemFixer fixer) { +// this.fixer = fixer; +// } public void setPropertyName(String name) { propertyName = name; diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java index bde1c3f47..9ebc9eb13 100644 --- a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java @@ -21,31 +21,30 @@ import org.springframework.ide.vscode.commons.maven.MavenBuilder; import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.maven.java.classpathfile.JavaProjectWithClasspathFile; -import org.springframework.ide.vscode.commons.util.ExceptionUtil; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; /** * Test projects harness - * + * * @author Alex Boyko * */ public class ProjectsHarness { - - public static final ProjectsHarness INSTANCE = new ProjectsHarness();; - + + public static final ProjectsHarness INSTANCE = new ProjectsHarness();; + public Cache cache = CacheBuilder.newBuilder().concurrencyLevel(1).build(); - + private enum ProjectType { MAVEN, CLASSPATH_TXT } - + private ProjectsHarness() { } - + public IJavaProject project(ProjectType type, String name) throws Exception { return cache.get(type + "/" + name, () -> { Path testProjectPath = getProjectPath(name); @@ -82,7 +81,7 @@ public class ProjectsHarness { return getProjectPathFromClasspath(name); // } } - + private Path getProjectPathFromClasspath(String name) throws URISyntaxException, IOException { URI resource = ProjectsHarness.class.getResource("/test-projects/" + name).toURI(); // if (resource.getScheme().equalsIgnoreCase("jar")) { @@ -91,7 +90,7 @@ public class ProjectsHarness { return Paths.get(resource); // } } - + // private Path getProjectPathFromJar(URI jar) throws IOException { // final String[] array = jar.toString().split("!"); // URI firstHalf = URI.create(array[0]); @@ -110,10 +109,10 @@ public class ProjectsHarness { // fs.close(); // } // } -// +// // private static void recursiveCopy(Path source, Path target, CopyOption... options) throws IOException { // Files.walkFileTree(source, new SimpleFileVisitor() { -// +// // Path destination = target; // // @Override @@ -135,13 +134,13 @@ public class ProjectsHarness { // destination = destination.getParent(); // return super.postVisitDirectory(dir, exc); // } -// +// // }); // } -// +// // private static void recursiveDelete(Path path) throws IOException { // Files.walkFileTree(path, new SimpleFileVisitor() { -// +// // @Override // public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // Files.delete(file); @@ -153,10 +152,10 @@ public class ProjectsHarness { // Files.delete(dir); // return super.postVisitDirectory(dir, exc); // } -// +// // }); // } - + public MavenJavaProject mavenProject(String name) throws Exception { return (MavenJavaProject) project(ProjectType.MAVEN, name); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/ProblemFixer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/ProblemFixer.java deleted file mode 100644 index e5cf4b2a4..000000000 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/ProblemFixer.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 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.quickfix; - -import java.util.List; - -import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; - -/** - * Represents a strategy for computing potential quickfixes for a given problem. - * - * @author Kris De Volder - */ -@FunctionalInterface -public interface ProblemFixer { - - /** - * Implementor can inspect the problem and quickfix context provided as parameters. - *

- * If the problem is deemed fixable, the strategy can contribute one or more fixes by - * adding them to the list of proposals (provided as third parameter). - */ - void contributeFixes( - QuickfixContext context, ReconcileProblem problem, - List proposals - ); - -} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/Quickfix.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/Quickfix.java new file mode 100644 index 000000000..21ff3971f --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/Quickfix.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * 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.quickfix; + +import org.eclipse.lsp4j.CodeActionContext; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Range; + +import com.google.common.collect.ImmutableList; + +public class Quickfix { + + public static class QuickfixData { + public final QuickfixType type; + public final T params; + public final String title; + public QuickfixData(QuickfixType type, T params, String title) { + super(); + this.type = type; + this.params = params; + this.title = title; + } + } + + private final Range range; + private final QuickfixData data; + + public Quickfix(Range range, QuickfixData data) { + super(); + this.range = range; + this.data = data; + } + + public Range getRange() { + return range; + } + + public Command getCodeAction() { + return new Command( + data.title, + "sts.quickfix", + ImmutableList.of(data.type.getId(), data.params) + ); + } + + public boolean appliesTo(Range range, CodeActionContext context) { + return range.equals(this.range); + } +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixContext.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java similarity index 51% rename from headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixContext.java rename to headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java index 6a42701cb..634df1fc1 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixContext.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015, 2016 Pivotal, Inc. + * 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 @@ -10,19 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.quickfix; -import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.eclipse.lsp4j.WorkspaceEdit; -/** - * Provides access to additional context info and objects that quickfixes might - * need in order to be able to apply themselves. - * - * @author Kris De Volder - */ -public interface QuickfixContext { -// IProject getProject(); -// IPreferenceStore getWorkspacePreferences(); -// IPreferenceStore getProjectPreferences(); -// IJavaProject getJavaProject(); -// UserInteractions getUI(); - IDocument getDocument(); +@FunctionalInterface +public interface QuickfixHandler { + WorkspaceEdit createEdits(Object params); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java new file mode 100644 index 000000000..fd277bba7 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixRegistry.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.quickfix; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.WorkspaceEdit; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.Assert; + +import reactor.core.publisher.Mono; + +/** + * Registry keeping track of quickfix types for a {@link SimpleLanguageServer}. + *

+ * Each type must be associated with a handler. The handler accepts a parameter object + * and computes workspace edits to be applied when the quickfix is executed. + *

+ * The parameters object must be convertible to json since it is sent over the + * wire to the client and later sent back when the quickfix is selected. + * + * @author Kris De Volder + */ +public class QuickfixRegistry { + + private Map registry = new HashMap<>(); + + public synchronized QuickfixType register(String typeName, QuickfixHandler handler) { + Assert.isLegal(!registry.containsKey(typeName), "Quickfix type already registered: '"+typeName); + registry.put(typeName, handler); + return new QuickfixType() { + + @Override + public WorkspaceEdit createEdits(Object params) { + return handler.createEdits(params); + } + + @Override + public String getId() { + return typeName; + } + }; + } + + public CompletableFuture handle(QuickfixResolveParams params) { + QuickfixHandler handler = registry.get(params.getType()); + return Mono.fromSupplier(() -> { + return handler.createEdits(params.getParams()); + }).toFuture(); + } + + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixResolveParams.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixResolveParams.java new file mode 100644 index 000000000..10ef8711f --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixResolveParams.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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.quickfix; + +public class QuickfixResolveParams { + + private String type; + private Object params; + + + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public Object getParams() { + return params; + } + public void setParams(Object params) { + this.params = params; + } + @Override + public String toString() { + return "QuickfixResolveParams [type=" + type + ", params=" + params + "]"; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((params == null) ? 0 : params.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + QuickfixResolveParams other = (QuickfixResolveParams) obj; + if (params == null) { + if (other.params != null) + return false; + } else if (!params.equals(other.params)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + + + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixType.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixType.java new file mode 100644 index 000000000..bebc7af19 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/quickfix/QuickfixType.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * 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.quickfix; + +public interface QuickfixType extends QuickfixHandler { + String getId(); +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblem.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblem.java index d831cdc6b..f84c58399 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblem.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblem.java @@ -11,6 +11,10 @@ package org.springframework.ide.vscode.commons.languageserver.reconcile; +import java.util.List; + +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; + /** * Minamal interface that objects representing a reconciler problem must * implement. @@ -23,4 +27,5 @@ public interface ReconcileProblem { int getOffset(); int getLength(); String getCode(); + List> getQuickfixes(); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblemImpl.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblemImpl.java index c7346c15d..76fa061ec 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblemImpl.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileProblemImpl.java @@ -11,6 +11,10 @@ package org.springframework.ide.vscode.commons.languageserver.reconcile; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; /** @@ -24,6 +28,7 @@ public class ReconcileProblemImpl implements ReconcileProblem { final private String msg; final private int offset; final private int len; + private List> fixes = new ArrayList<>(); public ReconcileProblemImpl(ProblemType type, String msg, int offset, int len) { super(); @@ -78,4 +83,14 @@ public class ReconcileProblemImpl implements ReconcileProblem { return c!='\n'&&c!='\r'; } + @Override + public List> getQuickfixes() { + return fixes; + } + + public ReconcileProblemImpl addQuickfix(QuickfixData command) { + fixes.add(command); + return this; + } + } 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 9687c1df3..48cefac09 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 @@ -8,7 +8,6 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.commons.languageserver.util; import java.nio.file.Path; @@ -25,18 +24,26 @@ import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.MessageType; +import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; import org.eclipse.lsp4j.services.LanguageServer; import org.springframework.ide.vscode.commons.languageserver.ProgressParams; import org.springframework.ide.vscode.commons.languageserver.ProgressService; import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix; +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixResolveParams; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; import org.springframework.ide.vscode.commons.util.BadLocationException; +import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; import reactor.core.publisher.Mono; @@ -72,11 +79,20 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl private CompletableFuture busyReconcile = CompletableFuture.completedFuture(null); + private QuickfixRegistry quickfixRegistry; + @Override public void connect(LanguageClient _client) { this.client = (STS4LanguageClient) _client; } + protected synchronized QuickfixRegistry getQuickfixRegistry() { + if (quickfixRegistry==null) { + quickfixRegistry = new QuickfixRegistry(); + } + return quickfixRegistry; + } + @Override public CompletableFuture initialize(InitializeParams params) { // LOG.info("Initializing"); @@ -166,9 +182,11 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl IProblemCollector problems = new IProblemCollector() { private List diagnostics = new ArrayList<>(); + private List quickfixes = new ArrayList<>(); @Override public void endCollecting() { + documents.setQuickfixes(doc, quickfixes); documents.publishDiagnostics(doc, diagnostics); } @@ -185,8 +203,15 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl Diagnostic d = new Diagnostic(); d.setCode(problem.getCode()); d.setMessage(problem.getMessage()); - d.setRange(doc.toRange(problem.getOffset(), problem.getLength())); + Range rng = doc.toRange(problem.getOffset(), problem.getLength()); + d.setRange(rng); d.setSeverity(severity); + List> fixes = problem.getQuickfixes(); + if (CollectionUtil.hasElements(fixes)) { + for (QuickfixData fix : fixes) { + quickfixes.add(new Quickfix<>(rng, fix)); + } + } diagnostics.add(d); } } catch (BadLocationException e) { @@ -235,4 +260,11 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl return progressService; } + @JsonRequest("sts/quickfix") + public CompletableFuture quickfixResolve(QuickfixResolveParams params) { + QuickfixRegistry quickfixes = getQuickfixRegistry(); + return quickfixes.handle(params); + } + + } 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 a6d0363e4..4a0bab55f 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 @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.commons.languageserver.util; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -21,6 +20,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.CodeLens; @@ -55,6 +55,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.TextDocumentService; import org.springframework.ide.vscode.commons.languageserver.LanguageIds; +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.ExceptionUtil; @@ -62,12 +63,14 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; +import reactor.core.publisher.Flux; + public class SimpleTextDocumentService implements TextDocumentService { private static final Logger LOG = Logger.getLogger(SimpleTextDocumentService.class.getName()); final private SimpleLanguageServer server; - private Map documents = new HashMap<>(); + private Map documents = new HashMap<>(); private ListenerList documentChangeListeners = new ListenerList<>(); private CompletionHandler completionHandler = null; private CompletionResolveHandler completionResolveHandler = null; @@ -100,7 +103,7 @@ public class SimpleTextDocumentService implements TextDocumentService { this.definitionHandler = h; } - public synchronized void onRefeences(ReferencesHandler h) { + public synchronized void onReferences(ReferencesHandler h) { Assert.isNull("A references handler is already set, multiple handlers not supported yet", referencesHandler); this.referencesHandler = h; } @@ -110,7 +113,9 @@ public class SimpleTextDocumentService implements TextDocumentService { * and not yet closed. */ public synchronized Collection getAll() { - return new ArrayList<>(documents.values()); + return documents.values().stream() + .map((td) -> td.getDocument()) + .collect(Collectors.toList()); } @Override @@ -151,7 +156,7 @@ public class SimpleTextDocumentService implements TextDocumentService { String languageId = params.getTextDocument().getLanguageId(); if (url!=null) { String text = params.getTextDocument().getText(); - TextDocument doc = createDocument(url, languageId); + TextDocument doc = createDocument(url, languageId).getDocument(); doc.setText(text); TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent() { @Override @@ -191,20 +196,20 @@ public class SimpleTextDocumentService implements TextDocumentService { documentChangeListeners.add(l); } - private synchronized TextDocument getDocument(String url) { - TextDocument doc = documents.get(url); + public synchronized TextDocument getDocument(String url) { + TrackedDocument doc = documents.get(url); if (doc==null) { LOG.warning("Trying to get document ["+url+"] but it did not exists. Creating it with language-id 'plaintext'"); doc = createDocument(url, LanguageIds.PLAINTEXT); } - return doc; + return doc.getDocument(); } - private synchronized TextDocument createDocument(String url, String languageId) { + private synchronized TrackedDocument createDocument(String url, String languageId) { if (documents.get(url)!=null) { LOG.warning("Creating document ["+url+"] but it already exists. Existing document discarded!"); } - TextDocument doc = new TextDocument(url, languageId); + TrackedDocument doc = new TrackedDocument(new TextDocument(url, languageId)); documents.put(url, doc); return doc; } @@ -273,7 +278,17 @@ public class SimpleTextDocumentService implements TextDocumentService { @Override public CompletableFuture> codeAction(CodeActionParams params) { - return CompletableFuture.completedFuture(Collections.emptyList()); + TrackedDocument doc = documents.get(params.getTextDocument().getUri()); + if (doc!=null) { + return Flux.fromIterable(doc.getQuickfixes()) + .filter((fix) -> fix.appliesTo(params.getRange(), params.getContext())) + .map(Quickfix::getCodeAction) + .collectList() + .toFuture() + .thenApply(l -> (List) l); + } else { + return CompletableFuture.completedFuture(ImmutableList.of()); + } } @Override @@ -320,12 +335,21 @@ public class SimpleTextDocumentService implements TextDocumentService { } } + public void setQuickfixes(TextDocument doc, List quickfixes) { + TrackedDocument td = documents.get(doc.getUri()); + if (td!=null) { + td.setQuickfixes(quickfixes); + } + } + public synchronized TextDocument get(TextDocumentPositionParams params) { - return documents.get(params.getTextDocument().getUri()); + TrackedDocument td = documents.get(params.getTextDocument().getUri()); + return td == null ? null : td.getDocument(); } @Override public CompletableFuture> documentHighlight(TextDocumentPositionParams position) { return CompletableFuture.completedFuture(Collections.emptyList()); } + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/TrackedDocument.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/TrackedDocument.java new file mode 100644 index 000000000..b37d726d3 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/TrackedDocument.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * 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 org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +import com.google.common.collect.ImmutableList; + +public class TrackedDocument { + + private final TextDocument doc; + private List quickfixes = ImmutableList.of(); + + public TrackedDocument(TextDocument doc) { + this.doc = doc; + } + + public TextDocument getDocument() { + return doc; + } + + public void setQuickfixes(List quickfixes) { + this.quickfixes = quickfixes; + } + + public List getQuickfixes() { + return quickfixes; + } + +} diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Futures.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Futures.java index d69a8731c..f7d77b1bb 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Futures.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Futures.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.commons.util; +import java.io.IOException; import java.util.concurrent.CompletableFuture; public class Futures { @@ -22,5 +23,17 @@ public class Futures { public static CompletableFuture of(T value) { return CompletableFuture.completedFuture(value); } - + + public static CompletableFuture error(String message) { + CompletableFuture f = new CompletableFuture(); + f.completeExceptionally(new IOException(message)); + return f; + } + + public static CompletableFuture error(Throwable e) { + CompletableFuture f = new CompletableFuture(); + f.completeExceptionally(e); + return f; + } + } diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java index fa1310fad..693bf27fb 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java @@ -252,8 +252,13 @@ public class TextDocument implements IDocument { } } + @Override public String getLanguageId() { return languageId; } + public Range toRange(IRegion region) throws BadLocationException { + return toRange(region.getOffset(), region.getLength()); + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java index ac7b8be72..7336c21e2 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java @@ -14,6 +14,8 @@ package org.springframework.ide.vscode.commons.yaml.ast; import java.util.Collections; import java.util.Set; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.util.text.IDocument; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeId; @@ -125,4 +127,8 @@ public class NodeUtil { return null; } + public static DocumentRegion region(IDocument doc, Node node) { + return new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex()); + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPath.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPath.java index e22755b08..7b265b188 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPath.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPath.java @@ -24,6 +24,8 @@ 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; +import reactor.core.publisher.Flux; + /** * @author Kris De Volder */ @@ -301,4 +303,12 @@ public class YamlPath { return new YamlPath(common); } + public static YamlPath decode(List encodedSegments) { + return Flux.fromIterable(encodedSegments) + .map(YamlPathSegment::decode) + .collectList() + .map(YamlPath::new) + .block(); + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPathSegment.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPathSegment.java index def73d205..e0c676077 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPathSegment.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlPathSegment.java @@ -19,6 +19,21 @@ package org.springframework.ide.vscode.commons.yaml.path; */ public abstract class YamlPathSegment { + public static YamlPathSegment decode(String code) { + switch (code.charAt(0)) { + case '*': + return anyChild(); + case '.': + return valueAt(code.substring(1)); + case '&': + return keyAt(code.substring(1)); + case '[': + return valueAt(Integer.parseInt(code.substring(1))); + default: + throw new IllegalArgumentException("Can't decode YamlPathSegment from '"+code+"'"); + } + } + public static enum YamlPathSegmentType { VAL_AT_KEY, //Go to value associate with given key in a map. KEY_AT_KEY, //Go to the key node associated with a given key in a map. @@ -50,7 +65,17 @@ public abstract class YamlPathSegment { @Override public YamlPathSegmentType getType() { return YamlPathSegmentType.ANY_CHILD; + } + + @Override + protected char getTypeCode() { + return '*'; }; + + @Override + protected String getValueCode() { + return ""; + } } public static class AtIndex extends YamlPathSegment { @@ -102,6 +127,16 @@ public abstract class YamlPathSegment { return false; return true; } + + @Override + protected char getTypeCode() { + return '['; + } + + @Override + protected String getValueCode() { + return ""+index; + } } public static class ValAtKey extends YamlPathSegment { @@ -161,6 +196,16 @@ public abstract class YamlPathSegment { return false; return true; } + + @Override + protected char getTypeCode() { + return '.'; + } + + @Override + protected String getValueCode() { + return key; + } } public static class KeyAtKey extends ValAtKey { @@ -174,6 +219,10 @@ public abstract class YamlPathSegment { return YamlPathSegmentType.KEY_AT_KEY; } + @Override + protected char getTypeCode() { + return '&'; + } } @@ -202,4 +251,11 @@ public abstract class YamlPathSegment { return AnyChild.INSTANCE; } + public String encode() { + return getTypeCode() + getValueCode(); + } + + protected abstract String getValueCode(); + + protected abstract char getTypeCode(); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/MissingPropertiesData.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/MissingPropertiesData.java new file mode 100644 index 000000000..d3d41ee1f --- /dev/null +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/MissingPropertiesData.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * 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 java.util.List; + +public class MissingPropertiesData { + + private String uri; + private List path; + private List props; + + public MissingPropertiesData(String uri, List path, List props) { + super(); + this.uri = uri; + this.path = path; + this.props = props; + } + + public MissingPropertiesData() { + } + + public String getUri() { + return uri; + } + public void setUri(String uri) { + this.uri = uri; + } + public List getPath() { + return path; + } + public void setPath(List path) { + this.path = path; + } + public List getProps() { + return props; + } + public void setProps(List props) { + this.props = props; + } + + @Override + public String toString() { + return "MissingPropertiesData [uri=" + uri + ", path=" + path + ", props=" + props + "]"; + } +} diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index a76cf194b..d951b522a 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -15,7 +15,6 @@ import static org.springframework.ide.vscode.commons.util.ExceptionUtil.getSimpl import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -60,14 +59,17 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { private final YamlSchema schema; private final YTypeUtil typeUtil; private final ITypeCollector typeCollector; + private final YamlQuickfixes quickfixes; - public SchemaBasedYamlASTReconciler(IProblemCollector problems, YamlSchema schema, ITypeCollector typeCollector) { + public SchemaBasedYamlASTReconciler(IProblemCollector problems, YamlSchema schema, ITypeCollector typeCollector, YamlQuickfixes quickfixes) { this.problems = problems; this.schema = schema; this.typeCollector = typeCollector; this.typeUtil = schema.getTypeUtil(); + this.quickfixes = quickfixes; } + @Override public void reconcile(YamlFileAST ast) { if (typeCollector!=null) typeCollector.beginCollecting(ast); @@ -205,7 +207,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } } } - + private String getMessage(Exception _e) { Throwable e = ExceptionUtil.getDeepestCause(_e); @@ -247,7 +249,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } else { message = "Properties "+missingProps+" are required for '"+type+"'"; } - problems.accept(YamlSchemaProblems.missingProperty(message, dc.getDocument(), parent, map)); + problems.accept(YamlSchemaProblems.missingProperties(message, dc, missingProps, parent, map, quickfixes.MISSING_PROP_FIX)); } //Check for other constraints attached to the type @@ -347,7 +349,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } problem(region, parseErrorMsg, problemType); } - + private void unknownBeanProperty(Node keyNode, YType type, String name) { problem(keyNode, "Unknown property '"+name+"' for type '"+typeUtil.niceTypeName(type)+"'"); } @@ -408,7 +410,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { private void problem(DocumentRegion region, String msg, ProblemType problemType) { problems.accept(YamlSchemaProblems.problem(problemType, msg, region)); } - + private void problem(DocumentRegion region, String msg) { problems.accept(YamlSchemaProblems.schemaProblem(msg, region)); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java new file mode 100644 index 000000000..a4b88a170 --- /dev/null +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlQuickfixes.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.commons.yaml.completion.YamlPathEdits; +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.structure.YamlDocument; +import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode; +import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode; +import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class YamlQuickfixes { + + public final QuickfixType MISSING_PROP_FIX; + + public YamlQuickfixes(QuickfixRegistry r, SimpleTextDocumentService textDocumentService, YamlStructureProvider structureProvider) { + MISSING_PROP_FIX = r.register("MISSING_PROP_FIX", (Object _params) -> { + MissingPropertiesData params = new ObjectMapper().convertValue(_params, MissingPropertiesData.class); + try { + TextDocument _doc = textDocumentService.getDocument(params.getUri()); + if (_doc!=null) { + YamlDocument doc = new YamlDocument(_doc, structureProvider); + SNode root = doc.getStructure(); + if (root!=null) { + YamlPath path = YamlPath.decode(params.getPath()); + SNode _target = path.traverse(root); + if (_target instanceof SChildBearingNode) { + YamlPathEdits edits = new YamlPathEdits(doc); + SChildBearingNode target = (SChildBearingNode) _target; + for (String prop : params.getProps()) { + edits.createPath(target, new YamlPath(YamlPathSegment.valueAt(prop)), " "); + } + TextReplace replaceEdit = edits.asReplacement(_doc); + if (replaceEdit!=null) { + WorkspaceEdit wsEdits = new WorkspaceEdit(); + wsEdits.setChanges(ImmutableMap.of( + params.getUri(), + ImmutableList.of(new TextEdit(_doc.toRange(replaceEdit.getRegion()), replaceEdit.newText)) + )); + return wsEdits; + } + } + } + } + } catch (Exception e) { + Log.log(e); + } + //Something went wrong. Return empty edit object. + return new WorkspaceEdit(ImmutableMap.of(), null); + }); + } + +} diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java index 27dd3c197..eb6d18cc0 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaBasedReconcileEngine.java @@ -29,9 +29,12 @@ public final class YamlSchemaBasedReconcileEngine extends YamlReconcileEngine { */ private ITypeCollector typeCollector; - public YamlSchemaBasedReconcileEngine(YamlASTProvider parser, YamlSchema schema) { + private YamlQuickfixes quickfixes; + + public YamlSchemaBasedReconcileEngine(YamlASTProvider parser, YamlSchema schema, YamlQuickfixes quickfixes) { super(parser); this.schema = schema; + this.quickfixes = quickfixes; } @Override @@ -41,7 +44,7 @@ public final class YamlSchemaBasedReconcileEngine extends YamlReconcileEngine { @Override protected YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problems) { - return new SchemaBasedYamlASTReconciler(problems, schema, typeCollector); + return new SchemaBasedYamlASTReconciler(problems, schema, typeCollector, quickfixes); } public ITypeCollector getTypeCollector() { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java index ad1fcd0f2..3e719e77c 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java @@ -10,8 +10,13 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.reconcile; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; @@ -19,9 +24,9 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.Reconcile import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; 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.path.NodeCursor; 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.schema.DynamicSchemaContext; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.yaml.snakeyaml.error.Mark; @@ -30,6 +35,7 @@ import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.SequenceNode; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; /** @@ -45,6 +51,7 @@ public class YamlSchemaProblems { public static final ProblemType MISSING_PROPERTY = problemType("MissingProperty", ProblemSeverity.ERROR); public static final ProblemType EXTRA_PROPERTY = problemType("ExtraProperty", ProblemSeverity.ERROR); + public static final Set PROPERTY_CONSTRAINT = ImmutableSet.of( MISSING_PROPERTY, EXTRA_PROPERTY ); @@ -93,7 +100,7 @@ public class YamlSchemaProblems { return deprecatedProperty("Property '"+property.getName()+"' of '"+bean+"' is Deprecated", node); } - public static ReconcileProblem problem(ProblemType problemType, String msg, DocumentRegion node) { + public static ReconcileProblemImpl problem(ProblemType problemType, String msg, DocumentRegion node) { int start = node.getStart(); int end = node.getEnd(); return new ReconcileProblemImpl(problemType, msg, start, end-start); @@ -105,26 +112,46 @@ public class YamlSchemaProblems { return new ReconcileProblemImpl(problemType, msg, start, end-start); } - public static ReconcileProblem missingProperty(String msg, IDocument doc, Node parent, MappingNode map) { + public static ReconcileProblemImpl missingProperty(String msg, IDocument doc, Node parent, MappingNode map) { + DocumentRegion underline = NodeUtil.region(doc, map); if (parent instanceof MappingNode) { for (NodeTuple prop : ((MappingNode) parent).getValue()) { if (prop.getValueNode()==map) { - return problem(MISSING_PROPERTY, msg, prop.getKeyNode()); + underline = NodeUtil.region(doc, prop.getKeyNode()); } } } else if (parent instanceof SequenceNode) { Boolean flowStyle = ((SequenceNode) parent).getFlowStyle(); if (flowStyle!=null && !flowStyle) { Mark nodeStart = map.getStartMark(); - DocumentRegion underline = new DocumentRegion(doc, 0, nodeStart.getIndex()); + underline = new DocumentRegion(doc, 0, nodeStart.getIndex()); underline = underline.trimEnd(); if (underline.endsWith("-")) { underline = underline.subSequence(underline.length()-1, underline.length()); - return problem(MISSING_PROPERTY, msg, underline); } } } - return problem(MISSING_PROPERTY, msg, map); + return problem(MISSING_PROPERTY, msg, underline); + } + + public static ReconcileProblem missingProperties(String msg, DynamicSchemaContext dc, Set missingProps, Node parent, MappingNode map, QuickfixType quickfixType) { + YamlPath contextPath = dc.getPath(); + List segments = Stream.of(contextPath.getSegments()) + .map(YamlPathSegment::encode) + .collect(Collectors.toList()); + + QuickfixData fix = new QuickfixData( + quickfixType, + new MissingPropertiesData( + dc.getDocument().getUri(), + segments, + ImmutableList.copyOf(missingProps) + ), + "Add properties: "+missingProps + ); + + return missingProperty(msg, dc.getDocument(), parent, map) + .addQuickfix(fix); } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java index 1045820f3..aa1af750f 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.schema; -import java.util.Collections; import java.util.Set; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -18,9 +17,6 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; -import org.yaml.snakeyaml.nodes.NodeTuple; - -import com.google.common.collect.ImmutableSet; /** * Adapts a SnakeYaml ast node as a {@link DynamicSchemaContext} (so it diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index 31c25007f..689be120c 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -705,7 +705,7 @@ public class YTypeFactory { t.addHintProvider((dc) -> () -> values.withContext(dc)); t.parseWith((DynamicSchemaContext dc) -> { Collection strings = YTypeFactory.values(values.withContext(dc)); - return new EnumValueParser("blah", strings) { + return new EnumValueParser(name, strings) { @Override protected String createErrorMessage(String parseString, Collection values) { return errorMessageFormatter.apply(parseString, values); diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index da98b1648..a50bbe0db 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -14,7 +14,8 @@ package org.springframework.ide.vscode.languageserver.testharness; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.*; +import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains; +import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertDoesNotContain; import java.util.ArrayList; import java.util.Collections; @@ -43,7 +44,6 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.junit.Assert; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import reactor.core.publisher.Flux; 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 8749edf3a..67f3a81da 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 @@ -18,7 +18,6 @@ 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.VscodeCompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter; @@ -32,6 +31,7 @@ import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAssistContextProvider; 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.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.YamlSchema; @@ -47,6 +47,7 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { YamlASTProvider currentAsts = models.getAstProvider(false); private SchemaSpecificPieces forPipelines; private SchemaSpecificPieces forTasks; + private final YamlQuickfixes yamlQuickfixes; private class SchemaSpecificPieces { @@ -62,7 +63,7 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { HoverInfoProvider infoProvider = new YamlHoverInfoProvider(currentAsts, structureProvider, contextProvider); this.hoverEngine = new VscodeHoverEngineAdapter(ConcourseLanguageServer.this, infoProvider); - this.reconcileEngine = new YamlSchemaBasedReconcileEngine(currentAsts, schema); + this.reconcileEngine = new YamlSchemaBasedReconcileEngine(currentAsts, schema, yamlQuickfixes); reconcileEngine.setTypeCollector(models.getAstTypeCache()); } @@ -73,6 +74,7 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { public ConcourseLanguageServer() { PipelineYmlSchema pipelineSchema = new PipelineYmlSchema(models); + this.yamlQuickfixes = new YamlQuickfixes(getQuickfixRegistry(), documents, structureProvider); this.forPipelines = new SchemaSpecificPieces(pipelineSchema); this.forTasks = new SchemaSpecificPieces(pipelineSchema.getTaskSchema()); @@ -158,6 +160,8 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { c.setDefinitionProvider(true); + c.setCodeActionProvider(true); + return c; } diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java index 35283cf0f..f7885ff8c 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java @@ -13,6 +13,8 @@ package org.springframework.ide.vscode.concourse; import java.util.function.Function; import org.springframework.ide.vscode.commons.util.RegexpParser; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.ValueParseException; import org.springframework.ide.vscode.commons.util.ValueParser; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware; @@ -53,13 +55,24 @@ public class ConcourseValueParsers { public static SchemaContextAware acceptOnlyUniqueNames( Function> getDefinedNameCounts, String typeName + ) { + return acceptOnlyUniqueNames(getDefinedNameCounts, typeName, false); + } + + public static SchemaContextAware acceptOnlyUniqueNames( + Function> getDefinedNameCounts, + String typeName, + boolean allowEmptyName ) { return (dc) -> { - Multiset resourceNames = getDefinedNameCounts.apply(dc.getDocument()); return (String input) -> { + if (!allowEmptyName && !StringUtil.hasText(input)) { + throw new ValueParseException("'"+typeName +"' should not be blank"); + } + Multiset resourceNames = getDefinedNameCounts.apply(dc.getDocument()); if (resourceNames.count(input)<=1) { //okay - return resourceNames; + return input; } throw new IllegalArgumentException("Duplicate "+typeName+" '"+input+"'"); }; diff --git a/vscode-extensions/commons-vscode/src/launch-util.ts b/vscode-extensions/commons-vscode/src/launch-util.ts index a5928026c..944ddd2fe 100644 --- a/vscode-extensions/commons-vscode/src/launch-util.ts +++ b/vscode-extensions/commons-vscode/src/launch-util.ts @@ -28,11 +28,11 @@ export interface ActivatorOptions { jvmHeap?: string; } -export function activate(options: ActivatorOptions, context: VSCode.ExtensionContext) { +export function activate(options: ActivatorOptions, context: VSCode.ExtensionContext): Promise { let DEBUG = options.DEBUG; let jvmHeap = options.jvmHeap; if (options.CONNECT_TO_LS) { - connectToLS(context, options); + return connectToLS(context, options); } else { let clientOptions = options.clientOptions; let fatJarFile = Path.resolve(context.extensionPath, options.fatJarFile); @@ -61,7 +61,7 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon log("Found java exe: " + javaExecutablePath); - isJava8(javaExecutablePath).then(eight => { + return isJava8(javaExecutablePath).then(eight => { if (!eight) { VSCode.window.showErrorMessage('Java-based Language Server requires Java 8 (using ' + javaExecutablePath + ')'); return; @@ -109,12 +109,12 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon }); } - setupLanguageClient(context, createServer, options); + return Promise.resolve(setupLanguageClient(context, createServer, options)); }); } } -function connectToLS(context: VSCode.ExtensionContext, options: ActivatorOptions) { +function connectToLS(context: VSCode.ExtensionContext, options: ActivatorOptions): Promise { let connectionInfo = { port: 5007 }; @@ -128,10 +128,10 @@ function connectToLS(context: VSCode.ExtensionContext, options: ActivatorOptions return Promise.resolve(result); }; - setupLanguageClient(context, serverOptions, options); + return Promise.resolve(setupLanguageClient(context, serverOptions, options)); } -function setupLanguageClient(context: VSCode.ExtensionContext, createServer: ServerOptions, options: ActivatorOptions) { +function setupLanguageClient(context: VSCode.ExtensionContext, createServer: ServerOptions, options: ActivatorOptions): LanguageClient { // Create the language client and start the client. let client = new LanguageClient(options.extensionId, options.extensionId, createServer, options.clientOptions @@ -150,6 +150,7 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser // client can be deactivated on extension deactivation context.subscriptions.push(disposable); context.subscriptions.push(progressService); + return client } diff --git a/vscode-extensions/vscode-concourse/.vscode/launch.json b/vscode-extensions/vscode-concourse/.vscode/launch.json index 52ebf9f9e..140324f98 100644 --- a/vscode-extensions/vscode-concourse/.vscode/launch.json +++ b/vscode-extensions/vscode-concourse/.vscode/launch.json @@ -2,13 +2,14 @@ { "version": "0.1.0", "configurations": [ + { "name": "Launch Extension", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], - "stopOnEntry": false, + "stopOnEntry": true, "sourceMaps": true, "outDir": "${workspaceRoot}/out/lib", "preLaunchTask": "npm" diff --git a/vscode-extensions/vscode-concourse/lib/Main.ts b/vscode-extensions/vscode-concourse/lib/Main.ts index 8032d37d3..9d8126ea2 100644 --- a/vscode-extensions/vscode-concourse/lib/Main.ts +++ b/vscode-extensions/vscode-concourse/lib/Main.ts @@ -8,8 +8,10 @@ import * as Path from 'path'; import * as FS from 'fs'; import * as Net from 'net'; import * as ChildProcess from 'child_process'; -import {LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo} from 'vscode-languageclient'; +import {LanguageClient, RequestType, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo} from 'vscode-languageclient'; import {TextDocument, OutputChannel} from 'vscode'; +import {WorkspaceEdit} from 'vscode-languageserver-types'; +import * as p2c from 'vscode-languageclient/lib/protocolConverter'; var log_output : OutputChannel = null; @@ -28,8 +30,14 @@ function error(msg : string) { } } +interface QuickfixRequest { + type: string; + params: any; +} + /** Called when extension is activated */ export function activate(context: VSCode.ExtensionContext) { + let commands = VSCode.commands let options : commons.ActivatorOptions = { DEBUG : false, CONNECT_TO_LS: false, @@ -47,6 +55,21 @@ export function activate(context: VSCode.ExtensionContext) { } } }; - commons.activate(options, context); + let clientPromise = commons.activate(options, context); + commands.registerCommand("sts.quickfix", (fixType, fixParams) => { + return clientPromise.then(client => { + let type : RequestType = {method : "sts/quickfix"}; + let params : QuickfixRequest = {type: fixType, params: fixParams}; + return client.sendRequest(type, params) + .then( + (edit) => { + return VSCode.workspace.applyEdit(p2c.asWorkspaceEdit(edit)) + }, + (error) => { + return VSCode.window.showErrorMessage(""+error) + } + ); + }) + }); } diff --git a/vscode-extensions/vscode-concourse/package.json b/vscode-extensions/vscode-concourse/package.json index 9dc730516..760e867ac 100644 --- a/vscode-extensions/vscode-concourse/package.json +++ b/vscode-extensions/vscode-concourse/package.json @@ -28,6 +28,12 @@ "onLanguage:concourse-task-yaml" ], "contributes": { + "commands": [ + { + "command": "mycommand.sayHello", + "title": "Say Hello" + } + ], "languages": [ { "id": "concourse-pipeline-yaml", @@ -84,7 +90,7 @@ }, "devDependencies": { "vsce": "^1.17.0", - "typescript": "^2.0.x", + "typescript": "^2.2.x", "@types/node": "^6.0.40", "vscode": "^1.0.0" }