Snippet generator mostly working, still needs some tweaks / bug fixes.
This commit is contained in:
@@ -25,13 +25,13 @@ import org.springframework.ide.vscode.bosh.models.ReleasesModel;
|
||||
import org.springframework.ide.vscode.bosh.models.StemcellData;
|
||||
import org.springframework.ide.vscode.bosh.models.StemcellModel;
|
||||
import org.springframework.ide.vscode.bosh.models.StemcellsModel;
|
||||
import org.springframework.ide.vscode.bosh.snippets.SchemaBasedSnippetGenerator;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.PartialCollection;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParsers;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
|
||||
@@ -40,7 +40,6 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
|
||||
@@ -53,7 +52,6 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
|
||||
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
@@ -328,7 +326,6 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
return new StemcellModel(path.dropLast().traverseToNode(ast));
|
||||
}
|
||||
|
||||
|
||||
private String getCurrentEntityProperty(DynamicSchemaContext dc, String propName) {
|
||||
YamlPath path = dc.getPath();
|
||||
YamlFileAST ast = asts.getSafeAst(dc.getDocument(), true);
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.ide.vscode.bosh.models.CloudConfigModel;
|
||||
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.ReleasesModel;
|
||||
import org.springframework.ide.vscode.bosh.models.StemcellsModel;
|
||||
import org.springframework.ide.vscode.bosh.snippets.SchemaBasedSnippetGenerator;
|
||||
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.VscodeHoverEngine;
|
||||
@@ -23,7 +24,6 @@ import org.springframework.ide.vscode.commons.languageserver.util.Settings;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
|
||||
@@ -41,6 +41,7 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvid
|
||||
public class BoshLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
private final VscodeCompletionEngineAdapter completionEngine;
|
||||
private BoshDeploymentManifestSchema schema;
|
||||
|
||||
public BoshLanguageServer(BoshCliConfig cliConfig,
|
||||
DynamicModelProvider<CloudConfigModel> cloudConfigProvider,
|
||||
@@ -52,10 +53,11 @@ public class BoshLanguageServer extends SimpleLanguageServer {
|
||||
SimpleTextDocumentService documents = getTextDocumentService();
|
||||
|
||||
ASTTypeCache astTypeCache = new ASTTypeCache();
|
||||
BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(asts, astTypeCache, cloudConfigProvider, stemcellsProvider, releasesProvider);
|
||||
schema = new BoshDeploymentManifestSchema(asts, astTypeCache, cloudConfigProvider, stemcellsProvider, releasesProvider);
|
||||
|
||||
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
|
||||
YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);
|
||||
enableSnippets(true);
|
||||
YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider, YamlCompletionEngineOptions.DEFAULT);
|
||||
completionEngine = createCompletionEngineAdapter(this, yamlCompletionEngine);
|
||||
HoverInfoProvider infoProvider = new YamlHoverInfoProvider(asts.getAstProvider(true), structureProvider, contextProvider);
|
||||
@@ -87,6 +89,14 @@ public class BoshLanguageServer extends SimpleLanguageServer {
|
||||
}
|
||||
}
|
||||
|
||||
public void enableSnippets(boolean enable) {
|
||||
if (enable) {
|
||||
schema.f.setSnippetProvider(new SchemaBasedSnippetGenerator(schema.getTypeUtil(), this::createSnippetBuilder));
|
||||
} else {
|
||||
schema.f.setSnippetProvider(null);
|
||||
}
|
||||
}
|
||||
|
||||
public BoshLanguageServer setMaxCompletions(int maxCompletions) {
|
||||
completionEngine.setMaxCompletions(maxCompletions);
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*******************************************************************************
|
||||
* 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.bosh.snippets;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.Snippet;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* An implementation of {@link TypeBasedSnippetProvider} that generates snippets
|
||||
* automatically from schema types.
|
||||
*/
|
||||
public class SchemaBasedSnippetGenerator implements TypeBasedSnippetProvider {
|
||||
|
||||
private YTypeUtil typeUtil;
|
||||
private Supplier<SnippetBuilder> snippetBuilderFactory;
|
||||
|
||||
public SchemaBasedSnippetGenerator(YTypeUtil typeUtil, Supplier<SnippetBuilder> snippetBuilderFactory) {
|
||||
super();
|
||||
this.typeUtil = typeUtil;
|
||||
this.snippetBuilderFactory = snippetBuilderFactory;
|
||||
}
|
||||
|
||||
private Cache<YType, Collection<Snippet>> cache = CacheBuilder.newBuilder().build();
|
||||
private int maxNesting = Integer.MAX_VALUE;
|
||||
|
||||
@Override
|
||||
public Collection<Snippet> getSnippets(YType type) {
|
||||
try {
|
||||
return cache.get(type, () -> generateSnippets(type));
|
||||
} catch (ExecutionException e) {
|
||||
Log.log(e);
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Snippet> generateSnippets(YType type) {
|
||||
ImmutableList.Builder<Snippet> snippets = ImmutableList.builder();
|
||||
//Generate a 'full' snippet that defines all required properties of the current type.
|
||||
Snippet snippet = generateFullSnippet(type, 0);
|
||||
if (snippet!=null) {
|
||||
snippets.add(snippet);
|
||||
}
|
||||
//Generate single property snippets that only define a single properties (with 'mega snippets' for nested types)
|
||||
for (YTypedProperty p : typeUtil.getProperties(type)) {
|
||||
String propName = p.getName();
|
||||
SnippetBuilder builder = snippetBuilderFactory.get();
|
||||
generateBeanSnippet(ImmutableList.of(p), builder, 0, maxNesting);
|
||||
if (builder.getPlaceholderCount()>=2) {
|
||||
snippets.add(new Snippet(p.getName()+" Snippet", builder.toString(), (dc) ->
|
||||
!dc.getDefinedProperties().contains(propName)
|
||||
));
|
||||
}
|
||||
}
|
||||
return snippets.build();
|
||||
}
|
||||
|
||||
private Snippet generateFullSnippet(YType type, int indent) {
|
||||
if (typeUtil.isBean(type)) {
|
||||
SnippetBuilder builder = snippetBuilderFactory.get();
|
||||
List<YTypedProperty> requiredProps = typeUtil.getProperties(type).stream()
|
||||
.filter(p -> p.isPrimary() || p.isRequired())
|
||||
.collect(Collectors.toList());
|
||||
if (!requiredProps.isEmpty()) {
|
||||
generateBeanSnippet(requiredProps, builder, indent, maxNesting);
|
||||
}
|
||||
if (builder.getPlaceholderCount()>=2) {
|
||||
//place holder count is a good indicator of snippet complexity and allows us to
|
||||
// avoid creating trivial snippets (which aren't very useful).
|
||||
ImmutableSet<YTypedProperty> vetoProps = ImmutableSet.copyOf(requiredProps);
|
||||
// Do not suggest snippet if it contains properties that are already defined.
|
||||
return new Snippet(typeUtil.niceTypeName(type)+" Snippet", builder.toString(), (dc) ->
|
||||
dc.getDefinedProperties().stream().noneMatch(vetoProps::contains)
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void generateBeanSnippet(List<YTypedProperty> props, SnippetBuilder builder, int indent, int nestingLimit) {
|
||||
if (nestingLimit>0) {
|
||||
boolean first = true;
|
||||
for (YTypedProperty p : props) {
|
||||
if (!first) {
|
||||
builder.newline(indent);
|
||||
}
|
||||
builder.text(p.getName());
|
||||
builder.text(":");
|
||||
generateNestedSnippet(false, p.getType(), builder, indent, nestingLimit-1);
|
||||
first = false;
|
||||
}
|
||||
} else {
|
||||
//reached the limit of bean number of nested property expansions allowed.
|
||||
builder.placeHolder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void generateNestedSnippet(boolean parentIsSeq, YType type, SnippetBuilder builder, int indent, int nestingLimit) {
|
||||
if (type==null) {
|
||||
//Assume its some kind of pojo bean
|
||||
builder.newline(indent+YamlIndentUtil.INDENT_BY);
|
||||
builder.placeHolder();
|
||||
} else if (typeUtil.isBean(type) || typeUtil.isMap(type)) {
|
||||
if (!parentIsSeq) {
|
||||
//ready to enter nested keys on next line
|
||||
indent += YamlIndentUtil.INDENT_BY;
|
||||
builder.newline(indent);
|
||||
}
|
||||
//Insert required keys
|
||||
List<YTypedProperty> requiredProps = typeUtil.getProperties(type).stream()
|
||||
.filter(p -> p.isPrimary() || p.isRequired())
|
||||
.collect(Collectors.toList());
|
||||
generateBeanSnippet(requiredProps, builder, indent, nestingLimit);
|
||||
} else if (typeUtil.isSequencable(type)) {
|
||||
//ready to enter sequence element on next line
|
||||
builder.newline(indent);
|
||||
builder.text("- ");
|
||||
indent += YamlIndentUtil.INDENT_BY;
|
||||
generateNestedSnippet(true, typeUtil.getDomainType(type), builder, indent, nestingLimit);
|
||||
} else { //Treat like atomic
|
||||
//ready to enter whatever on the same line
|
||||
builder.text(" ");
|
||||
builder.placeHolder();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,7 +45,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public class BoshEditorTest {
|
||||
|
||||
LanguageServerHarness harness;
|
||||
LanguageServerHarness<BoshLanguageServer> harness;
|
||||
|
||||
private BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider(cliConfig);
|
||||
@@ -53,7 +53,7 @@ public class BoshEditorTest {
|
||||
private DynamicModelProvider<ReleasesModel> releasesProvider = mock(DynamicModelProvider.class);
|
||||
|
||||
@Before public void setup() throws Exception {
|
||||
harness = new LanguageServerHarness(() -> {
|
||||
harness = new LanguageServerHarness<BoshLanguageServer>(() -> {
|
||||
return new BoshLanguageServer(cliConfig, cloudConfigProvider,
|
||||
(dc) -> stemcellsProvider.getModel(dc),
|
||||
(dc) -> releasesProvider.getModel(dc)
|
||||
@@ -156,6 +156,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void toplevelPropertyCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"<*>"
|
||||
);
|
||||
@@ -200,6 +201,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void stemcellCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"stemcells:\n" +
|
||||
"- <*>"
|
||||
@@ -253,6 +255,7 @@ public class BoshEditorTest {
|
||||
|
||||
|
||||
@Test public void releasesBlockCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- <*>"
|
||||
@@ -518,6 +521,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void updateBlockCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"update:\n" +
|
||||
" <*>"
|
||||
@@ -557,6 +561,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void variablesBlockCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"variables:\n" +
|
||||
"- <*>"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* 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.bosh;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.bosh.snippets.SchemaBasedSnippetGenerator;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
|
||||
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.YTypeUtil;
|
||||
|
||||
public class SchemaBasedSnippetGeneratorTest {
|
||||
|
||||
private ASTTypeCache astTypes = new ASTTypeCache();
|
||||
private YamlAstCache asts = new YamlAstCache();
|
||||
private BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(asts, astTypes, (dc) -> null, (dc) -> null, (dc) -> null);
|
||||
private YTypeUtil typeUtil = schema.getTypeUtil();
|
||||
private SchemaBasedSnippetGenerator generator = new SchemaBasedSnippetGenerator(typeUtil, SnippetBuilder::new);
|
||||
|
||||
@Test
|
||||
public void toplevelSnippet() throws Exception {
|
||||
YType v2Schema = typeUtil.inferMoreSpecificType(schema.getTopLevelType(), DynamicSchemaContext.NULL);
|
||||
System.out.println(generator.getSnippets(v2Schema).iterator().next());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,11 +35,7 @@ public class SnippetBuilder {
|
||||
* be overridden by subclasses to support other formats.
|
||||
* <p>
|
||||
* The default implementation creates place holder strings that
|
||||
* match the undocumented format vscode currently supports.
|
||||
* <p>
|
||||
* Note: this format is explicitly different from what the LSP
|
||||
* specifies. So it is very likely we should change this implementation
|
||||
* in the near future.
|
||||
* match format specified by LSP 3.0.
|
||||
*/
|
||||
protected String createPlaceHolder(int id) {
|
||||
return "$"+id;
|
||||
@@ -50,4 +46,24 @@ public class SnippetBuilder {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void newline(int indent) {
|
||||
buf.append("\n");
|
||||
for (int i = 0; i < indent; i++) {
|
||||
buf.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureSpace() {
|
||||
if (buf.length()>0 && !Character.isWhitespace(buf.charAt(buf.length()-1))) {
|
||||
buf.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of placeholder that where inserted in the snippet.
|
||||
*/
|
||||
public int getPlaceholderCount() {
|
||||
return nextPlaceHolderId-1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.Snippet;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
|
||||
@@ -98,6 +100,29 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
List<ICompletionProposal> completions = getValueCompletions(doc, node, offset, query);
|
||||
if (completions.isEmpty()) {
|
||||
completions = getKeyCompletions(doc, offset, query);
|
||||
TypeBasedSnippetProvider snippetProvider = typeUtil.getSnippetProvider();
|
||||
if (snippetProvider!=null) {
|
||||
Collection<Snippet> snippets = snippetProvider.getSnippets(type);
|
||||
YamlIndentUtil indenter = new YamlIndentUtil(doc);
|
||||
for (Snippet snippet : snippets) {
|
||||
String snippetName = snippet.getName();
|
||||
double score = FuzzyMatcher.matchScore(query, snippetName);
|
||||
if (score!=0.0) {
|
||||
String textBeforeQuery = doc.getLineTextBefore(offset);
|
||||
DocumentEdits edits = new DocumentEdits(doc.getDocument());
|
||||
int start = offset - query.length();
|
||||
edits.delete(start, query);
|
||||
int referenceIndent = textBeforeQuery.length();
|
||||
boolean needsSpace = start > 0 && !Character.isWhitespace(doc.getChar(offset-1));
|
||||
if (needsSpace) {
|
||||
referenceIndent++;
|
||||
edits.insert(start, " ");
|
||||
}
|
||||
edits.insert(start, indenter.applyIndentation(snippet.getSnippet(), referenceIndent));
|
||||
completions.add(completionFactory().valueProposal(snippetName, query, snippetName, type, null, score, edits, typeUtil));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeUtil.isSequencable(type)) {
|
||||
completions = new ArrayList<>(completions);
|
||||
|
||||
@@ -13,15 +13,15 @@ package org.springframework.ide.vscode.commons.yaml.completion;
|
||||
public interface YamlCompletionEngineOptions {
|
||||
/**
|
||||
* Whether the completion engine includes 'less indented' proposals (i.e. proposals
|
||||
* that aren't valid at the current CA position, but are valid if we delete
|
||||
* some spaces in front of the cursor first.
|
||||
* that aren't valid at the current CA position, but are valid if we delete
|
||||
* some spaces in front of the cursor first.
|
||||
*/
|
||||
default boolean includeDeindentedProposals() {
|
||||
default boolean includeDeindentedProposals() {
|
||||
//Disabled by default for now because of bug introduced in VSCode 1.12:
|
||||
//https://github.com/Microsoft/vscode/issues/26096
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
YamlCompletionEngineOptions DEFAULT = new YamlCompletionEngineOptions() {};
|
||||
YamlCompletionEngineOptions TEST_DEFAULT = new YamlCompletionEngineOptions() {
|
||||
@Override public boolean includeDeindentedProposals() { return true; }
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -31,8 +30,6 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.Replaceme
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.EnumValueParser;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.PartialCollection;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
@@ -40,8 +37,8 @@ import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -60,6 +57,7 @@ public class YTypeFactory {
|
||||
|
||||
private boolean enableTieredOptionalPropertyProposals = true;
|
||||
private boolean suggestDeprecatedProperties = true;
|
||||
private TypeBasedSnippetProvider snippetProvider = null;
|
||||
|
||||
private static class Deprecation {
|
||||
final String errorMsg;
|
||||
@@ -239,6 +237,11 @@ public class YTypeFactory {
|
||||
return ((AbstractType)type).getCustomContentAssistant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeBasedSnippetProvider getSnippetProvider() {
|
||||
return snippetProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tieredOptionalPropertyProposals() {
|
||||
return enableTieredOptionalPropertyProposals;
|
||||
@@ -980,5 +983,10 @@ public class YTypeFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
public YTypeFactory setSnippetProvider(TypeBasedSnippetProvider snippetProvider) {
|
||||
this.snippetProvider = snippetProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,12 +10,15 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.schema;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.PartialCollection;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.Snippet;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
|
||||
/**
|
||||
* An implementation of YTypeUtil provides implementations of various
|
||||
@@ -51,6 +54,14 @@ public interface YTypeUtil {
|
||||
|
||||
ISubCompletionEngine getCustomContentAssistant(YType type);
|
||||
|
||||
/**
|
||||
* Config option for type-bases complection enging. Snippets can be
|
||||
* associated with schema types. These snippets will be suggested as
|
||||
* additional completions based on the type of value expected in
|
||||
* a context.
|
||||
*/
|
||||
TypeBasedSnippetProvider getSnippetProvider();
|
||||
|
||||
/**
|
||||
* Config option for type-based completion engine. This enables the
|
||||
* 'tiered' proposals feature (so that optional properties are not
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*******************************************************************************
|
||||
* 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.snippet;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
|
||||
public class Snippet {
|
||||
|
||||
private final String name;
|
||||
private final String snippet;
|
||||
private final Predicate<DynamicSchemaContext> applicability;
|
||||
|
||||
public Snippet(String name, String snippet, Predicate<DynamicSchemaContext> applicability) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.snippet = snippet;
|
||||
this.applicability = applicability;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getSnippet() {
|
||||
return snippet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Snippet [ name="+name+",\n" +snippet +"\n]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* 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.snippet;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
|
||||
public interface TypeBasedSnippetProvider {
|
||||
|
||||
Collection<Snippet> getSnippets(YType contextType);
|
||||
|
||||
}
|
||||
@@ -92,16 +92,16 @@ import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class LanguageServerHarness {
|
||||
public class LanguageServerHarness<S extends SimpleLanguageServer> {
|
||||
|
||||
//Warning this 'harness' is incomplete. Growing it as needed.
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
private Callable<? extends SimpleLanguageServer> factory;
|
||||
private Callable<S> factory;
|
||||
private LanguageId defaultLanguageId;
|
||||
|
||||
private SimpleLanguageServer server;
|
||||
private S server;
|
||||
|
||||
private InitializeResult initResult;
|
||||
|
||||
@@ -110,12 +110,12 @@ public class LanguageServerHarness {
|
||||
private List<Editor> activeEditors = new ArrayList<>();
|
||||
|
||||
|
||||
public LanguageServerHarness(Callable<? extends SimpleLanguageServer> factory, LanguageId defaultLanguageId) {
|
||||
public LanguageServerHarness(Callable<S> factory, LanguageId defaultLanguageId) {
|
||||
this.factory = factory;
|
||||
this.defaultLanguageId = defaultLanguageId;
|
||||
}
|
||||
|
||||
public LanguageServerHarness(Callable<? extends SimpleLanguageServer> factory) throws Exception {
|
||||
public LanguageServerHarness(Callable<S> factory) throws Exception {
|
||||
this(factory, LanguageId.PLAINTEXT);
|
||||
}
|
||||
|
||||
@@ -184,9 +184,9 @@ public class LanguageServerHarness {
|
||||
workspaceCap.setExecuteCommand(exeCap);
|
||||
clientCap.setWorkspace(workspaceCap);
|
||||
initParams.setCapabilities(clientCap);
|
||||
initResult = server.initialize(initParams).get();
|
||||
if (server instanceof LanguageClientAware) {
|
||||
((LanguageClientAware) server).connect(new STS4LanguageClient() {
|
||||
initResult = getServer().initialize(initParams).get();
|
||||
if (getServer() instanceof LanguageClientAware) {
|
||||
((LanguageClientAware) getServer()).connect(new STS4LanguageClient() {
|
||||
@Override
|
||||
public void telemetryEvent(Object object) {
|
||||
// TODO Auto-generated method stub
|
||||
@@ -248,15 +248,15 @@ public class LanguageServerHarness {
|
||||
});
|
||||
|
||||
}
|
||||
server.initialized();
|
||||
getServer().initialized();
|
||||
return initResult;
|
||||
}
|
||||
|
||||
public TextDocumentInfo openDocument(TextDocumentInfo documentInfo) throws Exception {
|
||||
DidOpenTextDocumentParams didOpen = new DidOpenTextDocumentParams();
|
||||
didOpen.setTextDocument(documentInfo.getDocument());
|
||||
if (server!=null) {
|
||||
server.getTextDocumentService().didOpen(didOpen);
|
||||
if (getServer()!=null) {
|
||||
getServer().getTextDocumentService().didOpen(didOpen);
|
||||
}
|
||||
return documentInfo;
|
||||
}
|
||||
@@ -295,8 +295,8 @@ public class LanguageServerHarness {
|
||||
default:
|
||||
throw new IllegalStateException("Unkown SYNC mode: "+getDocumentSyncMode());
|
||||
}
|
||||
if (server!=null) {
|
||||
server.getTextDocumentService().didChange(didChange);
|
||||
if (getServer()!=null) {
|
||||
getServer().getTextDocumentService().didChange(didChange);
|
||||
}
|
||||
return documents.get(uri);
|
||||
}
|
||||
@@ -320,8 +320,8 @@ public class LanguageServerHarness {
|
||||
default:
|
||||
throw new IllegalStateException("Unkown SYNC mode: "+getDocumentSyncMode());
|
||||
}
|
||||
if (server!=null) {
|
||||
server.getTextDocumentService().didChange(didChange);
|
||||
if (getServer()!=null) {
|
||||
getServer().getTextDocumentService().didChange(didChange);
|
||||
}
|
||||
return documents.get(uri);
|
||||
}
|
||||
@@ -339,7 +339,7 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public PublishDiagnosticsParams getDiagnostics(TextDocumentInfo doc) throws Exception {
|
||||
this.server.waitForReconcile();
|
||||
this.getServer().waitForReconcile();
|
||||
return diagnostics.get(doc.getUri());
|
||||
}
|
||||
|
||||
@@ -376,8 +376,8 @@ public class LanguageServerHarness {
|
||||
TextDocumentPositionParams params = new TextDocumentPositionParams();
|
||||
params.setPosition(cursor);
|
||||
params.setTextDocument(doc.getId());
|
||||
server.waitForReconcile();
|
||||
Either<List<CompletionItem>, CompletionList> completions = server.getTextDocumentService().completion(params).get();
|
||||
getServer().waitForReconcile();
|
||||
Either<List<CompletionItem>, CompletionList> completions = getServer().getTextDocumentService().completion(params).get();
|
||||
if (completions.isLeft()) {
|
||||
List<CompletionItem> list = completions.getLeft();
|
||||
return new CompletionList(false, list);
|
||||
@@ -391,14 +391,14 @@ public class LanguageServerHarness {
|
||||
TextDocumentPositionParams params = new TextDocumentPositionParams();
|
||||
params.setPosition(cursor);
|
||||
params.setTextDocument(document.getId());
|
||||
return server.getTextDocumentService().hover(params ).get();
|
||||
return getServer().getTextDocumentService().hover(params ).get();
|
||||
}
|
||||
|
||||
|
||||
public CompletionItem resolveCompletionItem(CompletionItem maybeUnresolved) {
|
||||
if (server.hasLazyCompletionResolver()) {
|
||||
if (getServer().hasLazyCompletionResolver()) {
|
||||
try {
|
||||
return server.getTextDocumentService().resolveCompletionItem(maybeUnresolved).get();
|
||||
return getServer().getTextDocumentService().resolveCompletionItem(maybeUnresolved).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -477,14 +477,14 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public List<? extends Location> getDefinitions(TextDocumentPositionParams params) throws Exception {
|
||||
server.waitForReconcile(); //goto definitions relies on reconciler infos! Must wait or race condition breaking tests occasionally.
|
||||
return server.getTextDocumentService().definition(params).get();
|
||||
getServer().waitForReconcile(); //goto definitions relies on reconciler infos! Must wait or race condition breaking tests occasionally.
|
||||
return getServer().getTextDocumentService().definition(params).get();
|
||||
}
|
||||
|
||||
public List<CodeAction> getCodeActions(TextDocumentInfo doc, Diagnostic problem) throws Exception {
|
||||
CodeActionContext context = new CodeActionContext(ImmutableList.of(problem));
|
||||
List<? extends Command> actions =
|
||||
server.getTextDocumentService().codeAction(new CodeActionParams(doc.getId(), problem.getRange(), context)).get();
|
||||
getServer().getTextDocumentService().codeAction(new CodeActionParams(doc.getId(), problem.getRange(), context)).get();
|
||||
return actions.stream()
|
||||
.map((command) -> new CodeAction(this, command))
|
||||
.collect(Collectors.toList());
|
||||
@@ -498,7 +498,7 @@ public class LanguageServerHarness {
|
||||
//Note convert the params to a 'typeless' Object because that is more representative on how it will be
|
||||
// received when we get it in a real client/server setting (i.e. parsed from json).
|
||||
List untypedParams = mapper.convertValue(args, List.class);
|
||||
server.getWorkspaceService()
|
||||
getServer().getWorkspaceService()
|
||||
.executeCommand(new ExecuteCommandParams(command.getCommand(), untypedParams))
|
||||
.get();
|
||||
}
|
||||
@@ -544,9 +544,9 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public List<? extends SymbolInformation> getDocumentSymbols(TextDocumentInfo document) throws Exception {
|
||||
server.waitForReconcile(); //TODO: if the server works properly this shouldn't be needed it should do that internally itself somehow.
|
||||
getServer().waitForReconcile(); //TODO: if the server works properly this shouldn't be needed it should do that internally itself somehow.
|
||||
DocumentSymbolParams params = new DocumentSymbolParams(document.getId());
|
||||
return server.getTextDocumentService().documentSymbol(params).get();
|
||||
return getServer().getTextDocumentService().documentSymbol(params).get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,7 +554,7 @@ public class LanguageServerHarness {
|
||||
*/
|
||||
public SynchronizationPoint reconcilerThreadStart() {
|
||||
CompletableFuture<Void> blocker = new CompletableFuture<>();
|
||||
server.setTestListener(new LanguageServerTestListener() {
|
||||
getServer().setTestListener(new LanguageServerTestListener() {
|
||||
@Override
|
||||
public void reconcileStarted(String uri, int version) {
|
||||
try {
|
||||
@@ -585,4 +585,8 @@ public class LanguageServerHarness {
|
||||
return newEditor(IOUtil.toString(is));
|
||||
}
|
||||
}
|
||||
|
||||
public S getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user