Add quickfix for missing properties in concourse editor

This commit is contained in:
Kris De Volder
2017-04-11 17:08:03 -07:00
parent 7e2f6a6c9c
commit aee00fb487
40 changed files with 721 additions and 165 deletions

View File

@@ -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) {

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -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();

View File

@@ -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<PropertyInfo> 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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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<String, IJavaProject> 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>() {
//
//
// 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<Path>() {
//
//
// @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);
}

View File

@@ -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.
* <p>
* 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<ICompletionProposal> proposals
);
}

View File

@@ -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<T> {
public static class QuickfixData<T> {
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<T> data;
public Quickfix(Range range, QuickfixData<T> 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);
}
}

View File

@@ -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);
}

View File

@@ -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}.
* <p>
* 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.
* <p>
* 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<String, QuickfixHandler> 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<WorkspaceEdit> handle(QuickfixResolveParams params) {
QuickfixHandler handler = registry.get(params.getType());
return Mono.fromSupplier(() -> {
return handler.createEdits(params.getParams());
}).toFuture();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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<QuickfixData<?>> getQuickfixes();
}

View File

@@ -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<QuickfixData<?>> 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<QuickfixData<?>> getQuickfixes() {
return fixes;
}
public ReconcileProblemImpl addQuickfix(QuickfixData<?> command) {
fixes.add(command);
return this;
}
}

View File

@@ -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<Void> 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<InitializeResult> initialize(InitializeParams params) {
// LOG.info("Initializing");
@@ -166,9 +182,11 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
IProblemCollector problems = new IProblemCollector() {
private List<Diagnostic> diagnostics = new ArrayList<>();
private List<Quickfix> 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<QuickfixData<?>> 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<WorkspaceEdit> quickfixResolve(QuickfixResolveParams params) {
QuickfixRegistry quickfixes = getQuickfixRegistry();
return quickfixes.handle(params);
}
}

View File

@@ -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<String, TextDocument> documents = new HashMap<>();
private Map<String, TrackedDocument> documents = new HashMap<>();
private ListenerList<TextDocumentContentChange> 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<TextDocument> 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<List<? extends Command>> 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<? extends Command>) l);
} else {
return CompletableFuture.completedFuture(ImmutableList.of());
}
}
@Override
@@ -320,12 +335,21 @@ public class SimpleTextDocumentService implements TextDocumentService {
}
}
public void setQuickfixes(TextDocument doc, List<Quickfix> 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<List<? extends DocumentHighlight>> documentHighlight(TextDocumentPositionParams position) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
}

View File

@@ -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<Quickfix> quickfixes = ImmutableList.of();
public TrackedDocument(TextDocument doc) {
this.doc = doc;
}
public TextDocument getDocument() {
return doc;
}
public void setQuickfixes(List<Quickfix> quickfixes) {
this.quickfixes = quickfixes;
}
public List<Quickfix> getQuickfixes() {
return quickfixes;
}
}

View File

@@ -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 <T> CompletableFuture<T> of(T value) {
return CompletableFuture.completedFuture(value);
}
public static <T> CompletableFuture<T> error(String message) {
CompletableFuture<T> f = new CompletableFuture<T>();
f.completeExceptionally(new IOException(message));
return f;
}
public static <T> CompletableFuture<T> error(Throwable e) {
CompletableFuture<T> f = new CompletableFuture<T>();
f.completeExceptionally(e);
return f;
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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<String> encodedSegments) {
return Flux.fromIterable(encodedSegments)
.map(YamlPathSegment::decode)
.collectList()
.map(YamlPath::new)
.block();
}
}

View File

@@ -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();
}

View File

@@ -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<String> path;
private List<String> props;
public MissingPropertiesData(String uri, List<String> path, List<String> 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<String> getPath() {
return path;
}
public void setPath(List<String> path) {
this.path = path;
}
public List<String> getProps() {
return props;
}
public void setProps(List<String> props) {
this.props = props;
}
@Override
public String toString() {
return "MissingPropertiesData [uri=" + uri + ", path=" + path + ", props=" + props + "]";
}
}

View File

@@ -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));
}

View File

@@ -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);
});
}
}

View File

@@ -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() {

View File

@@ -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<ProblemType> 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<String> missingProps, Node parent, MappingNode map, QuickfixType quickfixType) {
YamlPath contextPath = dc.getPath();
List<String> segments = Stream.of(contextPath.getSegments())
.map(YamlPathSegment::encode)
.collect(Collectors.toList());
QuickfixData<MissingPropertiesData> fix = new QuickfixData<MissingPropertiesData>(
quickfixType,
new MissingPropertiesData(
dc.getDocument().getUri(),
segments,
ImmutableList.copyOf(missingProps)
),
"Add properties: "+missingProps
);
return missingProperty(msg, dc.getDocument(), parent, map)
.addQuickfix(fix);
}
}

View File

@@ -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

View File

@@ -705,7 +705,7 @@ public class YTypeFactory {
t.addHintProvider((dc) -> () -> values.withContext(dc));
t.parseWith((DynamicSchemaContext dc) -> {
Collection<String> strings = YTypeFactory.values(values.withContext(dc));
return new EnumValueParser("blah", strings) {
return new EnumValueParser(name, strings) {
@Override
protected String createErrorMessage(String parseString, Collection<String> values) {
return errorMessageFormatter.apply(parseString, values);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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<ValueParser> acceptOnlyUniqueNames(
Function<IDocument, Multiset<String>> getDefinedNameCounts,
String typeName
) {
return acceptOnlyUniqueNames(getDefinedNameCounts, typeName, false);
}
public static SchemaContextAware<ValueParser> acceptOnlyUniqueNames(
Function<IDocument, Multiset<String>> getDefinedNameCounts,
String typeName,
boolean allowEmptyName
) {
return (dc) -> {
Multiset<String> resourceNames = getDefinedNameCounts.apply(dc.getDocument());
return (String input) -> {
if (!allowEmptyName && !StringUtil.hasText(input)) {
throw new ValueParseException("'"+typeName +"' should not be blank");
}
Multiset<String> resourceNames = getDefinedNameCounts.apply(dc.getDocument());
if (resourceNames.count(input)<=1) {
//okay
return resourceNames;
return input;
}
throw new IllegalArgumentException("Duplicate "+typeName+" '"+input+"'");
};

View File

@@ -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<LanguageClient> {
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<LanguageClient> {
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
}

View File

@@ -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"

View File

@@ -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<QuickfixRequest, WorkspaceEdit, void> = {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)
}
);
})
});
}

View File

@@ -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"
}