Merge branch 'master' into async_validations
Conflicts: headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java
@@ -5,7 +5,7 @@ class ManifestYamlLanguageClient extends JarLanguageClient {
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
'https://s3-us-west-1.amazonaws.com/s3-test.spring.io/sts4/fatjars/snapshots/manifest-yaml-language-server-0.0.9-201707201812.jar',
|
||||
'https://s3-us-west-1.amazonaws.com/s3-test.spring.io/sts4/fatjars/snapshots/manifest-yaml-language-server-0.0.9-201707270057.jar',
|
||||
path.join(__dirname, '..', 'server')
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"atom": ">=1.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-languageclient": "0.1.1",
|
||||
"decompress": "^4.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"remote-file-size": "^3.0.3",
|
||||
|
||||
@@ -52,8 +52,8 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
@Override
|
||||
public CompletableFuture<Hover> handle(TextDocumentPositionParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
if (doc != null) {
|
||||
if (documents.get(params) != null) {
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
try {
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
CompletableFuture<Hover> hoverResult = provideHover(doc, offset);
|
||||
@@ -64,7 +64,7 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return SimpleTextDocumentService.NO_HOVER;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
parser.setStatementsRecovery(true);
|
||||
parser.setBindingsRecovery(true);
|
||||
parser.setResolveBindings(true);
|
||||
|
||||
|
||||
String[] classpathEntries = getClasspathEntries(document);
|
||||
String[] sourceEntries = new String[] {};
|
||||
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
|
||||
@@ -89,7 +89,7 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
|
||||
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||
ASTNode node = NodeFinder.perform(cu, offset, 0);
|
||||
|
||||
|
||||
if (node != null) {
|
||||
System.out.println("AST node found: " + node.getClass().getName());
|
||||
return provideHoverForAnnotation(node, offset, document);
|
||||
@@ -101,11 +101,11 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
private CompletableFuture<Hover> provideHoverForAnnotation(ASTNode node, int offset, TextDocument doc) {
|
||||
Annotation annotation = null;
|
||||
ASTNode exactNode = node;
|
||||
|
||||
|
||||
while (node != null && !(node instanceof Annotation)) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
|
||||
if (node != null) {
|
||||
annotation = (Annotation) node;
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
@@ -116,7 +116,7 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
if (type.getQualifiedName().equals(SPRING_VALUE)) {
|
||||
return new ValueHoverProvider().provideHoverForValueAnnotation(node, annotation, type, offset, doc);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
Stream<Path> classpathEntries = classpath.getClasspathEntries();
|
||||
return classpathEntries
|
||||
.filter(path -> path.toFile().exists())
|
||||
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
|
||||
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.time.Duration;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
/**
|
||||
* Provides access to configuration options that allow user to
|
||||
* change the way bosh CLI commands are executed by the
|
||||
* bosh language server.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class BoshCliConfig {
|
||||
|
||||
/**
|
||||
* The settings object. This is obtained from 'didChangeConfiguration' events.
|
||||
*/
|
||||
private Settings settings = new Settings(null);
|
||||
|
||||
public String getCommand() {
|
||||
return (String) settings.getProperty("bosh", "cli", "command");
|
||||
}
|
||||
public String getTarget() {
|
||||
return (String) settings.getProperty("bosh", "cli", "target");
|
||||
}
|
||||
public Duration getTimeout() {
|
||||
Integer seconds = (Integer) settings.getProperty("cli", "timeout");
|
||||
return seconds == null ? Duration.ofSeconds(3) : Duration.ofSeconds(seconds);
|
||||
}
|
||||
|
||||
public void handleConfigurationChange(Settings newConfig) {
|
||||
Log.info("BoshCliConfig changed: "+newConfig);
|
||||
this.settings = newConfig;
|
||||
}
|
||||
}
|
||||
@@ -20,22 +20,26 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.springframework.ide.vscode.bosh.models.CachingModelProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.CloudConfigModel;
|
||||
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.ReleaseData;
|
||||
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.ValueParsers;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
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;
|
||||
@@ -96,24 +100,27 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
private final ASTTypeCache astTypes;
|
||||
private DynamicModelProvider<CloudConfigModel> cloudConfigProvider;
|
||||
private DynamicModelProvider<StemcellsModel> stemcellsProvider;
|
||||
private DynamicModelProvider<ReleasesModel> releasesProvider;
|
||||
private List<Pair<YType, YType>> defAndRefTypes;
|
||||
|
||||
public BoshDeploymentManifestSchema(
|
||||
YamlAstCache asts, ASTTypeCache astTypes,
|
||||
DynamicModelProvider<CloudConfigModel> cloudConfigProvider,
|
||||
DynamicModelProvider<StemcellsModel> stemcellsProvider
|
||||
DynamicModelProvider<StemcellsModel> stemcellsProvider,
|
||||
DynamicModelProvider<ReleasesModel> releasesProvider
|
||||
) {
|
||||
this.asts = asts;
|
||||
this.astTypes = astTypes;
|
||||
this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider);
|
||||
this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider);
|
||||
this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider, CloudConfigModel.class);
|
||||
this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider, StemcellsModel.class);
|
||||
this.releasesProvider = new CachingModelProvider<>(releasesProvider, ReleasesModel.class);
|
||||
TYPE_UTIL = f.TYPE_UTIL;
|
||||
|
||||
V2_TOPLEVEL_TYPE = createV2Schema();
|
||||
V1_TOPLEVEL_TYPE = createV1Schema(V2_TOPLEVEL_TYPE);
|
||||
|
||||
TOPLEVEL_TYPE = f.contextAware("DeploymenManifestV1orV2", (dc) -> {
|
||||
boolean looksLikeV1 = dc.getDefinedProperties().stream().anyMatch(DEPRECATED_V1_PROPS::contains);
|
||||
boolean looksLikeV1 = dc.getDefinedProperties().contains("networks");
|
||||
return looksLikeV1 ? V1_TOPLEVEL_TYPE : V2_TOPLEVEL_TYPE;
|
||||
});
|
||||
}
|
||||
@@ -146,10 +153,12 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
|
||||
t_stemcell_alias_def = f.yatomic("StemcellAlias")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
t_stemcell_alias_ref = f.yenumFromDynamicValues("StemcellAlias", (dc) -> astTypes.getDefinedNames(dc, t_stemcell_alias_def));
|
||||
t_release_name_def = f.yatomic("ReleaseName")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
t_release_name_ref = f.yenumFromDynamicValues("ReleaseName", (dc) -> astTypes.getDefinedNames(dc, t_release_name_def));
|
||||
t_stemcell_alias_ref = f.yenumFromDynamicValues("StemcellAlias", (dc) -> PartialCollection.compute(() -> astTypes.getDefinedNames(dc, t_stemcell_alias_def)));
|
||||
t_release_name_def = f.yenumFromDynamicValues("ReleaseName", (dc) -> {
|
||||
PartialCollection<String> releaseNames = PartialCollection.compute(() -> releasesProvider.getModel(dc).getReleaseNames());
|
||||
return StringUtil.hasText(getCurrentEntityProperty(dc, "url")) ? releaseNames.addUncertainty() : releaseNames;
|
||||
});
|
||||
t_release_name_ref = f.yenumFromDynamicValues("ReleaseName", (dc) -> PartialCollection.compute(() -> astTypes.getDefinedNames(dc, t_release_name_def)));
|
||||
|
||||
t_var_name_def = f.yatomic("VariableName")
|
||||
.parseWith(ValueParsers.NE_STRING);
|
||||
@@ -161,12 +170,16 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
YAtomicType t_url = f.yatomic("URL");
|
||||
t_url.parseWith(BoshValueParsers.url("http", "https", "file"));
|
||||
|
||||
YAtomicType t_network_name = f.yenumFromDynamicValues("NetworkName", (dc) -> cloudConfigProvider.getModel(dc).getNetworkNames());
|
||||
YAtomicType t_disk_type = f.yenumFromDynamicValues("DiskType", (dc) -> cloudConfigProvider.getModel(dc).getDiskTypes());
|
||||
YAtomicType t_vm_extension = f.yenumFromDynamicValues("VMExtension", (dc) -> cloudConfigProvider.getModel(dc).getVMExtensions());
|
||||
YAtomicType t_vm_type = f.yenumFromDynamicValues("VMType", (dc) -> cloudConfigProvider.getModel(dc).getVMTypes());
|
||||
|
||||
YAtomicType t_az = f.yenumFromDynamicValues("AvailabilityZone", (dc) -> cloudConfigProvider.getModel(dc).getAvailabilityZones());
|
||||
YAtomicType t_network_name = f.yenumFromDynamicValues("NetworkName",
|
||||
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getNetworkNames()));
|
||||
YAtomicType t_disk_type = f.yenumFromDynamicValues("DiskType",
|
||||
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getDiskTypes()));
|
||||
YAtomicType t_vm_extension = f.yenumFromDynamicValues("VMExtension",
|
||||
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getVMExtensions()));
|
||||
YAtomicType t_vm_type = f.yenumFromDynamicValues("VMType",
|
||||
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getVMTypes()));
|
||||
YAtomicType t_az = f.yenumFromDynamicValues("AvailabilityZone",
|
||||
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getAvailabilityZones()));
|
||||
|
||||
YBeanType t_network = f.ybean("Network");
|
||||
addProp(t_network, "name", t_network_name).isRequired(true);
|
||||
@@ -177,59 +190,66 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
|
||||
addProp(t_instance_group_env, "bosh", t_params);
|
||||
addProp(t_instance_group_env, "password", t_ne_string);
|
||||
|
||||
YAtomicType t_version = f.yatomic("Version");
|
||||
t_version.addHints("latest");
|
||||
t_version.parseWith(ValueParsers.NE_STRING);
|
||||
YType t_release_version = f.yenumFromDynamicValues("ReleaseVersion",
|
||||
//message formatter:
|
||||
dc -> (s, values) -> {
|
||||
String name = getCurrentEntityProperty(dc, "name");
|
||||
if (StringUtil.hasText(name)) {
|
||||
return "'"+s+"' is an unknown 'ReleaseVersion[name="+name+"]'. Valid values are: "+values;
|
||||
} else {
|
||||
return "'"+s+"' is an unknown 'ReleaseVersion'. Valid values are: "+values;
|
||||
}
|
||||
},
|
||||
//value provider:
|
||||
dc -> {
|
||||
PartialCollection<ReleaseData> releases = PartialCollection.compute(() -> releasesProvider.getModel(dc).getReleases());
|
||||
if (StringUtil.hasText(getCurrentEntityProperty(dc, "url"))) {
|
||||
releases = releases.addUncertainty();
|
||||
} else {
|
||||
String name = getCurrentEntityProperty(dc, "name");
|
||||
if (StringUtil.hasText(name)) {
|
||||
releases = releases.map(r -> name.equals(r.getName()) ? r : null);
|
||||
}
|
||||
}
|
||||
return releases.map(r -> r.getVersion()).add("latest");
|
||||
}
|
||||
);
|
||||
|
||||
YBeanType t_release = f.ybean("Release");
|
||||
addProp(t_release, "name", t_release_name_def).isPrimary(true);
|
||||
addProp(t_release, "version", t_version);
|
||||
//TODO: the checking here is just 'my best guess'. Unclarity remains:
|
||||
// See: https://github.com/cloudfoundry/docs-bosh/issues/330
|
||||
addProp(t_release, "version", t_release_version).isRequired(true);
|
||||
addProp(t_release, "url", t_url);
|
||||
addProp(t_release, "sha1", t_ne_string);
|
||||
addProp(v2Schema, "releases", f.yseq(t_release)).isRequired(true);
|
||||
t_release.require(Constraints.requireAtLeastOneOf("url", "version"));
|
||||
// ^^^^^^^ allthough docs seem to imply you shouldn't
|
||||
// define both url and version it seems that bosh tolerates it.
|
||||
t_release.require(BoshConstraints.SHA1_REQUIRED_FOR_HTTP_URL);
|
||||
addProp(v2Schema, "releases", f.yseq(t_release)).isRequired(true);
|
||||
|
||||
YBeanType t_stemcell = f.ybean("Stemcell");
|
||||
|
||||
YType t_stemcell_name_ref = f.yenumFromDynamicValues("StemcellName", (dc) ->
|
||||
stemcellsProvider.getModel(dc).getStemcellNames()
|
||||
PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getStemcellNames())
|
||||
);
|
||||
YType t_stemcell_os_ref = f.yenumFromDynamicValues("StemcellOs", (dc) ->
|
||||
stemcellsProvider.getModel(dc).getStemcellOss()
|
||||
PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getStemcellOss())
|
||||
);
|
||||
YType t_stemcell_version_ref = f.contextAware("StemcellVersion", new SchemaContextAware<YType>() {
|
||||
|
||||
YAtomicType baseType = f.yenumFromDynamicValues("StemcellVersion", (dc) -> stemcellsProvider.getModel(dc).getVersions());
|
||||
{
|
||||
baseType.addHints("latest");
|
||||
baseType.alsoAccept("latest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public YType withContext(DynamicSchemaContext dc) throws Exception {
|
||||
StemcellModel currentStemcell = getCurrentStemcell(dc);
|
||||
if (StringUtil.hasText(currentStemcell.getName())||StringUtil.hasText(currentStemcell.getOs())) {
|
||||
Predicate<StemcellData> filter = currentStemcell.createVersionFilter();
|
||||
YAtomicType filteredType = f.yenumFromDynamicValues("StemcellVersion["+filter+"]", (_dc) -> {
|
||||
//Note: it doesn't really matter whether we use _dc or dc in code below as they should be the same.
|
||||
return stemcellsProvider.getModel(dc).getStemcells().stream()
|
||||
.filter(sc -> StringUtil.hasText(sc.getVersion()))
|
||||
.filter(currentStemcell.createVersionFilter())
|
||||
.map(sc -> sc.getVersion())
|
||||
.collect(CollectorUtil.toImmutableSet());
|
||||
});
|
||||
filteredType.addHints("latest");
|
||||
filteredType.alsoAccept("latest");
|
||||
return filteredType;
|
||||
YType t_stemcell_version_ref = f.yenumFromDynamicValues("StemcellVersion",
|
||||
(dc) -> (parseString, validValues) -> {
|
||||
try {
|
||||
Predicate<StemcellData> filter = getCurrentStemcell(dc).createVersionFilter();
|
||||
if (filter!=StemcellModel.ALLWAYS_TRUE_FILTER) {
|
||||
return "'"+parseString+"' is an unknown 'StemcellVersion["+filter+"]'. Valid values are: "+validValues;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignore (parse error most likely)
|
||||
}
|
||||
return baseType;
|
||||
return "'"+parseString+"' is an unknown 'StemcellVersion'. Valid values are: "+validValues;
|
||||
},
|
||||
(dc) -> {
|
||||
Predicate<StemcellData> filter = getCurrentStemcell(dc).createVersionFilter();
|
||||
return PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getStemcells())
|
||||
.map(sc -> filter.test(sc) ? sc.getVersion() : null)
|
||||
.add("latest");
|
||||
}
|
||||
}).treatAsAtomic();
|
||||
);
|
||||
|
||||
addProp(t_stemcell, "alias", t_stemcell_alias_def).isRequired(true);
|
||||
addProp(t_stemcell, "version", t_stemcell_version_ref).isRequired(true);
|
||||
@@ -306,6 +326,15 @@ 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);
|
||||
if (ast!=null) {
|
||||
return NodeUtil.asScalar(path.dropLast().thenValAt(propName).traverseToNode(ast));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YType getTopLevelType() {
|
||||
return TOPLEVEL_TYPE;
|
||||
|
||||
@@ -12,14 +12,18 @@ package org.springframework.ide.vscode.bosh;
|
||||
|
||||
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;
|
||||
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.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.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
|
||||
@@ -37,17 +41,23 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvid
|
||||
public class BoshLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
private final VscodeCompletionEngineAdapter completionEngine;
|
||||
private BoshDeploymentManifestSchema schema;
|
||||
|
||||
public BoshLanguageServer(DynamicModelProvider<CloudConfigModel> cloudConfigProvider, DynamicModelProvider<StemcellsModel> stemcellsProvider) {
|
||||
public BoshLanguageServer(BoshCliConfig cliConfig,
|
||||
DynamicModelProvider<CloudConfigModel> cloudConfigProvider,
|
||||
DynamicModelProvider<StemcellsModel> stemcellsProvider,
|
||||
DynamicModelProvider<ReleasesModel> releasesProvider
|
||||
) {
|
||||
super("vscode-bosh");
|
||||
YamlAstCache asts = new YamlAstCache();
|
||||
SimpleTextDocumentService documents = getTextDocumentService();
|
||||
|
||||
ASTTypeCache astTypeCache = new ASTTypeCache();
|
||||
BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(asts, astTypeCache, cloudConfigProvider, stemcellsProvider);
|
||||
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);
|
||||
@@ -64,6 +74,11 @@ public class BoshLanguageServer extends SimpleLanguageServer {
|
||||
documents.onCompletionResolve(completionEngine::resolveCompletion);
|
||||
documents.onHover(hoverEngine ::getHover);
|
||||
documents.onDefinition(new BoshDefintionFinder(this, schema, asts, astTypeCache));
|
||||
|
||||
SimpleWorkspaceService workspace = getWorkspaceService();
|
||||
workspace.onDidChangeConfiguraton((Settings settings) -> {
|
||||
cliConfig.handleConfigurationChange(settings);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateOnDocumentChange(IReconcileEngine engine, TextDocument doc) {
|
||||
@@ -74,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;
|
||||
|
||||
@@ -13,14 +13,18 @@ package org.springframework.ide.vscode.bosh;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandReleasesProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandStemcellsProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
LaunguageServerApp.start(() -> new BoshLanguageServer(
|
||||
new BoshCommandCloudConfigProvider(),
|
||||
new BoshCommandStemcellsProvider()
|
||||
cliConfig,
|
||||
new BoshCommandCloudConfigProvider(cliConfig),
|
||||
new BoshCommandStemcellsProvider(cliConfig),
|
||||
new BoshCommandReleasesProvider(cliConfig)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,24 @@
|
||||
package org.springframework.ide.vscode.bosh.models;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalCommand;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalProcess;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
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.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
@@ -37,9 +45,10 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
|
||||
|
||||
private final YamlParser yamlParser;
|
||||
protected final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
protected Duration CMD_TIMEOUT = Duration.ofSeconds(3);
|
||||
private final BoshCliConfig config;
|
||||
|
||||
protected BoshCommandBasedModelProvider() {
|
||||
protected BoshCommandBasedModelProvider(BoshCliConfig config) {
|
||||
this.config = config;
|
||||
Representer representer = new Representer();
|
||||
representer.getPropertyUtils().setSkipMissingProperties(true);
|
||||
yamlParser = new YamlParser(new Yaml());
|
||||
@@ -69,6 +78,24 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
|
||||
return blocks[0];
|
||||
}
|
||||
|
||||
protected final ExternalCommand getCommand() {
|
||||
List<String> commandAndArgs = new ArrayList<>();
|
||||
String command = config.getCommand();
|
||||
if (command==null) {
|
||||
return null;
|
||||
}
|
||||
commandAndArgs.add(command);
|
||||
String target = config.getTarget();
|
||||
if (target!=null) {
|
||||
commandAndArgs.add("-e");
|
||||
commandAndArgs.add(target);
|
||||
}
|
||||
for (String s : getBoshCommand()) {
|
||||
commandAndArgs.add(s);
|
||||
}
|
||||
return new ExternalCommand(commandAndArgs.toArray(new String[commandAndArgs.size()]));
|
||||
}
|
||||
|
||||
protected JsonNode getJsonTree() throws Exception {
|
||||
String out = executeCommand(getCommand());
|
||||
return mapper.readTree(out);
|
||||
@@ -76,8 +103,11 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
|
||||
|
||||
protected String executeCommand(ExternalCommand command) throws Exception {
|
||||
Log.info("executing cmd: "+command);
|
||||
if (command==null) {
|
||||
throw new IOException("bosh cli based editor features are disabled");
|
||||
}
|
||||
try {
|
||||
ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, CMD_TIMEOUT);
|
||||
ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, config.getTimeout());
|
||||
Log.info("executing cmd SUCCESS: "+process);
|
||||
String out = process.getOut();
|
||||
return out;
|
||||
@@ -91,7 +121,7 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
|
||||
return new File(".").getAbsoluteFile();
|
||||
}
|
||||
|
||||
protected abstract ExternalCommand getCommand();
|
||||
protected abstract String[] getBoshCommand();
|
||||
|
||||
protected YamlFileAST parseYaml(String block) throws Exception {
|
||||
TextDocument doc = new TextDocument(null, LanguageId.BOSH_CLOUD_CONFIG);
|
||||
@@ -99,4 +129,17 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
|
||||
YamlFileAST ast = yamlParser.getAST(doc);
|
||||
return ast;
|
||||
}
|
||||
|
||||
protected Collection<String> getNames(JSONCursor _cursor, YamlTraversal path) {
|
||||
return path.traverseAmbiguously(_cursor)
|
||||
.flatMap((cursor) -> {
|
||||
String text = cursor.target.asText();
|
||||
if (StringUtil.hasText(text)) {
|
||||
return Stream.of(text);
|
||||
} else {
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(CollectorUtil.toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalCommand;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
@@ -31,7 +32,8 @@ import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
*/
|
||||
public class BoshCommandCloudConfigProvider extends BoshCommandBasedModelProvider<CloudConfigModel> {
|
||||
|
||||
public BoshCommandCloudConfigProvider() {
|
||||
public BoshCommandCloudConfigProvider(BoshCliConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
private static final YamlTraversal VM_TYPE_NAMES = YamlPath.EMPTY
|
||||
@@ -103,18 +105,9 @@ public class BoshCommandCloudConfigProvider extends BoshCommandBasedModelProvide
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure how long we wait for the command to fetch cloud config before
|
||||
* raising timeout exception. (The command may block for long amounts of time
|
||||
* of the director is unreachable on the network).
|
||||
*/
|
||||
public void setCommandTimeout(Duration duration) {
|
||||
this.CMD_TIMEOUT = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExternalCommand getCommand() {
|
||||
return new ExternalCommand("bosh", "cloud-config", "--json");
|
||||
protected String[] getBoshCommand() {
|
||||
return new String[] {"cloud-config", "--json"};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* 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.models;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
|
||||
public class BoshCommandReleasesProvider extends BoshCommandBasedModelProvider<ReleasesModel> {
|
||||
|
||||
private static final String[] COMMAND = new String[] { "releases", "--json" };
|
||||
|
||||
private static final YamlTraversal RELEASES = YamlPath.EMPTY
|
||||
.thenValAt("Tables")
|
||||
.thenAnyChild()
|
||||
.thenValAt("Rows")
|
||||
.thenAnyChild();
|
||||
|
||||
private static final YamlTraversal RELEASE_NAMES = RELEASES
|
||||
.thenValAt("name");
|
||||
|
||||
private static final YamlTraversal RELEASE_VERSIONS = RELEASES
|
||||
.thenValAt("version");
|
||||
|
||||
public BoshCommandReleasesProvider(BoshCliConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReleasesModel getModel(DynamicSchemaContext dc) throws Exception {
|
||||
JSONCursor cursor = new JSONCursor(getJsonTree());
|
||||
return new ReleasesModel() {
|
||||
@Override
|
||||
public List<ReleaseData> getReleases() {
|
||||
return RELEASES.traverseAmbiguously(cursor)
|
||||
.map(c -> new ReleaseData(
|
||||
getStringProperty(c, "name"),
|
||||
getStringProperty(c, "version")
|
||||
))
|
||||
.collect(CollectorUtil.toImmutableList());
|
||||
}
|
||||
|
||||
private String getStringProperty(JSONCursor c, String prop) {
|
||||
c = YamlPath.EMPTY.thenValAt(prop).traverse(c);
|
||||
if (c!=null) {
|
||||
return c.target.asText();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getReleaseNames() {
|
||||
return getNames(cursor, RELEASE_NAMES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getVersions() {
|
||||
return getNames(cursor, RELEASE_VERSIONS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getBoshCommand() {
|
||||
return COMMAND;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,11 +11,10 @@
|
||||
package org.springframework.ide.vscode.bosh.models;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalCommand;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
@@ -37,6 +36,10 @@ public class BoshCommandStemcellsProvider extends BoshCommandBasedModelProvider<
|
||||
private static final YamlTraversal STEMCELL_VERSIONS = STEMCELLS
|
||||
.thenValAt("version");
|
||||
|
||||
public BoshCommandStemcellsProvider(BoshCliConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StemcellsModel getModel(DynamicSchemaContext dc) throws Exception {
|
||||
JSONCursor cursor = new JSONCursor(getJsonTree());
|
||||
@@ -44,20 +47,7 @@ public class BoshCommandStemcellsProvider extends BoshCommandBasedModelProvider<
|
||||
|
||||
@Override
|
||||
public Collection<String> getStemcellNames() {
|
||||
return getNames(STEMCELL_NAMES);
|
||||
}
|
||||
|
||||
private Collection<String> getNames(YamlTraversal path) {
|
||||
return path.traverseAmbiguously(cursor)
|
||||
.flatMap((cursor) -> {
|
||||
String text = cursor.target.asText();
|
||||
if (StringUtil.hasText(text)) {
|
||||
return Stream.of(text);
|
||||
} else {
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(CollectorUtil.toImmutableSet());
|
||||
return getNames(cursor, STEMCELL_NAMES);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,19 +71,19 @@ public class BoshCommandStemcellsProvider extends BoshCommandBasedModelProvider<
|
||||
|
||||
@Override
|
||||
public Collection<String> getStemcellOss() {
|
||||
return getNames(STEMCELL_OSS);
|
||||
return getNames(cursor, STEMCELL_OSS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getVersions() {
|
||||
return getNames(STEMCELL_VERSIONS);
|
||||
return getNames(cursor, STEMCELL_VERSIONS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExternalCommand getCommand() {
|
||||
return new ExternalCommand("bosh", "stemcells", "--json");
|
||||
protected String[] getBoshCommand() {
|
||||
return new String[] { "stemcells", "--json" };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.bosh.models;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
@@ -35,9 +36,11 @@ public class CachingModelProvider<T> implements DynamicModelProvider<T> {
|
||||
private Cache<Object, CompletableFuture<T>> cache = createCache();
|
||||
|
||||
private final DynamicModelProvider<T> delegate;
|
||||
private Class<T> modelInterface;
|
||||
|
||||
public CachingModelProvider(DynamicModelProvider<T> delegate) {
|
||||
public CachingModelProvider(DynamicModelProvider<T> delegate, Class<T> modelInterface) {
|
||||
this.delegate = delegate;
|
||||
this.modelInterface = modelInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,15 +75,43 @@ public class CachingModelProvider<T> implements DynamicModelProvider<T> {
|
||||
synchronized (this) {
|
||||
cached = cache.get(key, () -> {
|
||||
try {
|
||||
return CompletableFuture.completedFuture(delegate.getModel(dc));
|
||||
return CompletableFuture.completedFuture(wrapWithCachingProxy(delegate.getModel(dc)));
|
||||
} catch (Throwable e) {
|
||||
CompletableFuture<T> failed = new CompletableFuture<>();
|
||||
failed.completeExceptionally(e);
|
||||
return failed;
|
||||
return failed(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return cached.get();
|
||||
}
|
||||
|
||||
private static <T> CompletableFuture<T> failed(Throwable e) {
|
||||
CompletableFuture<T> failed = new CompletableFuture<>();
|
||||
failed.completeExceptionally(e);
|
||||
return failed;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T wrapWithCachingProxy(T model) {
|
||||
if (model==null) {
|
||||
//Special case for 'no model' we'll create a model that always returns null
|
||||
return (T) Proxy.newProxyInstance(modelInterface.getClassLoader(), new Class[] {modelInterface}, (o, m, a) -> {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
Cache<String, CompletableFuture<Object>> attributesCache = CacheBuilder.newBuilder().build();
|
||||
return (T) Proxy.newProxyInstance(modelInterface.getClassLoader(), new Class[] {modelInterface}, (o, m, a) -> {
|
||||
//We only support caching results for methods that have no arguments (for now, its all we need).
|
||||
if (m.getParameterTypes().length==0) {
|
||||
return attributesCache.get(m.getName(), () -> {
|
||||
try {
|
||||
return CompletableFuture.completedFuture((T)m.invoke(model, a));
|
||||
} catch (Throwable e) {
|
||||
return failed(e);
|
||||
}
|
||||
}).get();
|
||||
}
|
||||
return m.invoke(model, a);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* 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.models;
|
||||
|
||||
public class ReleaseData {
|
||||
|
||||
private String name;
|
||||
private String version;
|
||||
|
||||
public ReleaseData(String name, String version) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public ReleaseData() {}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((version == null) ? 0 : version.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;
|
||||
ReleaseData other = (ReleaseData) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (version == null) {
|
||||
if (other.version != null)
|
||||
return false;
|
||||
} else if (!version.equals(other.version))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReleaseData [name=" + name + ", version=" + version + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* 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.models;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface ReleasesModel {
|
||||
List<ReleaseData> getReleases();
|
||||
Collection<String> getReleaseNames();
|
||||
Collection<String> getVersions();
|
||||
}
|
||||
@@ -21,6 +21,16 @@ import org.yaml.snakeyaml.nodes.Node;
|
||||
*/
|
||||
public class StemcellModel {
|
||||
|
||||
public static final Predicate<StemcellData> ALLWAYS_TRUE_FILTER = new Predicate<StemcellData>() {
|
||||
@Override
|
||||
public boolean test(StemcellData sc) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "true";
|
||||
}
|
||||
};
|
||||
private Node node;
|
||||
|
||||
public StemcellModel(Node node) {
|
||||
@@ -79,16 +89,7 @@ public class StemcellModel {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new Predicate<StemcellData>() {
|
||||
@Override
|
||||
public boolean test(StemcellData sc) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "true";
|
||||
}
|
||||
};
|
||||
return ALLWAYS_TRUE_FILTER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*******************************************************************************
|
||||
* 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.CollectorUtil;
|
||||
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(CollectorUtil.toImmutableList());
|
||||
if (!requiredProps.isEmpty()) {
|
||||
generateBeanSnippet(requiredProps, builder, indent, maxNesting);
|
||||
}
|
||||
if (builder.getPlaceholderCount()>=2) {
|
||||
return new Snippet(typeUtil.niceTypeName(type)+" Snippet", builder.toString(), (dc) ->
|
||||
requiredProps.stream().noneMatch(p -> dc.getDefinedProperties().contains(p.getName()))
|
||||
);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
The SHA1 of the release tarball. SHA1 is only required when using HTTP(s) URLs.
|
||||
The SHA1 of the release tarball. SHA1 is recommended when using HTTP(s) URLs.
|
||||
@@ -11,50 +11,55 @@
|
||||
package org.springframework.ide.vscode.bosh;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.Editor.*;
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.management.modelmbean.ModelMBeanAttributeInfo;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.DiagnosticSeverity;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandReleasesProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandStemcellsProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.ReleaseData;
|
||||
import org.springframework.ide.vscode.bosh.models.ReleasesModel;
|
||||
import org.springframework.ide.vscode.bosh.models.StemcellData;
|
||||
import org.springframework.ide.vscode.bosh.models.StemcellsModel;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalCommand;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public class BoshEditorTest {
|
||||
|
||||
LanguageServerHarness harness;
|
||||
LanguageServerHarness<BoshLanguageServer> harness;
|
||||
|
||||
private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider();
|
||||
private DynamicModelProvider<StemcellsModel> stemcellsProvider = Mockito.mock(DynamicModelProvider.class);
|
||||
private BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider(cliConfig);
|
||||
private DynamicModelProvider<StemcellsModel> stemcellsProvider = mock(DynamicModelProvider.class);
|
||||
private DynamicModelProvider<ReleasesModel> releasesProvider = mock(DynamicModelProvider.class);
|
||||
|
||||
@Before public void setup() throws Exception {
|
||||
harness = new LanguageServerHarness(() -> {
|
||||
return new BoshLanguageServer(cloudConfigProvider, (dc) -> stemcellsProvider.getModel(dc))
|
||||
.setMaxCompletions(100);
|
||||
harness = new LanguageServerHarness<BoshLanguageServer>(() -> {
|
||||
return new BoshLanguageServer(cliConfig, cloudConfigProvider,
|
||||
(dc) -> stemcellsProvider.getModel(dc),
|
||||
(dc) -> releasesProvider.getModel(dc)
|
||||
)
|
||||
.setMaxCompletions(100);
|
||||
},
|
||||
LanguageId.BOSH_DEPLOYMENT
|
||||
);
|
||||
@@ -152,6 +157,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void toplevelPropertyCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"<*>"
|
||||
);
|
||||
@@ -196,6 +202,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void stemcellCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"stemcells:\n" +
|
||||
"- <*>"
|
||||
@@ -249,6 +256,7 @@ public class BoshEditorTest {
|
||||
|
||||
|
||||
@Test public void releasesBlockCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- <*>"
|
||||
@@ -285,12 +293,15 @@ public class BoshEditorTest {
|
||||
"- name: some-release\n" +
|
||||
" url: https://my.releases.com/funky.tar.gz\n" +
|
||||
"- name: other-relase\n" +
|
||||
" version: other-version\n" +
|
||||
" url: file:///root/releases/a-nice-file.tar.gz\n" +
|
||||
"- name: bad-url\n" +
|
||||
" version: more-version\n" +
|
||||
" url: proto://something.com\n" +
|
||||
"#x"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"^-^ name: some-release|'version' is required",
|
||||
"url|'sha1' is recommended when the 'url' is http(s)",
|
||||
"proto|Url scheme must be one of [http, https, file]",
|
||||
"x|are required"
|
||||
@@ -328,6 +339,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void instanceGroupsCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"instance_groups:\n" +
|
||||
"- <*>"
|
||||
@@ -511,6 +523,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void updateBlockCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"update:\n" +
|
||||
" <*>"
|
||||
@@ -550,6 +563,7 @@ public class BoshEditorTest {
|
||||
}
|
||||
|
||||
@Test public void variablesBlockCompletions() throws Exception {
|
||||
harness.getServer().enableSnippets(false);
|
||||
Editor editor = harness.newEditor(
|
||||
"variables:\n" +
|
||||
"- <*>"
|
||||
@@ -954,6 +968,25 @@ public class BoshEditorTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void contentAssistStemcellNameNoDirector() throws Exception {
|
||||
Editor editor;
|
||||
stemcellsProvider = mock(DynamicModelProvider.class);
|
||||
when(stemcellsProvider.getModel(any())).thenThrow(new IOException("Couldn't connect to bosh"));
|
||||
editor = harness.newEditor(
|
||||
"stemcells:\n" +
|
||||
"- alias: good\n" +
|
||||
" name: <*>\n"
|
||||
);
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
assertEquals(1, completions.size());
|
||||
CompletionItem c = completions.get(0);
|
||||
c = harness.resolveCompletionItem(c);
|
||||
assertContains("Couldn't connect to bosh", c.getDocumentation());
|
||||
System.out.println("label = " + c.getLabel());
|
||||
System.out.println("detail = " + c.getDetail());
|
||||
System.out.println("doc = " + c.getDocumentation());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test public void contentAssistStemcellVersionNoDirector() throws Exception {
|
||||
Editor editor;
|
||||
@@ -1094,13 +1127,182 @@ public class BoshEditorTest {
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems(
|
||||
"123.4|unknown 'StemcellVersion[name=centos-agent]'. Valid values are: [222.2, 333.3]",
|
||||
"333.3|unknown 'StemcellVersion[os=ubuntu]'. Valid values are: [123.4, 222.2]"
|
||||
"123.4|unknown 'StemcellVersion[name=centos-agent]'. Valid values are: [222.2, 333.3, latest]",
|
||||
"333.3|unknown 'StemcellVersion[os=ubuntu]'. Valid values are: [123.4, 222.2, latest]"
|
||||
);
|
||||
}
|
||||
|
||||
private DynamicModelProvider<StemcellsModel> provideStemcellsFrom(StemcellData... stemcellData) {
|
||||
return new BoshCommandStemcellsProvider() {
|
||||
return new BoshCommandStemcellsProvider(cliConfig) {
|
||||
@Override
|
||||
protected String executeCommand(ExternalCommand command) throws Exception {
|
||||
String rows = mapper.writeValueAsString(stemcellData);
|
||||
return "{\n" +
|
||||
" \"Tables\": [\n" +
|
||||
" {\n" +
|
||||
" \"Rows\": " + rows +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test public void contentAssistReleaseNameDef() throws Exception {
|
||||
releasesProvider = provideReleasesFrom(
|
||||
new ReleaseData("foo", "123.4"),
|
||||
new ReleaseData("foo", "222.2"),
|
||||
new ReleaseData("bar", "222.2"),
|
||||
new ReleaseData("bar", "333.3")
|
||||
);
|
||||
|
||||
Editor editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: <*>"
|
||||
);
|
||||
editor.assertContextualCompletions("<*>",
|
||||
"bar<*>",
|
||||
"foo<*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void reconcileReleaseNameDef() throws Exception {
|
||||
releasesProvider = provideReleasesFrom(
|
||||
new ReleaseData("foo", "123.4"),
|
||||
new ReleaseData("foo", "222.2"),
|
||||
new ReleaseData("bar", "222.2"),
|
||||
new ReleaseData("bar", "333.3")
|
||||
);
|
||||
|
||||
Editor editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: foo\n" +
|
||||
"- name: bar\n" +
|
||||
"- name: bogus\n" +
|
||||
"- name: url-makes-this-ok\n" +
|
||||
" url: file://blah"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems("bogus|unknown 'ReleaseName'. Valid values are: [foo, bar]");
|
||||
}
|
||||
|
||||
@Test public void contentAssistReleaseVersion() throws Exception {
|
||||
releasesProvider = provideReleasesFrom(
|
||||
new ReleaseData("foo", "123.4"),
|
||||
new ReleaseData("foo", "222.2"),
|
||||
new ReleaseData("bar", "222.2"),
|
||||
new ReleaseData("bar", "333.3")
|
||||
);
|
||||
|
||||
Editor editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- version: <*>"
|
||||
);
|
||||
editor.assertContextualCompletions("<*>",
|
||||
"123.4<*>",
|
||||
"222.2<*>",
|
||||
"333.3<*>",
|
||||
"latest<*>"
|
||||
);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: foo\n" +
|
||||
" version: <*>"
|
||||
);
|
||||
editor.assertContextualCompletions("<*>",
|
||||
"123.4<*>",
|
||||
"222.2<*>",
|
||||
"latest<*>"
|
||||
);
|
||||
|
||||
//Still get all suggestions even when 'url' property is added
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- version: <*>\n" +
|
||||
" url: blah"
|
||||
);
|
||||
editor.assertContextualCompletions("<*>",
|
||||
"123.4<*>",
|
||||
"222.2<*>",
|
||||
"333.3<*>",
|
||||
"latest<*>"
|
||||
);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: foo\n" +
|
||||
" url: blah\n" +
|
||||
" version: <*>"
|
||||
);
|
||||
editor.assertContextualCompletions("<*>",
|
||||
"123.4<*>",
|
||||
"222.2<*>",
|
||||
"333.3<*>",
|
||||
"latest<*>"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test public void reconcileReleaseVersion() throws Exception {
|
||||
Editor editor;
|
||||
releasesProvider = provideReleasesFrom(
|
||||
new ReleaseData("foo", "123.4"),
|
||||
new ReleaseData("foo", "222.2"),
|
||||
new ReleaseData("bar", "222.2"),
|
||||
new ReleaseData("bar", "333.3")
|
||||
);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- version: bogus\n" +
|
||||
"- version: url-makes-this-possibly-correct\n" +
|
||||
" url: file:///relesease-folder/blah-release.tar.gz\n"+
|
||||
"- name: bar\n" +
|
||||
" version: url-makes-this-also-possibly-correct\n" +
|
||||
" url: file:///relesease-folder/other-release.tar.gz"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems("bogus|unknown 'ReleaseVersion'. Valid values are: [123.4, 222.2, 333.3, latest]");
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- version: 123.4\n" +
|
||||
"- version: latest\n" +
|
||||
"- version: bogus\n"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems("bogus|unknown 'ReleaseVersion'. Valid values are: [123.4, 222.2, 333.3, latest]");
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: foo\n" +
|
||||
" version: 123.4\n"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: foo\n" +
|
||||
" version: 222.2\n"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: foo\n" +
|
||||
" version: 333.3\n"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
editor.assertProblems("333.3|unknown 'ReleaseVersion[name=foo]'. Valid values are: [123.4, 222.2, latest]");
|
||||
|
||||
}
|
||||
|
||||
|
||||
private DynamicModelProvider<ReleasesModel> provideReleasesFrom(ReleaseData... stemcellData) {
|
||||
return new BoshCommandReleasesProvider(cliConfig) {
|
||||
@Override
|
||||
protected String executeCommand(ExternalCommand command) throws Exception {
|
||||
String rows = mapper.writeValueAsString(stemcellData);
|
||||
@@ -1391,4 +1593,176 @@ public class BoshEditorTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void bug_149769913() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"releases:\n" +
|
||||
"- name: learn-bosh\n" +
|
||||
" url: file:///blah\n" +
|
||||
" version:\n" +
|
||||
"- name: blah-blah\n" +
|
||||
" version:"
|
||||
);
|
||||
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
|
||||
|
||||
editor.assertProblems(
|
||||
"version:^^|cannot be blank",
|
||||
"version:^^|cannot be blank"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void snippet_toplevel() throws Exception {
|
||||
Editor editor = harness.newEditor("<*>");
|
||||
editor.assertCompletions(SNIPPET_COMPLETION,
|
||||
"name: $1\n" +
|
||||
"releases:\n" +
|
||||
"- name: $2\n" +
|
||||
" version: $3\n" +
|
||||
"stemcells:\n" +
|
||||
"- alias: $4\n" +
|
||||
" version: $5\n" +
|
||||
"update:\n" +
|
||||
" canaries: $6\n" +
|
||||
" max_in_flight: $7\n" +
|
||||
" canary_watch_time: $8\n" +
|
||||
" update_watch_time: $9\n" +
|
||||
"instance_groups:\n" +
|
||||
"- name: $10\n" +
|
||||
" azs:\n" +
|
||||
" - $11\n" +
|
||||
" instances: $12\n" +
|
||||
" jobs:\n" +
|
||||
" - name: $13\n" +
|
||||
" release: $14\n" +
|
||||
" vm_type: $15\n" +
|
||||
" stemcell: $16\n" +
|
||||
" networks:\n" +
|
||||
" - name: $17<*>"
|
||||
, // ------------------
|
||||
"instance_groups:\n" +
|
||||
"- name: $1\n" +
|
||||
" azs:\n" +
|
||||
" - $2\n" +
|
||||
" instances: $3\n" +
|
||||
" jobs:\n" +
|
||||
" - name: $4\n" +
|
||||
" release: $5\n" +
|
||||
" vm_type: $6\n" +
|
||||
" stemcell: $7\n" +
|
||||
" networks:\n" +
|
||||
" - name: $8<*>"
|
||||
, // ----------------
|
||||
"releases:\n" +
|
||||
"- name: $1\n" +
|
||||
" version: $2<*>"
|
||||
, // ----------------
|
||||
"stemcells:\n" +
|
||||
"- alias: $1\n" +
|
||||
" version: $2<*>"
|
||||
, // ----------------
|
||||
"update:\n" +
|
||||
" canaries: $1\n" +
|
||||
" max_in_flight: $2\n" +
|
||||
" canary_watch_time: $3\n" +
|
||||
" update_watch_time: $4<*>"
|
||||
, // -----------------
|
||||
"variables:\n" +
|
||||
"- name: $1\n" +
|
||||
" type: $2<*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void snippet_disabledWhenPropertiesAlreadyDefined() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"name:\n" +
|
||||
"releases:\n" +
|
||||
"stemcells:\n" +
|
||||
"<*>"
|
||||
);
|
||||
|
||||
editor.assertCompletionLabels(SNIPPET_COMPLETION,
|
||||
//"BoshDeploymentManifest Snippet",
|
||||
"instance_groups Snippet",
|
||||
// "releases Snippet",
|
||||
// "stemcells Snippet"
|
||||
"update Snippet",
|
||||
"variables Snippet",
|
||||
"- Stemcell Snippet"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void snippet_nested_plain() throws Exception {
|
||||
Editor editor;
|
||||
//Plain exact completion
|
||||
editor = harness.newEditor(
|
||||
"instance_groups:\n" +
|
||||
"- name: blah\n" +
|
||||
" <*>"
|
||||
);
|
||||
editor.assertContextualCompletions(LanguageId.BOSH_DEPLOYMENT, c -> c.getLabel().equals("jobs Snippet"),
|
||||
"jo<*>"
|
||||
, // ------
|
||||
"jobs:\n" +
|
||||
" - name: $1\n" +
|
||||
" release: $2<*>"
|
||||
);
|
||||
editor.assertCompletionWithLabel("jobs Snippet",
|
||||
"instance_groups:\n" +
|
||||
"- name: blah\n" +
|
||||
" jobs:\n" +
|
||||
" - name: $1\n" +
|
||||
" release: $2<*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void snippet_nested_indenting() throws Exception {
|
||||
Editor editor;
|
||||
//With extra indent:
|
||||
editor = harness.newEditor(
|
||||
"instance_groups:\n" +
|
||||
"- name: blah\n" +
|
||||
"<*>"
|
||||
);
|
||||
editor.assertCompletionWithLabel("→ jobs Snippet",
|
||||
"instance_groups:\n" +
|
||||
"- name: blah\n" +
|
||||
" jobs:\n" +
|
||||
" - name: $1\n" +
|
||||
" release: $2<*>"
|
||||
);
|
||||
editor.assertContextualCompletions(LanguageId.BOSH_DEPLOYMENT, c -> c.getLabel().equals("→ jobs Snippet"),
|
||||
"jo<*>"
|
||||
, // ------
|
||||
" jobs:\n" +
|
||||
" - name: $1\n" +
|
||||
" release: $2<*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void relaxedCAmoreSpaces() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"name: foo\n" +
|
||||
"instance_groups:\n" +
|
||||
"- name: \n" +
|
||||
"<*>"
|
||||
);
|
||||
editor.assertContextualCompletions(LanguageId.BOSH_DEPLOYMENT, c -> c.getLabel().equals("→ jobs"),
|
||||
"jo<*>"
|
||||
, // ==>
|
||||
" jobs:\n" +
|
||||
" - <*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test @Ignore public void keyCompletionThatNeedANewline() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"name: foo\n" +
|
||||
"update: canwa<*>"
|
||||
);
|
||||
editor.assertCompletions(
|
||||
"name: foo\n" +
|
||||
"update: \n" +
|
||||
" canary_watch_time: <*>"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.bosh;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -19,21 +20,23 @@ import java.nio.file.Paths;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncKind;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BoshLanguageServerTest {
|
||||
|
||||
public static File getTestResource(String name) throws URISyntaxException {
|
||||
return Paths.get(BoshLanguageServerTest.class.getResource(name).toURI()).toFile();
|
||||
}
|
||||
|
||||
private BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
|
||||
@Test
|
||||
public void createAndInitializeServerWithWorkspace() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(() ->
|
||||
new BoshLanguageServer(new MockCloudConfigProvider(), Mockito.mock(DynamicModelProvider.class))
|
||||
new BoshLanguageServer(cliConfig, new MockCloudConfigProvider(cliConfig), mock(DynamicModelProvider.class), mock(DynamicModelProvider.class))
|
||||
);
|
||||
File workspaceRoot = getTestResource("/workspace/");
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
@@ -43,7 +46,7 @@ public class BoshLanguageServerTest {
|
||||
public void createAndInitializeServerWithoutWorkspace() throws Exception {
|
||||
File workspaceRoot = null;
|
||||
LanguageServerHarness harness = new LanguageServerHarness(() ->
|
||||
new BoshLanguageServer(new MockCloudConfigProvider(), Mockito.mock(DynamicModelProvider.class))
|
||||
new BoshLanguageServer(cliConfig, new MockCloudConfigProvider(cliConfig), mock(DynamicModelProvider.class), mock(DynamicModelProvider.class))
|
||||
);
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.bosh.mocks;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider;
|
||||
import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProviderTest;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalCommand;
|
||||
@@ -29,6 +30,10 @@ public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvide
|
||||
|
||||
private Callable<String> reader = null;
|
||||
|
||||
public MockCloudConfigProvider(BoshCliConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override with a 'fake' which just returns some mock data. That way we can unit-test
|
||||
* without requiring a real bosh setup.
|
||||
|
||||
@@ -14,6 +14,7 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
|
||||
@@ -21,7 +22,8 @@ import com.google.common.collect.ImmutableMultiset;
|
||||
|
||||
public class BoshCommandCloudConfigProviderTest {
|
||||
|
||||
public final MockCloudConfigProvider provider = new MockCloudConfigProvider();
|
||||
private BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
public final MockCloudConfigProvider provider = new MockCloudConfigProvider(cliConfig);
|
||||
|
||||
// For local testing only... in CI builds we don't have the means to use a real bosh director and cli.
|
||||
// private BoshCommandCloudConfigProvider realProvider = new BoshCommandCloudConfigProvider();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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.models;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.commons.util.IOUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class BoshCommandReleasesProviderTest {
|
||||
|
||||
private static final String MOCK_DATA_RSRC = "/cmd-out/releases.json";
|
||||
private BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
public BoshCommandReleasesProvider provider = Mockito.spy(new BoshCommandReleasesProvider(cliConfig));
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
Mockito.doReturn(IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)))
|
||||
.when(provider).executeCommand(Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReleases() throws Exception {
|
||||
assertEquals(ImmutableList.of(new ReleaseData("learn-bosh", "0+dev.2")),
|
||||
provider.getModel(mock(DynamicSchemaContext.class)).getReleases()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,24 +11,34 @@
|
||||
package org.springframework.ide.vscode.bosh.models;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.ide.vscode.bosh.BoshCliConfig;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
|
||||
import org.springframework.ide.vscode.commons.util.ExternalCommand;
|
||||
import org.springframework.ide.vscode.commons.util.IOUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public class BoshCommandStemcellsProviderTest {
|
||||
|
||||
private static final String MOCK_DATA_RSRC = "/cmd-out/stemcells.json";;
|
||||
public BoshCommandStemcellsProvider provider = Mockito.spy(BoshCommandStemcellsProvider.class);
|
||||
private static final String MOCK_DATA_RSRC = "/cmd-out/stemcells.json";
|
||||
private BoshCliConfig cliConfig = new BoshCliConfig();
|
||||
public BoshCommandStemcellsProvider provider = Mockito.spy(new BoshCommandStemcellsProvider(cliConfig));
|
||||
|
||||
@Before
|
||||
public void settup() throws Exception {
|
||||
public void setup() throws Exception {
|
||||
Mockito.doReturn(IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)))
|
||||
.when(provider).executeCommand(Mockito.any());
|
||||
}
|
||||
@@ -62,5 +72,48 @@ public class BoshCommandStemcellsProviderTest {
|
||||
provider.getModel(mock(DynamicSchemaContext.class)).getVersions());
|
||||
}
|
||||
|
||||
// @Test public void defaultCliConfig() throws Exception {
|
||||
// assertEquals(ImmutableList.of(
|
||||
// new StemcellData("bosh-vsphere-esxi-centos-7-go_agent", "3421.11", "centos-7"),
|
||||
// new StemcellData("bosh-vsphere-esxi-ubuntu-trusty-go_agent", "3421.11", "ubuntu-trusty")
|
||||
// ),
|
||||
// provider.getModel(mock(DynamicSchemaContext.class)).getStemcells()
|
||||
// );
|
||||
// verify(provider).executeCommand(eq(new ExternalCommand("bosh", "stemcells", "--json")));
|
||||
// }
|
||||
|
||||
@Test public void obeysCliConfigCommand() throws Exception {
|
||||
Map<String, Object> settings = ImmutableMap.of("bosh", ImmutableMap.of("cli",
|
||||
ImmutableMap.of(
|
||||
"command", "alternate-command"
|
||||
)
|
||||
));
|
||||
|
||||
cliConfig.handleConfigurationChange(new Settings(settings));
|
||||
assertEquals(ImmutableList.of(
|
||||
new StemcellData("bosh-vsphere-esxi-centos-7-go_agent", "3421.11", "centos-7"),
|
||||
new StemcellData("bosh-vsphere-esxi-ubuntu-trusty-go_agent", "3421.11", "ubuntu-trusty")
|
||||
),
|
||||
provider.getModel(mock(DynamicSchemaContext.class)).getStemcells()
|
||||
);
|
||||
verify(provider).executeCommand(eq(new ExternalCommand("alternate-command", "stemcells", "--json")));
|
||||
}
|
||||
|
||||
@Test public void obeysCliConfigTarget() throws Exception {
|
||||
Map<String, Object> settings = ImmutableMap.of("bosh", ImmutableMap.of("cli",
|
||||
ImmutableMap.of(
|
||||
"command", "alternate-command",
|
||||
"target", "explicit-target"
|
||||
)
|
||||
));
|
||||
cliConfig.handleConfigurationChange(new Settings(settings));
|
||||
assertEquals(ImmutableList.of(
|
||||
new StemcellData("bosh-vsphere-esxi-centos-7-go_agent", "3421.11", "centos-7"),
|
||||
new StemcellData("bosh-vsphere-esxi-ubuntu-trusty-go_agent", "3421.11", "ubuntu-trusty")
|
||||
),
|
||||
provider.getModel(mock(DynamicSchemaContext.class)).getStemcells()
|
||||
);
|
||||
verify(provider).executeCommand(eq(new ExternalCommand("alternate-command", "-e", "explicit-target", "stemcells", "--json")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,9 +12,12 @@ package org.springframework.ide.vscode.bosh.models;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -23,24 +26,31 @@ import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
@SuppressWarnings("unchecked")
|
||||
public class CachingModelProviderTest {
|
||||
|
||||
interface BoxModel {
|
||||
String getContents();
|
||||
}
|
||||
|
||||
@Test public void goodValuesAreCached() throws Exception {
|
||||
DynamicModelProvider<String> modelProvider = mock(DynamicModelProvider.class);
|
||||
when(modelProvider.getModel(any())).thenReturn("RESULT");
|
||||
DynamicModelProvider<BoxModel> modelProvider = mock(DynamicModelProvider.class);
|
||||
BoxModel model = mock(BoxModel.class);
|
||||
when(modelProvider.getModel(any())).thenReturn(model);
|
||||
when(model.getContents()).thenReturn("RESULT");
|
||||
|
||||
DynamicModelProvider<String> cached = new CachingModelProvider<>(modelProvider);
|
||||
DynamicModelProvider<BoxModel> cached = new CachingModelProvider<>(modelProvider, BoxModel.class);
|
||||
|
||||
assertEquals("RESULT", cached.getModel(null));
|
||||
assertEquals("RESULT", cached.getModel(null));
|
||||
assertEquals("RESULT", cached.getModel(null));
|
||||
assertEquals("RESULT", cached.getModel(null).getContents());
|
||||
assertEquals("RESULT", cached.getModel(null).getContents());
|
||||
assertEquals("RESULT", cached.getModel(null).getContents());
|
||||
|
||||
verify(modelProvider, times(1)).getModel(any());
|
||||
verify(model, times(1)).getContents(); //model itself is also wrapped in a cache!
|
||||
}
|
||||
|
||||
@Test public void timeoutExceptionsAreCached() throws Exception {
|
||||
DynamicModelProvider<String> modelProvider = mock(DynamicModelProvider.class);
|
||||
when(modelProvider.getModel(any())).thenThrow(new TimeoutException("timed out"));
|
||||
|
||||
DynamicModelProvider<String> cached = new CachingModelProvider<>(modelProvider);
|
||||
DynamicModelProvider<String> cached = new CachingModelProvider<>(modelProvider, String.class);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
cached.getModel(null);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"Tables": [
|
||||
{
|
||||
"Content": "releases",
|
||||
"Header": {
|
||||
"commit_hash": "Commit Hash",
|
||||
"name": "Name",
|
||||
"version": "Version"
|
||||
},
|
||||
"Rows": [
|
||||
{
|
||||
"commit_hash": "5c612c4+",
|
||||
"name": "learn-bosh",
|
||||
"version": "0+dev.2"
|
||||
}
|
||||
],
|
||||
"Notes": [
|
||||
"(*) Currently deployed",
|
||||
"(+) Uncommitted changes"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Blocks": null,
|
||||
"Lines": [
|
||||
"Using environment '10.194.4.35' as client 'admin'",
|
||||
"Succeeded"
|
||||
]
|
||||
}
|
||||
@@ -47,6 +47,8 @@ public class DocumentEdits implements ProposalApplier {
|
||||
|
||||
private static final Pattern NON_WS_CHAR = Pattern.compile("\\S");
|
||||
|
||||
private boolean isRelativeIndent = false;
|
||||
|
||||
// Note: for small number of edits this implementation is okay.
|
||||
// for large number of edits it is potentially slow because of the
|
||||
// way it transforms edit coordinates (a growing chain of
|
||||
|
||||
@@ -117,8 +117,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
|
||||
private Mono<CompletionList> getCompletionsMono(TextDocumentPositionParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
if (doc!=null) {
|
||||
if (documents.get(params) != null) {
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
return Mono.fromCallable(() -> {
|
||||
if (resolver!=null) {
|
||||
//Assumes we don't have more than one completion request in flight from the client.
|
||||
|
||||
@@ -52,31 +52,33 @@ public class SimpleDefinitionFinder<T extends SimpleLanguageServer> implements D
|
||||
* currently pointed at in the current document using String.indexOf.
|
||||
*/
|
||||
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
|
||||
try {
|
||||
try {
|
||||
TextDocument doc = server.getTextDocumentService().get(params);
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
int start = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(start))) {
|
||||
start--;
|
||||
}
|
||||
start = start+1;
|
||||
int end = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(end))) {
|
||||
end++;
|
||||
}
|
||||
String word = doc.textBetween(start, end);
|
||||
Log.log("Looking for definition of '"+word+"'");
|
||||
String text = doc.get();
|
||||
int def = text.indexOf(word);
|
||||
if (def>=0) {
|
||||
return Flux.just(
|
||||
new Location(params.getTextDocument().getUri(),
|
||||
doc.toRange(def, word.length())
|
||||
if (doc != null) {
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
int start = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(start))) {
|
||||
start--;
|
||||
}
|
||||
start = start+1;
|
||||
int end = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(end))) {
|
||||
end++;
|
||||
}
|
||||
String word = doc.textBetween(start, end);
|
||||
Log.log("Looking for definition of '"+word+"'");
|
||||
String text = doc.get();
|
||||
int def = text.indexOf(word);
|
||||
if (def>=0) {
|
||||
return Flux.just(
|
||||
new Location(params.getTextDocument().getUri(),
|
||||
doc.toRange(def, word.length())
|
||||
)
|
||||
)
|
||||
)
|
||||
.doOnNext((Location loc) -> {
|
||||
Log.log("definition: "+loc);
|
||||
});
|
||||
.doOnNext((Location loc) -> {
|
||||
Log.log("definition: "+loc);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
|
||||
package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -18,9 +17,9 @@ import java.util.Map;
|
||||
* retrieve properties from the settings object.
|
||||
*/
|
||||
public class Settings {
|
||||
|
||||
|
||||
private Object settings;
|
||||
|
||||
|
||||
public Settings(Object settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
@@ -32,7 +31,7 @@ public class Settings {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Object getProperty(String... names) {
|
||||
return getProperty(settings, names, 0);
|
||||
}
|
||||
@@ -49,6 +48,8 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return settings.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ import com.google.common.collect.ImmutableSet;
|
||||
public class EnumValueParser implements ValueParser {
|
||||
|
||||
private String typeName;
|
||||
private Provider<Collection<String>> values;
|
||||
private final boolean longRunning;
|
||||
|
||||
private Provider<PartialCollection<String>> values;
|
||||
private final boolean longRunning;
|
||||
|
||||
|
||||
public EnumValueParser(String typeName, String... values) {
|
||||
this(typeName, ImmutableSet.copyOf(values));
|
||||
@@ -37,16 +39,28 @@ public class EnumValueParser implements ValueParser {
|
||||
this(typeName, false /* not long running by default */, provider(values));
|
||||
}
|
||||
|
||||
private static <T> Provider<PartialCollection<T>> provider(Collection<T> values) {
|
||||
return () -> PartialCollection.compute(() -> values);
|
||||
}
|
||||
|
||||
private static <T> Provider<PartialCollection<T>> provider(Callable<Collection<T>> values) {
|
||||
return () -> PartialCollection.compute(() -> values.call());
|
||||
}
|
||||
|
||||
public EnumValueParser(String typeName, boolean longRunning, Callable<Collection<String>> values) {
|
||||
this(typeName, longRunning, provider(values));
|
||||
}
|
||||
|
||||
public EnumValueParser(String typeName, boolean longRunning, Provider<Collection<String>> values) {
|
||||
public EnumValueParser(String typeName, boolean longRunning, Provider<PartialCollection<String>> values) {
|
||||
this.typeName = typeName;
|
||||
this.values = values;
|
||||
this.longRunning = longRunning;
|
||||
}
|
||||
|
||||
public EnumValueParser(String name, PartialCollection<String> values) {
|
||||
this(name, false /* not long running by default */, () -> values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(String str) throws Exception {
|
||||
// IMPORTANT: check the text FIRST before fetching values
|
||||
@@ -56,13 +70,13 @@ public class EnumValueParser implements ValueParser {
|
||||
throw errorOnBlank(createBlankTextErrorMessage());
|
||||
}
|
||||
|
||||
Collection<String> values = this.values.get();
|
||||
PartialCollection<String> values = this.values.get();
|
||||
|
||||
// If values is not known (null) then just assume the str is acceptable.
|
||||
if (values == null || values.contains(str)) {
|
||||
// If values is not fully known then just assume the str is acceptable.
|
||||
if (values == null || !values.isComplete() || values.getElements().contains(str)) {
|
||||
return str;
|
||||
} else {
|
||||
throw errorOnParse(createErrorMessage(str, values));
|
||||
throw errorOnParse(createErrorMessage(str, values.getElements()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,21 +95,6 @@ public class EnumValueParser implements ValueParser {
|
||||
protected Exception errorOnBlank(String message) {
|
||||
return new ValueParseException(message);
|
||||
}
|
||||
|
||||
private static <T> Provider<T> provider(T values) {
|
||||
return () -> values;
|
||||
}
|
||||
|
||||
private static <T> Provider<T> provider(Callable<T> values) {
|
||||
return () -> {
|
||||
try {
|
||||
return values.call();
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean longRunning() {
|
||||
return this.longRunning ;
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
@@ -76,4 +77,27 @@ public class ExternalCommand {
|
||||
// org.junit.Assert.assertEquals(0, process.getExitValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(command);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ExternalCommand other = (ExternalCommand) obj;
|
||||
if (!Arrays.equals(command, other.command))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*******************************************************************************
|
||||
* 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.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* A partial collection instance represents collection of
|
||||
* elements which may not be entirely known.
|
||||
* <p>
|
||||
* For unknown collection, an optional explanation, in the
|
||||
* form of a caught exception may be stored as well.
|
||||
*/
|
||||
public class PartialCollection<T> {
|
||||
|
||||
private static final PartialCollection<?> UNKNOWN = new PartialCollection<>(ImmutableSet.of(), false);
|
||||
private static final PartialCollection<?> EMPTY = new PartialCollection<>(ImmutableSet.of(), true);
|
||||
|
||||
final private ImmutableCollection<T> knownElements;
|
||||
final private boolean isComplete;
|
||||
final private Throwable explanation;
|
||||
|
||||
private PartialCollection(ImmutableCollection<T> knownElements, boolean isComplete, Throwable error) {
|
||||
this.knownElements = knownElements;
|
||||
this.isComplete = isComplete;
|
||||
this.explanation = error;
|
||||
}
|
||||
|
||||
private PartialCollection(ImmutableCollection<T> knownElements, boolean isComplete) {
|
||||
this.knownElements = knownElements;
|
||||
this.isComplete = isComplete;
|
||||
this.explanation = null;
|
||||
}
|
||||
|
||||
private PartialCollection(ImmutableCollection<T> knownElements, Throwable error) {
|
||||
this.knownElements = knownElements;
|
||||
this.isComplete = error==null;
|
||||
this.explanation = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PartialCollection} by executing some computation that returs a collectioon.
|
||||
* If the computation throws the resulting collection will be completely unknown, otherwise
|
||||
* it will be completely known.
|
||||
*/
|
||||
public static <T> PartialCollection<T> compute(Callable<Collection<T>> computer) {
|
||||
try {
|
||||
Collection<T> allValues = computer.call();
|
||||
if (allValues==null) {
|
||||
return PartialCollection.unknown();
|
||||
}
|
||||
return new PartialCollection<>(ImmutableSet.copyOf(allValues), true);
|
||||
} catch (Exception e) {
|
||||
return new PartialCollection<>(ImmutableSet.of(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PartialCollection} by executing some computation that returs a collectioon.
|
||||
* If the computation throws the resulting collection will be completely unknown, otherwise
|
||||
* it will be completely known.
|
||||
*/
|
||||
public static <T> PartialCollection<T> fromCallable(Callable<PartialCollection<T>> computer) {
|
||||
try {
|
||||
return computer.call();
|
||||
} catch (Exception e) {
|
||||
return new PartialCollection<>(ImmutableSet.of(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return All the known elements of this partial collection.
|
||||
*/
|
||||
public Collection<T> getElements() {
|
||||
return knownElements;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the totally unknown collection. I.e. a unknown collection with no known elements
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> PartialCollection<T> unknown() {
|
||||
return (PartialCollection<T>) UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like map on streams, but silently drops any null elements returned by the mapper.
|
||||
*/
|
||||
public <R> PartialCollection<R> map(Function<? super T, ? extends R> mapper) {
|
||||
ImmutableSet<R> mappedElements = getElements().stream().map((x) -> mapper.apply(x)).filter(x -> x!=null).collect(CollectorUtil.toImmutableSet());
|
||||
return new PartialCollection<R>(mappedElements, isComplete, explanation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a empty collection (i.e. the collection is know to be empty).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> PartialCollection<T> empty() {
|
||||
return (PartialCollection<T>) EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a copy of this collection that has the same known elements but also has unknown elements.
|
||||
*/
|
||||
public PartialCollection<T> addUncertainty() {
|
||||
if (!this.isComplete()) {
|
||||
return this; //No need to make a copy. Current collection is already only partially known.
|
||||
}
|
||||
return new PartialCollection<>(knownElements, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A completely unknown collection with a given exception explaining the reason.
|
||||
*/
|
||||
public static <T> PartialCollection<T> unknown(Exception e) {
|
||||
Assert.isLegal(e!=null);
|
||||
return new PartialCollection<>(ImmutableSet.of(), e);
|
||||
}
|
||||
|
||||
public PartialCollection<T> addAll(Collection<T> moreElements) {
|
||||
ImmutableSet.Builder<T> elements = ImmutableSet.builder();
|
||||
elements.addAll(getElements());
|
||||
elements.addAll(moreElements);
|
||||
return new PartialCollection<>(elements.build(), isComplete, explanation);
|
||||
}
|
||||
|
||||
public Throwable getExplanation() {
|
||||
return explanation;
|
||||
}
|
||||
|
||||
public PartialCollection<T> add(@SuppressWarnings("unchecked") T... values) {
|
||||
return addAll(Arrays.asList(values));
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.linetracker.DefaultLineTracker;
|
||||
import org.springframework.ide.vscode.commons.util.text.linetracker.ILineTracker;
|
||||
|
||||
@@ -88,11 +89,14 @@ public class TextDocument implements IDocument {
|
||||
|
||||
public synchronized void apply(DidChangeTextDocumentParams params) throws BadLocationException {
|
||||
int newVersion = params.getTextDocument().getVersion();
|
||||
Assert.isLegal(version<newVersion);
|
||||
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
|
||||
apply(change);
|
||||
if (version<newVersion) {
|
||||
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
|
||||
apply(change);
|
||||
}
|
||||
this.version = newVersion;
|
||||
} else {
|
||||
Log.warn("Change event with bad version ignored: "+params);
|
||||
}
|
||||
this.version = newVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.completion;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*;
|
||||
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DASH_PROPOSAL;
|
||||
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DEPRECATION;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -30,9 +31,9 @@ import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
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.ValueParseException;
|
||||
import org.springframework.ide.vscode.commons.yaml.completion.DefaultCompletionFactory.ValueProposal;
|
||||
import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
||||
@@ -44,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;
|
||||
@@ -97,6 +100,28 @@ 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 && snippet.isApplicable(getSchemaContext())) {
|
||||
DocumentEdits edits = new DocumentEdits(doc.getDocument());
|
||||
int start = offset - query.length();
|
||||
edits.delete(start, query);
|
||||
int referenceIndent = doc.getColumn(start);
|
||||
boolean needsSpace = start > 0 && !Character.isWhitespace(doc.getChar(start-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);
|
||||
@@ -107,7 +132,6 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
|
||||
public List<ICompletionProposal> getKeyCompletions(YamlDocument doc, int offset, String query) throws Exception {
|
||||
int queryOffset = offset - query.length();
|
||||
SNode contextNode = getContextNode();
|
||||
DynamicSchemaContext dynamicCtxt = getSchemaContext();
|
||||
List<YTypedProperty> allProperties = typeUtil.getProperties(type);
|
||||
if (CollectionUtil.hasElements(allProperties)) {
|
||||
@@ -115,6 +139,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
Set<String> definedProps = dynamicCtxt.getDefinedProperties();
|
||||
List<ICompletionProposal> proposals = new ArrayList<>();
|
||||
boolean suggestDeprecated = typeUtil.suggestDeprecatedProperties();
|
||||
YamlIndentUtil indenter = new YamlIndentUtil(doc);
|
||||
for (List<YTypedProperty> thisTier : tieredProperties) {
|
||||
List<YTypedProperty> undefinedProps = thisTier.stream()
|
||||
.filter(p -> !definedProps.contains(p.getName()) && (suggestDeprecated || !p.isDeprecated()))
|
||||
@@ -124,15 +149,17 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
String name = p.getName();
|
||||
double score = FuzzyMatcher.matchScore(query, name);
|
||||
if (score!=0) {
|
||||
YamlPath relativePath = YamlPath.fromSimpleProperty(name);
|
||||
YamlPathEdits edits = new YamlPathEdits(doc);
|
||||
DocumentEdits edits = new DocumentEdits(doc.getDocument());
|
||||
YType YType = p.getType();
|
||||
edits.delete(queryOffset, query);
|
||||
int referenceIndent = doc.getColumn(queryOffset);
|
||||
if (queryOffset>0 && !Character.isWhitespace(doc.getChar(queryOffset-1))) {
|
||||
//See https://www.pivotaltracker.com/story/show/137722057
|
||||
edits.insert(queryOffset, " ");
|
||||
referenceIndent++;
|
||||
}
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(YType));
|
||||
String snippet = p.getName()+":" +appendTextFor(YType);
|
||||
edits.insert(queryOffset, indenter.applyIndentation(snippet, referenceIndent));
|
||||
ICompletionProposal completion = completionFactory().beanProperty(doc.getDocument(),
|
||||
contextPath.toPropString(), getType(),
|
||||
query, p, score, edits, typeUtil);
|
||||
@@ -197,16 +224,11 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
}
|
||||
|
||||
private List<ICompletionProposal> getValueCompletions(YamlDocument doc, SNode node, int offset, String query) {
|
||||
YValueHint[] values=null;
|
||||
try {
|
||||
values = typeUtil.getHintValues(type, getSchemaContext());
|
||||
} catch (Exception e) {
|
||||
if (!Boolean.getBoolean("lsp.yaml.completions.errors.disable")) {
|
||||
return ImmutableList.of(completionFactory().errorMessage(query, getMessage(e)));
|
||||
} else {
|
||||
Log.warn(query, e);
|
||||
}
|
||||
PartialCollection<YValueHint> _values = typeUtil.getHintValues(type, getSchemaContext());
|
||||
if (_values.getExplanation()!=null && _values.getElements().isEmpty() && !Boolean.getBoolean("lsp.yaml.completions.errors.disable")) {
|
||||
return ImmutableList.of(completionFactory().errorMessage(query, getMessage(_values.getExplanation())));
|
||||
}
|
||||
Collection<YValueHint> values = _values.getElements();
|
||||
if (values!=null) {
|
||||
ArrayList<ICompletionProposal> completions = new ArrayList<>();
|
||||
YamlIndentUtil indenter = new YamlIndentUtil(doc);
|
||||
@@ -243,7 +265,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private String getMessage(Exception _e) {
|
||||
private String getMessage(Throwable _e) {
|
||||
Throwable e = ExceptionUtil.getDeepestCause(_e);
|
||||
|
||||
// If value parse exception, do not append any additional information
|
||||
@@ -384,18 +406,20 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
@Override
|
||||
protected DocumentEdits transformEdit(DocumentEdits textEdit) {
|
||||
textEdit.transformFirstNonWhitespaceEdit((Integer offset, String insertText) -> {
|
||||
YamlIndentUtil indenter = new YamlIndentUtil("\n");
|
||||
if (needNewline(textEdit)) {
|
||||
return insertText.substring(0, offset)
|
||||
+ "\n" +Strings.repeat(" ", node.getIndent())+"- "
|
||||
+ insertText.substring(offset);
|
||||
+ indenter.applyIndentation(insertText.substring(offset), YamlIndentUtil.INDENT_BY);
|
||||
} else if (offset > 2) {
|
||||
String prefix = insertText.substring(offset-2, offset);
|
||||
if (" ".equals(prefix)) {
|
||||
//special case don't add the "- " in front, but replace the inserted spaces instead.
|
||||
return insertText.substring(0, offset-2)+"- "+insertText.substring(offset);
|
||||
return insertText.substring(0, offset-2)
|
||||
+ "- "+ insertText.substring(offset);
|
||||
}
|
||||
}
|
||||
return insertText.substring(0, offset) + "- "+insertText.substring(offset);
|
||||
return insertText.substring(0, offset) + "- "+indenter.applyIndentation(insertText.substring(offset), YamlIndentUtil.INDENT_BY);
|
||||
});
|
||||
return textEdit;
|
||||
}
|
||||
@@ -404,11 +428,9 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
//value proposals which are inserted right after a key will not automatically include a newline, as
|
||||
// its not required for them. So we should add it along with the dash.
|
||||
try {
|
||||
if (original instanceof ValueProposal) {
|
||||
Integer insertAt = textEdit.getFirstEditStart();
|
||||
if (insertAt!=null) {
|
||||
return !"".equals(doc.getLineTextBefore(insertAt).trim());
|
||||
}
|
||||
Integer insertAt = textEdit.getFirstEditStart();
|
||||
if (insertAt!=null) {
|
||||
return !"".equals(doc.getLineTextBefore(insertAt).trim());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
|
||||
@@ -52,7 +52,7 @@ import com.google.common.collect.ImmutableList;
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class YamlCompletionEngine implements ICompletionEngine {
|
||||
|
||||
|
||||
Pattern SPACES = Pattern.compile("[ ]+");
|
||||
|
||||
final static Logger logger = LoggerFactory.getLogger(YamlCompletionEngine.class);
|
||||
@@ -110,7 +110,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
|
||||
protected Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, int baseIndent, double deempasizeBy) {
|
||||
try {
|
||||
return fixIndentations(getBaseCompletions(offset, doc, current, contextNode),
|
||||
return fixIndentations(getBaseCompletions(offset, doc, current, contextNode),
|
||||
current, contextNode, baseIndent, deempasizeBy);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
@@ -118,7 +118,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode,
|
||||
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode,
|
||||
SNode contextNode, int baseIndent, double deempasizeBy) {
|
||||
if (!completions.isEmpty()) {
|
||||
int dashyIndent = getTargetIndent(contextNode, currentNode, true);
|
||||
@@ -144,7 +144,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
if (isExtraIndentRelaxable(contextNode, fixIndentBy)) {
|
||||
return indented(p, Strings.repeat(" ", fixIndentBy));
|
||||
}
|
||||
} else { // fixIndentBy < 0
|
||||
} else { // fixIndentBy < 0
|
||||
if (isLesserIndentRelaxable(currentNode, contextNode)) {
|
||||
return dedented(p, -fixIndentBy, contextNode.getDocument());
|
||||
}
|
||||
@@ -168,7 +168,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the indentation level needed to line up with other contextNode children.
|
||||
* Determine the indentation level needed to line up with other contextNode children.
|
||||
* If the contextNode has no children, then compute a proper default indentation where
|
||||
* a new child could be added.
|
||||
*/
|
||||
@@ -182,8 +182,8 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
if (child.isPresent()) {
|
||||
return child.get().getIndent();
|
||||
}
|
||||
return (dashy || contextNode.getNodeType()==SNodeType.DOC)
|
||||
? contextNode.getIndent()
|
||||
return (dashy || contextNode.getNodeType()==SNodeType.DOC)
|
||||
? contextNode.getIndent()
|
||||
: contextNode.getIndent() + YamlIndentUtil.INDENT_BY;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
};
|
||||
transformed.deemphasize(DEEMP_DEDENTED_PROPOSAL*numArrows);
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
// we can't dedent the proposal by the requested amount of space. So err on the safe
|
||||
// side and ignore the proposal. (Otherwise me might end up deleting non-space chars
|
||||
// in our attempt to de-dent.)
|
||||
@@ -226,7 +226,13 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
return Strings.repeat(Unicodes.RIGHT_ARROW+" ", numArrows) + originalLabel;
|
||||
}
|
||||
@Override public DocumentEdits transformEdit(DocumentEdits originalEdit) {
|
||||
originalEdit.indentFirstEdit(indentStr);
|
||||
// originalEdit.indentFirstEdit(indentStr);
|
||||
YamlIndentUtil indenter = new YamlIndentUtil("\n");
|
||||
originalEdit.transformFirstNonWhitespaceEdit((Integer offset, String insertText) -> {
|
||||
String prefix = insertText.substring(0, offset);
|
||||
String target = insertText.substring(offset);
|
||||
return prefix + indentStr + indenter.applyIndentation(target, indentStr);
|
||||
});
|
||||
return originalEdit;
|
||||
}
|
||||
};
|
||||
@@ -306,14 +312,14 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get context node candidates taking into account that we want to have a 'relaxed' interpretation
|
||||
* of the context node with respect to the current indentation where we ask for a completion.
|
||||
* To allow for the ambiguity in indentation a list of context nodes is returned instead of a
|
||||
* To allow for the ambiguity in indentation a list of context nodes is returned instead of a
|
||||
* single node. (Note we may still return a singleton list for cases where relaxed indentation
|
||||
* doesn't seem desirable).
|
||||
* @param baseIndent
|
||||
* @param baseIndent
|
||||
*/
|
||||
protected List<SNode> getContextNodes(YamlDocument doc, SNode node, int offset, int baseIndent) {
|
||||
if (node==null) {
|
||||
@@ -340,7 +346,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
//This node has flexibility around indentation. So this is where me need to build a list of candidates!
|
||||
ImmutableList.Builder<SNode> contextNodes = ImmutableList.builder();
|
||||
while (node!=null ) {
|
||||
//Any node that represents a 'step' between contexts and is not too deeply nested is kept.
|
||||
//Any node that represents a 'step' between contexts and is not too deeply nested is kept.
|
||||
if (node.getSegment()!=null && node.getIndent()<=baseIndent) {
|
||||
contextNodes.add(node);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -29,14 +28,16 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix;
|
||||
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.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;
|
||||
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.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
@@ -56,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;
|
||||
@@ -185,7 +187,7 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YValueHint[] getHintValues(YType type, DynamicSchemaContext dc) throws Exception {
|
||||
public PartialCollection<YValueHint> getHintValues(YType type, DynamicSchemaContext dc) {
|
||||
return ((AbstractType)type).getHintValues(dc);
|
||||
}
|
||||
|
||||
@@ -235,6 +237,11 @@ public class YTypeFactory {
|
||||
return ((AbstractType)type).getCustomContentAssistant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeBasedSnippetProvider getSnippetProvider() {
|
||||
return snippetProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tieredOptionalPropertyProposals() {
|
||||
return enableTieredOptionalPropertyProposals;
|
||||
@@ -257,7 +264,7 @@ public class YTypeFactory {
|
||||
private List<YTypedProperty> propertyList = new ArrayList<>();
|
||||
private List<YValueHint> hints = new ArrayList<>();
|
||||
private Map<String, YTypedProperty> cachedPropertyMap;
|
||||
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
|
||||
private SchemaContextAware<PartialCollection<YValueHint>> hintProvider;
|
||||
//TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware<Collection<YValueHint>>
|
||||
|
||||
private List<Constraint> constraints = new ArrayList<>(2);
|
||||
@@ -293,47 +300,19 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
public AbstractType setHintProvider(Callable<Collection<YValueHint>> hintProvider) {
|
||||
setHintProvider((DynamicSchemaContext dc) -> hintProvider);
|
||||
setHintProvider((DynamicSchemaContext dc) -> PartialCollection.compute(hintProvider));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AbstractType setHintProvider(SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider) {
|
||||
public AbstractType setHintProvider(SchemaContextAware<PartialCollection<YValueHint>> hintProvider) {
|
||||
//TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware<Collection<YValueHint>>
|
||||
this.hintProvider = hintProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public YValueHint[] getHintValues(DynamicSchemaContext dc) throws Exception {
|
||||
Collection<YValueHint> providerHints = null;
|
||||
try {
|
||||
providerHints=getProviderHints(dc);
|
||||
} catch (Exception e) {
|
||||
if (!hints.isEmpty()) {
|
||||
Log.log(e);
|
||||
//Recover from error returning just the static hints.
|
||||
return hints.toArray(new YValueHint[hints.size()]);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (providerHints == null || providerHints.isEmpty()) {
|
||||
return hints.toArray(new YValueHint[hints.size()]);
|
||||
} else {
|
||||
// Only merge if there are provider hints to merge
|
||||
Set<YValueHint> mergedHints = new LinkedHashSet<>();
|
||||
|
||||
// Add type hints first
|
||||
for (YValueHint val : hints) {
|
||||
mergedHints.add(val);
|
||||
}
|
||||
|
||||
// merge the provider hints
|
||||
for (YValueHint val : providerHints) {
|
||||
mergedHints.add(val);
|
||||
}
|
||||
return mergedHints.toArray(new YValueHint[mergedHints.size()]);
|
||||
}
|
||||
public PartialCollection<YValueHint> getHintValues(DynamicSchemaContext dc) {
|
||||
return getProviderHints(dc)
|
||||
.addAll(hints);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,14 +322,15 @@ public class YTypeFactory {
|
||||
hints = ImmutableList.copyOf(hints);
|
||||
}
|
||||
|
||||
private Collection<YValueHint> getProviderHints(DynamicSchemaContext dc) throws Exception {
|
||||
private PartialCollection<YValueHint> getProviderHints(DynamicSchemaContext dc) {
|
||||
if (hintProvider != null) {
|
||||
Callable<Collection<YValueHint>> withContext = hintProvider.withContext(dc);
|
||||
if (withContext != null) {
|
||||
return withContext.call();
|
||||
try {
|
||||
return hintProvider.withContext(dc);
|
||||
} catch (Exception e) {
|
||||
return PartialCollection.unknown(e);
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
return PartialCollection.empty();
|
||||
}
|
||||
|
||||
public List<Constraint> getConstraints() {
|
||||
@@ -417,7 +397,7 @@ public class YTypeFactory {
|
||||
parseWith((DynamicSchemaContext dc) -> parser);
|
||||
return this;
|
||||
}
|
||||
private SchemaContextAware<ValueParser> getParser() {
|
||||
public SchemaContextAware<ValueParser> getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
@@ -878,25 +858,41 @@ public class YTypeFactory {
|
||||
return ((YTypedPropertyImpl)prop).copy();
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<YValueHint>> values) {
|
||||
public YAtomicType yenumFromHints(String name, SchemaContextAware<BiFunction<String, Collection<String>, String>> errorMessageFormatter, SchemaContextAware<PartialCollection<YValueHint>> values) {
|
||||
YAtomicType t = yatomic(name);
|
||||
t.setHintProvider((dc) -> () -> values.withContext(dc));
|
||||
t.setHintProvider(values);
|
||||
t.parseWith((DynamicSchemaContext dc) -> {
|
||||
Collection<String> strings = YTypeFactory.values(values.withContext(dc));
|
||||
return new EnumValueParser(name, strings) {
|
||||
PartialCollection<YValueHint> hints = PartialCollection.fromCallable(() -> values.withContext(dc));
|
||||
return new EnumValueParser(name, hints.map(h -> h.getValue())) {
|
||||
@Override
|
||||
protected String createErrorMessage(String parseString, Collection<String> values) {
|
||||
return errorMessageFormatter.apply(parseString, values);
|
||||
try {
|
||||
return errorMessageFormatter.withContext(dc).apply(parseString, values);
|
||||
} catch (Exception e) {
|
||||
return super.createErrorMessage(parseString, values);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
return t;
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<Collection<String>> values) {
|
||||
public YAtomicType yenumFromDynamicValues(String name,
|
||||
SchemaContextAware<BiFunction<String, Collection<String>, String>> errorMessageFormatter,
|
||||
SchemaContextAware<PartialCollection<String>> values
|
||||
) {
|
||||
return yenumFromHints(name,
|
||||
//Error message formatter:
|
||||
(parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
|
||||
errorMessageFormatter,
|
||||
//Hints provider:
|
||||
(dc) -> hints(values.withContext(dc))
|
||||
);
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<PartialCollection<String>> values) {
|
||||
return yenumFromHints(name,
|
||||
//Error message formatter:
|
||||
(dc) -> (parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
|
||||
//Hints provider:
|
||||
(dc) -> hints(values.withContext(dc))
|
||||
);
|
||||
@@ -906,30 +902,35 @@ public class YTypeFactory {
|
||||
return new EnumTypeBuilder(name, values);
|
||||
}
|
||||
|
||||
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
|
||||
public YAtomicType yenum(String name, SchemaContextAware<BiFunction<String, Collection<String>, String>> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
|
||||
YAtomicType t = yatomic(name);
|
||||
t.setHintProvider((dc) -> {
|
||||
Collection<String> strings = values.withContext(dc);
|
||||
return strings==null
|
||||
? null
|
||||
: () -> strings.stream()
|
||||
.map((s) -> new BasicYValueHint(s))
|
||||
.collect(Collectors.toSet());
|
||||
return PartialCollection.compute(() -> values.withContext(dc))
|
||||
.map(BasicYValueHint::new);
|
||||
});
|
||||
t.parseWith((DynamicSchemaContext dc) -> {
|
||||
EnumValueParser enumParser = new EnumValueParser(name, values.withContext(dc)) {
|
||||
@Override
|
||||
protected String createErrorMessage(String parseString, Collection<String> values) {
|
||||
return errorMessageFormatter.apply(parseString, values);
|
||||
try {
|
||||
return errorMessageFormatter.withContext(dc).apply(parseString, values);
|
||||
} catch (Exception e) {
|
||||
return super.createErrorMessage(parseString, values);
|
||||
}
|
||||
}
|
||||
};
|
||||
return enumParser;
|
||||
});
|
||||
return t;
|
||||
|
||||
}
|
||||
|
||||
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
|
||||
return yenum(name, (dc) -> errorMessageFormatter, values);
|
||||
}
|
||||
|
||||
public static Collection<String> values(Collection<YValueHint> hints) {
|
||||
return hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
|
||||
return hints == null ? null : hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public YAtomicType yenum(String name, String... values) {
|
||||
@@ -960,10 +961,16 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
public static Collection<YValueHint> hints(Collection<String> values) {
|
||||
return values.stream()
|
||||
.map(YTypeFactory::hint)
|
||||
.collect(CollectorUtil.toMultiset());
|
||||
}
|
||||
|
||||
public static PartialCollection<YValueHint> hints(PartialCollection<String> values) {
|
||||
if (values!=null) {
|
||||
return values.stream().map(YTypeFactory::hint).collect(Collectors.toList());
|
||||
return values.map(YTypeFactory::hint);
|
||||
}
|
||||
return null;
|
||||
return PartialCollection.unknown();
|
||||
}
|
||||
|
||||
public YTypeFactory enableTieredProposals(boolean enable) {
|
||||
@@ -976,5 +983,10 @@ public class YTypeFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
public YTypeFactory setSnippetProvider(TypeBasedSnippetProvider snippetProvider) {
|
||||
this.snippetProvider = snippetProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,11 +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
|
||||
@@ -30,7 +34,7 @@ public interface YTypeUtil {
|
||||
boolean isSequencable(YType type);
|
||||
boolean isBean(YType type);
|
||||
YType getDomainType(YType type);
|
||||
YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc) throws Exception;
|
||||
PartialCollection<YValueHint> getHintValues(YType yType, DynamicSchemaContext dc);
|
||||
String niceTypeName(YType type);
|
||||
YType getKeyType(YType type);
|
||||
SchemaContextAware<ValueParser> getValueParser(YType type);
|
||||
@@ -47,9 +51,17 @@ public interface YTypeUtil {
|
||||
*/
|
||||
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
|
||||
List<Constraint> getConstraints(YType type);
|
||||
|
||||
|
||||
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
|
||||
@@ -58,7 +70,7 @@ public interface YTypeUtil {
|
||||
boolean tieredOptionalPropertyProposals();
|
||||
/**
|
||||
* Config option for type-based completion engine. This enables/disables
|
||||
* whether engine should generate proposals for deprecated properties (true),
|
||||
* whether engine should generate proposals for deprecated properties (true),
|
||||
* or suppress them (false).
|
||||
*/
|
||||
boolean suggestDeprecatedProperties();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* 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]";
|
||||
}
|
||||
public Predicate<DynamicSchemaContext> getApplicability() {
|
||||
return applicability;
|
||||
}
|
||||
public boolean isApplicable(DynamicSchemaContext dc) {
|
||||
return applicability==null || applicability.test(dc);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.commons.yaml.util;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
@@ -42,7 +43,11 @@ public class YamlIndentUtil {
|
||||
}
|
||||
|
||||
public YamlIndentUtil(YamlDocument doc) {
|
||||
this(doc.getDocument().getDefaultLineDelimiter());
|
||||
this(doc.getDocument());
|
||||
}
|
||||
|
||||
public YamlIndentUtil(IDocument doc) {
|
||||
this(doc.getDefaultLineDelimiter());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +94,10 @@ public class YamlIndentUtil {
|
||||
return text.replaceAll("\\n", newlineWithIndent(indentBy));
|
||||
}
|
||||
|
||||
public String applyIndentation(String text, String indentStr) {
|
||||
return text.replaceAll("\\n", "\n"+indentStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase offset by indentation. Take care when 'indent' is -1 (unkownn) to
|
||||
* just return offset unmodified.
|
||||
|
||||
@@ -63,6 +63,7 @@ public class Editor {
|
||||
public static final Predicate<CompletionItem> PLAIN_COMPLETION = c -> !RELAXED_COMPLETION.test(c);
|
||||
public static final Predicate<CompletionItem> DEDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ");
|
||||
public static final Predicate<CompletionItem> INDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.RIGHT_ARROW+" ");
|
||||
public static final Predicate<CompletionItem> SNIPPET_COMPLETION = c -> c.getLabel().endsWith("Snippet");
|
||||
|
||||
static class EditorState {
|
||||
String documentContents;
|
||||
@@ -327,6 +328,10 @@ public class Editor {
|
||||
}
|
||||
|
||||
public List<CompletionItem> assertCompletionLabels(String... expectedLabels) throws Exception {
|
||||
return assertCompletionLabels(c -> true, expectedLabels);
|
||||
}
|
||||
|
||||
public List<CompletionItem> assertCompletionLabels(Predicate<CompletionItem> isInteresting, String... expectedLabels) throws Exception {
|
||||
StringBuilder expect = new StringBuilder();
|
||||
StringBuilder actual = new StringBuilder();
|
||||
for (String label : expectedLabels) {
|
||||
@@ -336,8 +341,10 @@ public class Editor {
|
||||
|
||||
List<CompletionItem> completions;
|
||||
for (CompletionItem completion : completions = getCompletions()) {
|
||||
actual.append(completion.getLabel());
|
||||
actual.append("\n");
|
||||
if (isInteresting.test(completion)) {
|
||||
actual.append(completion.getLabel());
|
||||
actual.append("\n");
|
||||
}
|
||||
}
|
||||
assertEquals(expect.toString(), actual.toString());
|
||||
return completions;
|
||||
@@ -417,19 +424,25 @@ public class Editor {
|
||||
String docText = doc.getText();
|
||||
if (edit!=null) {
|
||||
String replaceWith = edit.getNewText();
|
||||
//Apply indentfix, this is magic vscode seems to apply to edits returned by language server. So our harness has to
|
||||
// mimick that behavior. See https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
int referenceLine = edit.getRange().getStart().getLine();
|
||||
int cursorOffset = edit.getRange().getStart().getCharacter();
|
||||
String referenceIndent = doc.getLineIndentString(referenceLine);
|
||||
if (cursorOffset<referenceIndent.length()) {
|
||||
referenceIndent = referenceIndent.substring(0, cursorOffset);
|
||||
}
|
||||
replaceWith = replaceWith.replaceAll("\\n", "\n"+referenceIndent);
|
||||
int cursorReplaceOffset = 0;
|
||||
|
||||
int cursorReplaceOffset = replaceWith.indexOf(VS_CODE_CURSOR_MARKER);
|
||||
if (cursorReplaceOffset>=0) {
|
||||
replaceWith = replaceWith.substring(0, cursorReplaceOffset) + replaceWith.substring(cursorReplaceOffset+VS_CODE_CURSOR_MARKER.length());
|
||||
if (!Boolean.getBoolean("lsp.completions.indentation.enable")) {
|
||||
//Apply indentfix, this is magic vscode seems to apply to edits returned by language server. So our harness has to
|
||||
// mimick that behavior. See https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
int referenceLine = edit.getRange().getStart().getLine();
|
||||
int cursorOffset = edit.getRange().getStart().getCharacter();
|
||||
String referenceIndent = doc.getLineIndentString(referenceLine);
|
||||
if (cursorOffset<referenceIndent.length()) {
|
||||
referenceIndent = referenceIndent.substring(0, cursorOffset);
|
||||
}
|
||||
replaceWith = replaceWith.replaceAll("\\n", "\n"+referenceIndent);
|
||||
}
|
||||
|
||||
// Replace the cursor string
|
||||
cursorReplaceOffset = replaceWith.indexOf(VS_CODE_CURSOR_MARKER);
|
||||
if (cursorReplaceOffset >= 0) {
|
||||
replaceWith = replaceWith.substring(0, cursorReplaceOffset)
|
||||
+ replaceWith.substring(cursorReplaceOffset + VS_CODE_CURSOR_MARKER.length());
|
||||
} else {
|
||||
cursorReplaceOffset = replaceWith.length();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.MimeTypes;
|
||||
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.ValueParseException;
|
||||
@@ -170,11 +171,11 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
t_version.addHints("latest", "every");
|
||||
|
||||
t_resource_type_name = f.yenumFromHints("ResourceType Name",
|
||||
(parseString, validValues) -> {
|
||||
(dc) -> (parseString, validValues) -> {
|
||||
return "The '"+parseString+"' Resource Type does not exist. Existing types: "+validValues;
|
||||
},
|
||||
(DynamicSchemaContext dc) -> {
|
||||
return models.getResourceTypeNameHints(dc);
|
||||
return PartialCollection.compute(() -> models.getResourceTypeNameHints(dc));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -190,8 +191,8 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
t_maybe_resource_name.setHintProvider((DynamicSchemaContext dc) -> {
|
||||
//Putting the Callable into a local variable is strange, but the compiler doesn't like it if
|
||||
// we return it directly. Too much complexity for Java type-inference?
|
||||
Callable<Collection<YValueHint>> callable = () -> YTypeFactory.hints(models.getResourceNames(dc));
|
||||
return callable;
|
||||
PartialCollection<YValueHint> hints = PartialCollection.compute(() -> YTypeFactory.hints(models.getResourceNames(dc)));
|
||||
return hints;
|
||||
});
|
||||
t_maybe_resource_name.parseWith(ValueParsers.NE_STRING);
|
||||
|
||||
|
||||
@@ -3,35 +3,6 @@
|
||||
This extension provides validation, content assist and documentation hovers
|
||||
for editing [Bosh](https://bosh.io/) Deployment Manifest files.
|
||||
|
||||
## Usage
|
||||
|
||||
### Activating the Editor
|
||||
|
||||
The Bosh editor automatically activates when the name of the `.yml` file you are editing
|
||||
follows a certain pattern:
|
||||
|
||||
- `**/*deployment*.yml` : activates support for bosh manifest file.
|
||||
|
||||
You can also define your own patterns and map them to the language-id `bosh-deployment-manifest`
|
||||
by defining `files.associations` in workspace settings.
|
||||
See [vscode documentation](https://code.visualstudio.com/Docs/languages/overview#_adding-a-file-extension-to-a-language) for details.
|
||||
|
||||
### Targetting a specific Director
|
||||
|
||||
Some Validation and Content Assist use information dymanically retrieved from an active Bosh director.
|
||||
For these feature to work it is required that you
|
||||
|
||||
- have the bosh cli V2 installed (information is obtained by executing commands using the V2 cli)
|
||||
- target a director by setting the `BOSH_ENVIROMENT` variable.
|
||||
|
||||
You can verify that you have set things up right by executing command:
|
||||
|
||||
```
|
||||
bosh cloud-config --json
|
||||
```
|
||||
|
||||
If setup correctly, it should return information about the cloud-config on your intended bosh director/environment.
|
||||
|
||||
## Functionality
|
||||
|
||||
### Validation
|
||||
@@ -85,13 +56,56 @@ When you use attributes from the V1 schema the editor will detect this however a
|
||||
In this mode, V1 properties are accepted but marked with deprecation warnings and V2 properties are marked as (unknown property)
|
||||
errors.
|
||||
|
||||
## Usage
|
||||
|
||||
### Activating the Editor
|
||||
|
||||
The Bosh editor automatically activates when the name of the `.yml` file you are editing
|
||||
follows a certain pattern:
|
||||
|
||||
- `**/*deployment*.yml` : activates support for bosh manifest file.
|
||||
|
||||
You can also define your own patterns and map them to the language-id `bosh-deployment-manifest`
|
||||
by defining `files.associations` in workspace settings.
|
||||
See [vscode documentation](https://code.visualstudio.com/Docs/languages/overview#_adding-a-file-extension-to-a-language) for details.
|
||||
|
||||
### Targetting a specific Director
|
||||
|
||||
Some of the Validations and Content Assist depend on information dymanically retrieved from an active Bosh director.
|
||||
The editor retreives information by executing commands using the Bosh CLI. For this to work the CLI (V2
|
||||
CLI is required) and editor have to be installed and configured correctly.
|
||||
|
||||
There are two ways to set things up to make this work:
|
||||
|
||||
#### Explicitly Configure the CLI:
|
||||
|
||||
From vscode, press `CTRL-SHIFT-P` and type `Settings` then select either `Open User Settings` or `Open Workspace Settings`.
|
||||
The bosh cli is configured by specifying keys of the form `bosh.cli.XXX`. Content assist and hover docs show you the
|
||||
the available keys and their meaning.
|
||||
|
||||
#### Implictly Configure the CLI:
|
||||
|
||||
If the bosh cli is not explicitly configured it will, by default, try to execute commands like `bosh cloud-config --json`
|
||||
and `bosh stemcells --json` without an explicit `-e ...` argument. This works only if you ensure that the editor
|
||||
process executes with a proper set of OS environment variables:
|
||||
|
||||
- `PATH`: must be set so that `bosh` cli executable can be found and refers to the V2 CLI.
|
||||
- `BOSH_ENVIRONMENT`: must be set to point to the bosh director you want to target.
|
||||
|
||||
If you start vscode from a terminal, you can verify that things are setup correctly by executing command:
|
||||
|
||||
bosh cloud-config
|
||||
|
||||
If that command executes without any errors and returns the cloud-config you expected, then things are setup correctly.
|
||||
If subsequently launch vscode from that same terminal the dynamic CA and linting should work correctly.
|
||||
|
||||
## Issues and Feature Requests
|
||||
|
||||
Please report bugs, issues and feature requests on the [Github STS4 issue tracker](https://github.com/spring-projects/sts4/issues).
|
||||
|
||||
[linting]: https://raw.githubusercontent.com/spring-projects/sts4/master/vscode-extensions/vscode-bosh/readme-imgs/linting.png
|
||||
[ca1]: https://raw.githubusercontent.com/spring-projects/sts4/master/vscode-extensions/vscode-bosh/readme-imgs/content-assist-1.png
|
||||
[ca2]: https://raw.githubusercontent.com/spring-projects/sts4/master/vscode-extensions/vscode-bosh/readme-imgs/content-assist-2.png
|
||||
[hovers]: https://raw.githubusercontent.com/spring-projects/sts4/master/vscode-extensions/vscode-bosh/readme-imgs/hover.png
|
||||
[peek]: https://raw.githubusercontent.com/spring-projects/sts4/master/vscode-extensions/vscode-bosh/readme-imgs/peek.png
|
||||
[goto_symbol]: https://raw.githubusercontent.com/spring-projects/sts4/master/vscode-extensions/vscode-bosh/readme-imgs/goto-symbol.png
|
||||
[linting]: https://raw.githubusercontent.com/spring-projects/sts4/7e3cf4808095f8b126bf1e5a90c09f3917f60fa4/vscode-extensions/vscode-bosh/readme-imgs/linting.png
|
||||
[ca1]: https://raw.githubusercontent.com/spring-projects/sts4/7e3cf4808095f8b126bf1e5a90c09f3917f60fa4/vscode-extensions/vscode-bosh/readme-imgs/content-assist-1.png
|
||||
[ca2]: https://raw.githubusercontent.com/spring-projects/sts4/7e3cf4808095f8b126bf1e5a90c09f3917f60fa4/vscode-extensions/vscode-bosh/readme-imgs/content-assist-2.png
|
||||
[hovers]: https://raw.githubusercontent.com/spring-projects/sts4/7e3cf4808095f8b126bf1e5a90c09f3917f60fa4/vscode-extensions/vscode-bosh/readme-imgs/hover.png
|
||||
[peek]: https://raw.githubusercontent.com/spring-projects/sts4/7e3cf4808095f8b126bf1e5a90c09f3917f60fa4/vscode-extensions/vscode-bosh/readme-imgs/peek.png
|
||||
[goto_symbol]: https://raw.githubusercontent.com/spring-projects/sts4/7e3cf4808095f8b126bf1e5a90c09f3917f60fa4/vscode-extensions/vscode-bosh/readme-imgs/goto-symbol.png
|
||||
|
||||
@@ -36,7 +36,10 @@ export function activate(context: VSCode.ExtensionContext) {
|
||||
fatJarFile: 'jars/language-server.jar',
|
||||
jvmHeap: "48m",
|
||||
clientOptions: {
|
||||
documentSelector: [ BOSH_DEPLOYMENT_LANGUAGE_ID ]
|
||||
documentSelector: [ BOSH_DEPLOYMENT_LANGUAGE_ID ],
|
||||
synchronize: {
|
||||
configurationSection: "bosh"
|
||||
}
|
||||
}
|
||||
};
|
||||
let clientPromise = commons.activate(options, context);
|
||||
|
||||
@@ -44,7 +44,28 @@
|
||||
"scopeName": "source.yaml",
|
||||
"path": "./yaml-support/yaml.tmLanguage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Bosh CLI Configuration",
|
||||
"properties": {
|
||||
"bosh.cli.command": {
|
||||
"type": ["string" , "null"],
|
||||
"default": "bosh",
|
||||
"description": "Path to an executable to launch the bosh cli V2. A V2 cli is required! Set this to null to completely disable all editor features that require access to the bosh director"
|
||||
},
|
||||
"bosh.cli.target": {
|
||||
"type": ["string", "null"],
|
||||
"default": null,
|
||||
"description": "Specifies the director/environment to target when executing bosh cli commands. I.e. this value is passed to the CLI via `-e` parameter."
|
||||
},
|
||||
"bosh.cli.timeout": {
|
||||
"type": "integer",
|
||||
"default": 3,
|
||||
"description": "Number of seconds before CLI commands are terminated with a timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": "./out/lib/Main",
|
||||
"scripts": {
|
||||
|
||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 102 KiB |