Merge branch 'master' into async_validations

Conflicts:
	headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java
This commit is contained in:
nsingh
2017-08-02 12:16:33 -07:00
55 changed files with 1837 additions and 408 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 + "]";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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