Content assist for 'domains' inside of route-strings
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
package org.springframework.ide.vscode.commons.languageserver.completion;
|
package org.springframework.ide.vscode.commons.languageserver.completion;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -295,7 +296,7 @@ public class DocumentEdits implements ProposalApplier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<Edit> edits = new ArrayList<Edit>();
|
private List<Edit> edits = new ArrayList<Edit>();
|
||||||
private IDocument doc;
|
private IDocument doc;
|
||||||
|
|
||||||
public DocumentEdits(IDocument doc) {
|
public DocumentEdits(IDocument doc) {
|
||||||
|
|||||||
@@ -58,18 +58,17 @@ public class FuzzyMatcher {
|
|||||||
//tend to favor matches at the end of the string over matches in the middle.
|
//tend to favor matches at the end of the string over matches in the middle.
|
||||||
skips+=dlen-dpos; //but do count the extra chars at end => more extra = worse score
|
skips+=dlen-dpos; //but do count the extra chars at end => more extra = worse score
|
||||||
}
|
}
|
||||||
return score(gaps, skips);
|
return score(gaps, skips, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double score(int gaps, int skips) {
|
private static double score(int gaps, int skips, String pattern) {
|
||||||
if (gaps==0) {
|
if (gaps==0) {
|
||||||
//gaps == 0 means a prefix match, ignore 'skips' at end of String and just sort
|
//gaps == 0 means a prefix match, ignore 'skips' at end of String and just sort
|
||||||
// alphabetic (see STS-4049)
|
// alphabetic (see STS-4049)
|
||||||
double badness = 0.1; // all scored equally, assumes using a 'stable' sorter.
|
return 0.5+pattern.length(); //all scored equally, assumes using a 'stable' sorter.
|
||||||
return -badness; //higher is better
|
|
||||||
} else {
|
} else {
|
||||||
double badness = 1+gaps + skips/10000.0; // higher is worse
|
double badness = 1+gaps + skips/1000.0; // higher is worse
|
||||||
return -badness; //higher is better
|
return 1.0/badness + pattern.length(); //higher is better
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ public abstract class AbstractYamlAssistContext implements YamlAssistContext {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected DocumentRegion getCustomAssistRegion(YamlDocument doc, SNode node, int offset) {
|
||||||
|
if (node.getNodeType()==SNodeType.KEY) {
|
||||||
|
SKeyNode keyNode = (SKeyNode) node;
|
||||||
|
if (keyNode.isInValue(offset)) {
|
||||||
|
int valueStart = keyNode.getColonOffset()+1;
|
||||||
|
int valueEnd = keyNode.getNodeEnd(); // assumes we only look at the current line, good enough for now
|
||||||
|
return new DocumentRegion(doc.getDocument(), valueStart, valueEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // TODO Reaching here might mean support for calling the custom assistant isn't
|
||||||
|
// implemented for this kind of context yet. It will have to be expanded upon
|
||||||
|
// as the need for it arises in real use-cases.
|
||||||
|
}
|
||||||
|
|
||||||
protected final String getPrefix(YamlDocument doc, SNode node, int offset) {
|
protected final String getPrefix(YamlDocument doc, SNode node, int offset) {
|
||||||
//For value completions... in general we would like to determine the whole text
|
//For value completions... in general we would like to determine the whole text
|
||||||
// corresponding to the value, so a simplistic backwards scan isn't good enough.
|
// corresponding to the value, so a simplistic backwards scan isn't good enough.
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ public class DefaultCompletionFactory implements CompletionFactory {
|
|||||||
public class ValueProposal extends ScoreableProposal {
|
public class ValueProposal extends ScoreableProposal {
|
||||||
|
|
||||||
private String value;
|
private String value;
|
||||||
//private String query;
|
|
||||||
private String label;
|
private String label;
|
||||||
private YType type;
|
private YType type;
|
||||||
private double baseScore;
|
private double baseScore;
|
||||||
@@ -94,7 +93,6 @@ public class DefaultCompletionFactory implements CompletionFactory {
|
|||||||
|
|
||||||
public ValueProposal(String value, String query, String label, YType type, Renderable docs, double score, DocumentEdits edits, YTypeUtil typeUtil) {
|
public ValueProposal(String value, String query, String label, YType type, Renderable docs, double score, DocumentEdits edits, YTypeUtil typeUtil) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
//this.query = query;
|
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.docs = docs;
|
this.docs = docs;
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ public abstract class TransformedCompletion extends ScoreableProposal {
|
|||||||
this.original = proposal;
|
this.original = proposal;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract String tranformLabel(String originalLabel);
|
protected String tranformLabel(String originalLabel) {
|
||||||
|
return originalLabel;
|
||||||
|
}
|
||||||
protected abstract DocumentEdits transformEdit(DocumentEdits textEdit);
|
protected abstract DocumentEdits transformEdit(DocumentEdits textEdit);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
|||||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
||||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.YamlPathSegmentType;
|
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.YamlPathSegmentType;
|
||||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||||
|
import org.springframework.ide.vscode.commons.yaml.schema.ISubCompletionEngine;
|
||||||
import org.springframework.ide.vscode.commons.yaml.schema.SNodeDynamicSchemaContext;
|
import org.springframework.ide.vscode.commons.yaml.schema.SNodeDynamicSchemaContext;
|
||||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
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.YTypeUtil;
|
||||||
@@ -84,6 +85,13 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
|
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
|
||||||
|
ISubCompletionEngine customContentAssistant = typeUtil.getCustomContentAssistant(type);
|
||||||
|
if (customContentAssistant!=null) {
|
||||||
|
DocumentRegion region = getCustomAssistRegion(doc, node, offset);
|
||||||
|
if (region!=null) {
|
||||||
|
return customContentAssistant.getCompletions(completionFactory(), region, region.toRelative(offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
String query = getPrefix(doc, node, offset);
|
String query = getPrefix(doc, node, offset);
|
||||||
List<ICompletionProposal> valueCompletions = getValueCompletions(doc, node, offset, query);
|
List<ICompletionProposal> valueCompletions = getValueCompletions(doc, node, offset, query);
|
||||||
if (!valueCompletions.isEmpty()) {
|
if (!valueCompletions.isEmpty()) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.schema;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||||
|
import org.springframework.ide.vscode.commons.yaml.completion.CompletionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that can be used by a {@link ICompletionEngine} to delegate to a
|
||||||
|
* 'helper' completion engine that computes completions for some sub-region of
|
||||||
|
* the document.
|
||||||
|
*
|
||||||
|
* @author Kris De Volder
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ISubCompletionEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param region The relevant subregion of the document (typically, the sub-engine only cares about the text
|
||||||
|
* within this particular region.)
|
||||||
|
* @param offset The cursor position relative to the region.
|
||||||
|
*/
|
||||||
|
List<ICompletionProposal> getCompletions(CompletionFactory f, DocumentRegion region, int offset);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -224,6 +224,11 @@ public class YTypeFactory {
|
|||||||
public List<Constraint> getConstraints(YType type) {
|
public List<Constraint> getConstraints(YType type) {
|
||||||
return ((AbstractType)type).getConstraints();
|
return ((AbstractType)type).getConstraints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ISubCompletionEngine getCustomContentAssistant(YType type) {
|
||||||
|
return ((AbstractType)type).getCustomContentAssistant();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -239,11 +244,21 @@ public class YTypeFactory {
|
|||||||
private Map<String, YTypedProperty> cachedPropertyMap;
|
private Map<String, YTypedProperty> cachedPropertyMap;
|
||||||
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
|
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
|
||||||
private List<Constraint> constraints = new ArrayList<>(2);
|
private List<Constraint> constraints = new ArrayList<>(2);
|
||||||
|
private ISubCompletionEngine customContentAssistant = null;
|
||||||
|
|
||||||
public boolean isSequenceable() {
|
public boolean isSequenceable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ISubCompletionEngine getCustomContentAssistant() {
|
||||||
|
return customContentAssistant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractType setCustomContentAssistant(ISubCompletionEngine customContentAssistant) {
|
||||||
|
this.customContentAssistant = customContentAssistant;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public YType inferMoreSpecificType(DynamicSchemaContext dc) {
|
public YType inferMoreSpecificType(DynamicSchemaContext dc) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.schema;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
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.schema.constraints.Constraint;
|
||||||
|
|
||||||
@@ -47,4 +48,6 @@ public interface YTypeUtil {
|
|||||||
*/
|
*/
|
||||||
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
|
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
|
||||||
List<Constraint> getConstraints(YType type);
|
List<Constraint> getConstraints(YType type);
|
||||||
|
|
||||||
|
ISubCompletionEngine getCustomContentAssistant(YType type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
|
||||||
|
sp_cleanup.add_default_serial_version_id=true
|
||||||
|
sp_cleanup.add_generated_serial_version_id=false
|
||||||
|
sp_cleanup.add_missing_annotations=true
|
||||||
|
sp_cleanup.add_missing_deprecated_annotations=true
|
||||||
|
sp_cleanup.add_missing_methods=false
|
||||||
|
sp_cleanup.add_missing_nls_tags=false
|
||||||
|
sp_cleanup.add_missing_override_annotations=true
|
||||||
|
sp_cleanup.add_missing_override_annotations_interface_methods=true
|
||||||
|
sp_cleanup.add_serial_version_id=false
|
||||||
|
sp_cleanup.always_use_blocks=true
|
||||||
|
sp_cleanup.always_use_parentheses_in_expressions=false
|
||||||
|
sp_cleanup.always_use_this_for_non_static_field_access=false
|
||||||
|
sp_cleanup.always_use_this_for_non_static_method_access=false
|
||||||
|
sp_cleanup.convert_functional_interfaces=false
|
||||||
|
sp_cleanup.convert_to_enhanced_for_loop=false
|
||||||
|
sp_cleanup.correct_indentation=false
|
||||||
|
sp_cleanup.format_source_code=false
|
||||||
|
sp_cleanup.format_source_code_changes_only=false
|
||||||
|
sp_cleanup.insert_inferred_type_arguments=false
|
||||||
|
sp_cleanup.make_local_variable_final=true
|
||||||
|
sp_cleanup.make_parameters_final=false
|
||||||
|
sp_cleanup.make_private_fields_final=true
|
||||||
|
sp_cleanup.make_type_abstract_if_missing_method=false
|
||||||
|
sp_cleanup.make_variable_declarations_final=false
|
||||||
|
sp_cleanup.never_use_blocks=false
|
||||||
|
sp_cleanup.never_use_parentheses_in_expressions=true
|
||||||
|
sp_cleanup.on_save_use_additional_actions=true
|
||||||
|
sp_cleanup.organize_imports=false
|
||||||
|
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
|
||||||
|
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
|
||||||
|
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
|
||||||
|
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
|
||||||
|
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
|
||||||
|
sp_cleanup.remove_private_constructors=true
|
||||||
|
sp_cleanup.remove_redundant_type_arguments=false
|
||||||
|
sp_cleanup.remove_trailing_whitespaces=true
|
||||||
|
sp_cleanup.remove_trailing_whitespaces_all=true
|
||||||
|
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
|
||||||
|
sp_cleanup.remove_unnecessary_casts=true
|
||||||
|
sp_cleanup.remove_unnecessary_nls_tags=false
|
||||||
|
sp_cleanup.remove_unused_imports=false
|
||||||
|
sp_cleanup.remove_unused_local_variables=false
|
||||||
|
sp_cleanup.remove_unused_private_fields=true
|
||||||
|
sp_cleanup.remove_unused_private_members=false
|
||||||
|
sp_cleanup.remove_unused_private_methods=true
|
||||||
|
sp_cleanup.remove_unused_private_types=true
|
||||||
|
sp_cleanup.sort_members=false
|
||||||
|
sp_cleanup.sort_members_all=false
|
||||||
|
sp_cleanup.use_anonymous_class_creation=false
|
||||||
|
sp_cleanup.use_blocks=false
|
||||||
|
sp_cleanup.use_blocks_only_for_return_and_throw=false
|
||||||
|
sp_cleanup.use_lambda=true
|
||||||
|
sp_cleanup.use_parentheses_in_expressions=false
|
||||||
|
sp_cleanup.use_this_for_non_static_field_access=false
|
||||||
|
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
|
||||||
|
sp_cleanup.use_this_for_non_static_method_access=false
|
||||||
|
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
|
||||||
@@ -45,10 +45,12 @@ public class ManifestYmlSchema implements YamlSchema {
|
|||||||
|
|
||||||
private static final String HEALTH_CHECK_HTTP_ENDPOINT_PROP = "health-check-http-endpoint";
|
private static final String HEALTH_CHECK_HTTP_ENDPOINT_PROP = "health-check-http-endpoint";
|
||||||
private static final String HEALTH_CHECK_TYPE_PROP = "health-check-type";
|
private static final String HEALTH_CHECK_TYPE_PROP = "health-check-type";
|
||||||
|
|
||||||
private final AbstractType TOPLEVEL_TYPE;
|
private final AbstractType TOPLEVEL_TYPE;
|
||||||
private final YTypeUtil TYPE_UTIL;
|
private final YTypeUtil TYPE_UTIL;
|
||||||
|
|
||||||
|
public final AbstractType t_route_string;
|
||||||
|
|
||||||
private static final Set<String> TOPLEVEL_EXCLUDED = ImmutableSet.of(
|
private static final Set<String> TOPLEVEL_EXCLUDED = ImmutableSet.of(
|
||||||
"name", "host", "hosts"
|
"name", "host", "hosts"
|
||||||
);
|
);
|
||||||
@@ -65,7 +67,7 @@ public class ManifestYmlSchema implements YamlSchema {
|
|||||||
if (markerNode != null) {
|
if (markerNode != null) {
|
||||||
String healthCheckType = getEffectiveHealthCheckType(ast, dc.getPath(), node);
|
String healthCheckType = getEffectiveHealthCheckType(ast, dc.getPath(), node);
|
||||||
if (!"http".equals(healthCheckType)) {
|
if (!"http".equals(healthCheckType)) {
|
||||||
problems.accept(YamlSchemaProblems.problem(ManifestYamlSchemaProblemsTypes.IGNORED_PROPERTY,
|
problems.accept(YamlSchemaProblems.problem(ManifestYamlSchemaProblemsTypes.IGNORED_PROPERTY,
|
||||||
"This has no effect unless `"+HEALTH_CHECK_TYPE_PROP+"` is `http` (but it is currently set to `"+healthCheckType+"`)", markerNode));
|
"This has no effect unless `"+HEALTH_CHECK_TYPE_PROP+"` is `http` (but it is currently set to `"+healthCheckType+"`)", markerNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,14 +144,12 @@ public class ManifestYmlSchema implements YamlSchema {
|
|||||||
YType t_string = f.yatomic("String");
|
YType t_string = f.yatomic("String");
|
||||||
YType t_strings = f.yseq(t_string);
|
YType t_strings = f.yseq(t_string);
|
||||||
|
|
||||||
// "routes" has nested required property "route":
|
t_route_string = f.yatomic("RouteUri")
|
||||||
// routes:
|
.parseWith(new RouteValueParser(YTypeFactory.valuesFromHintProvider(domainsProvider)))
|
||||||
// - route: someroute.io
|
.setCustomContentAssistant(new RouteContentAssistant(domainsProvider, this));
|
||||||
|
|
||||||
YBeanType route = f.ybean("Route");
|
YBeanType route = f.ybean("Route");
|
||||||
YAtomicType t_route_string = f.yatomic("route");
|
|
||||||
route.addProperty(f.yprop("route", t_route_string).isRequired(true));
|
route.addProperty(f.yprop("route", t_route_string).isRequired(true));
|
||||||
t_route_string.parseWith(new RouteValueParser(YTypeFactory.valuesFromHintProvider(domainsProvider)));
|
|
||||||
|
|
||||||
YAtomicType t_memory = f.yatomic("Memory");
|
YAtomicType t_memory = f.yatomic("Memory");
|
||||||
t_memory.addHints("256M", "512M", "1024M");
|
t_memory.addHints("256M", "512M", "1024M");
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.manifest.yaml;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||||
|
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||||
|
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||||
|
import org.springframework.ide.vscode.commons.util.Log;
|
||||||
|
import org.springframework.ide.vscode.commons.yaml.completion.CompletionFactory;
|
||||||
|
import org.springframework.ide.vscode.commons.yaml.schema.ISubCompletionEngine;
|
||||||
|
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom content assistant for making 'domains' suggestions inside of a RouteUri.
|
||||||
|
*
|
||||||
|
* @author Kris De Volder
|
||||||
|
*/
|
||||||
|
public class RouteContentAssistant implements ISubCompletionEngine {
|
||||||
|
|
||||||
|
private static final Pattern STOP_AP = Pattern.compile("[#:/]");
|
||||||
|
|
||||||
|
private Callable<Collection<YValueHint>> domainsProvider;
|
||||||
|
|
||||||
|
private ManifestYmlSchema schema;
|
||||||
|
|
||||||
|
public RouteContentAssistant(Callable<Collection<YValueHint>> domainsProvider, ManifestYmlSchema manifestYmlSchema) {
|
||||||
|
this.domainsProvider = domainsProvider;
|
||||||
|
this.schema = manifestYmlSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ICompletionProposal> getCompletions(CompletionFactory f, DocumentRegion region, int offset) {
|
||||||
|
// offset = 0 means: "<*>abc"
|
||||||
|
// offset = 1 means: "a<*>bc"
|
||||||
|
// offset = 2 means: "ab<*>c"
|
||||||
|
try {
|
||||||
|
region = chopEnd(region, offset);
|
||||||
|
// region = "abc"
|
||||||
|
// offset = 3 means: "abc<*>"
|
||||||
|
// So offset > 3 means we are 'our of bounds for this content assistant
|
||||||
|
if (offset<=region.length()) {
|
||||||
|
String[] queries = getQueries(region.subSequence(0, offset));
|
||||||
|
Collection<YValueHint> domains = domainsProvider.call();
|
||||||
|
List<ICompletionProposal> proposals = new ArrayList<>();
|
||||||
|
for (YValueHint domain : domains) {
|
||||||
|
for (String query : queries) {
|
||||||
|
double score = FuzzyMatcher.matchScore(query, domain.getValue());
|
||||||
|
if (score!=0.0) {
|
||||||
|
proposals.add(createProposal(f, region, offset, query, score, domain));
|
||||||
|
break; //break here so we select the first (i.e. longest) query that matches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proposals;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.log(e);
|
||||||
|
//Ignore. This is somewhat expected. Stuff can go wrong resolving the domains
|
||||||
|
// and CA engine just doesn't provide CA in that case.
|
||||||
|
}
|
||||||
|
return ImmutableList.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getQueries(DocumentRegion region) {
|
||||||
|
// Example of what this should do
|
||||||
|
// region = "foo.bar.com"
|
||||||
|
// We have to make a guess which part of this is the host-name and which part is domain-name
|
||||||
|
// The split could be either at the very start, or at any one of the '.' chars.
|
||||||
|
// So the possible queries are as follows:
|
||||||
|
// - "foo.bar.com"
|
||||||
|
// - "bar.com"
|
||||||
|
// - "com"
|
||||||
|
region = region.trimStart();
|
||||||
|
DocumentRegion[] pieces = region.split('.');
|
||||||
|
String[] queries = new String[pieces.length];
|
||||||
|
for (int i = pieces.length-1; i >= 0; i--) {
|
||||||
|
if (i==pieces.length-1) {
|
||||||
|
queries[i] = pieces[i].toString();
|
||||||
|
} else {
|
||||||
|
queries[i] = pieces[i] + "." + queries[i+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICompletionProposal createProposal(CompletionFactory f, DocumentRegion region, int offset, String query, double score, YValueHint domain) {
|
||||||
|
DocumentEdits edits = new DocumentEdits(region.getDocument());
|
||||||
|
region = region.subSequence(offset - query.length());
|
||||||
|
boolean needSpace = region.textBefore(1).charAt(0)==':'; //Add extra space after ':' if needed!
|
||||||
|
edits.replace(region.getStart(), region.getEnd(), needSpace ? " "+domain.getValue() : domain.getValue());
|
||||||
|
return f.valueProposal(domain.getValue(), query, domain.getLabel(), schema.t_route_string,
|
||||||
|
domain.getDocumentation(), score, edits, schema.getTypeUtil());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chop-off the uninteresting parts of region (whitespace, comments, anything after the first ':' or '/'
|
||||||
|
*/
|
||||||
|
private DocumentRegion chopEnd(DocumentRegion region, int offset) {
|
||||||
|
Matcher matcher = STOP_AP.matcher(region);
|
||||||
|
if (matcher.find()) {
|
||||||
|
region = region.subSequence(0, matcher.start());
|
||||||
|
}
|
||||||
|
if (offset<=region.getLength()) {
|
||||||
|
//We want to trim whitespace of the end, but must be careful not to trim past the offset
|
||||||
|
DocumentRegion trimmed = region.trimEnd();
|
||||||
|
if (offset<=trimmed.length()) {
|
||||||
|
return trimmed;
|
||||||
|
} else {
|
||||||
|
return region.subSequence(0, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -463,7 +463,7 @@ public class ManifestYamlEditorTest {
|
|||||||
);
|
);
|
||||||
editor.assertProblems(/*NONE*/);
|
editor.assertProblems(/*NONE*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void reconcileHealthHttpEndPointIgnoredWarning() throws Exception {
|
@Test public void reconcileHealthHttpEndPointIgnoredWarning() throws Exception {
|
||||||
Editor editor;
|
Editor editor;
|
||||||
Diagnostic problem;
|
Diagnostic problem;
|
||||||
@@ -483,7 +483,7 @@ public class ManifestYamlEditorTest {
|
|||||||
" health-check-http-endpoint: /health"
|
" health-check-http-endpoint: /health"
|
||||||
);
|
);
|
||||||
editor.assertProblems(/*NONE*/);
|
editor.assertProblems(/*NONE*/);
|
||||||
|
|
||||||
editor = harness.newEditor(
|
editor = harness.newEditor(
|
||||||
"applications:\n" +
|
"applications:\n" +
|
||||||
"- name: my-app\n" +
|
"- name: my-app\n" +
|
||||||
@@ -499,7 +499,7 @@ public class ManifestYamlEditorTest {
|
|||||||
" health-check-http-endpoint: /health"
|
" health-check-http-endpoint: /health"
|
||||||
);
|
);
|
||||||
editor.assertProblems(/*NONE*/);
|
editor.assertProblems(/*NONE*/);
|
||||||
|
|
||||||
editor = harness.newEditor(
|
editor = harness.newEditor(
|
||||||
"health-check-type: http\n" +
|
"health-check-type: http\n" +
|
||||||
"applications:\n" +
|
"applications:\n" +
|
||||||
@@ -508,12 +508,12 @@ public class ManifestYamlEditorTest {
|
|||||||
" health-check-http-endpoint: /health"
|
" health-check-http-endpoint: /health"
|
||||||
);
|
);
|
||||||
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
|
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
|
||||||
|
|
||||||
editor = harness.newEditor(
|
editor = harness.newEditor(
|
||||||
"health-check-http-endpoint: /health"
|
"health-check-http-endpoint: /health"
|
||||||
);
|
);
|
||||||
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `port`)");
|
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `port`)");
|
||||||
|
|
||||||
editor = harness.newEditor(
|
editor = harness.newEditor(
|
||||||
"health-check-type: process\n" +
|
"health-check-type: process\n" +
|
||||||
"health-check-http-endpoint: /health"
|
"health-check-http-endpoint: /health"
|
||||||
@@ -662,19 +662,19 @@ public class ManifestYamlEditorTest {
|
|||||||
assertEquals("an-org : a-space [test.io]", c.getDocumentation());
|
assertEquals("an-org : a-space [test.io]", c.getDocumentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test public void domainReconcile() throws Exception {
|
@Test public void domainReconcile() throws Exception {
|
||||||
List<CFDomain> domains = ImmutableList.of(mockDomain("one.com"), mockDomain("two.com"));
|
List<CFDomain> domains = ImmutableList.of(mockDomain("one.com"), mockDomain("two.com"));
|
||||||
when(cloudfoundry.client.getDomains()).thenReturn(domains);
|
when(cloudfoundry.client.getDomains()).thenReturn(domains);
|
||||||
Editor editor;
|
Editor editor;
|
||||||
Diagnostic p;
|
Diagnostic p;
|
||||||
|
|
||||||
editor = harness.newEditor(
|
editor = harness.newEditor(
|
||||||
"domain: bad.com"
|
"domain: bad.com"
|
||||||
);
|
);
|
||||||
p = editor.assertProblems("bad.com|unknown 'Domain'. Valid values are: [one.com, two.com]").get(0);
|
p = editor.assertProblems("bad.com|unknown 'Domain'. Valid values are: [one.com, two.com]").get(0);
|
||||||
assertEquals(DiagnosticSeverity.Warning, p.getSeverity());
|
assertEquals(DiagnosticSeverity.Warning, p.getSeverity());
|
||||||
|
|
||||||
editor= harness.newEditor(
|
editor= harness.newEditor(
|
||||||
"domains:\n" +
|
"domains:\n" +
|
||||||
"- one.com\n" +
|
"- one.com\n" +
|
||||||
@@ -1282,7 +1282,7 @@ public class ManifestYamlEditorTest {
|
|||||||
" - route: host.springsource.org\n");
|
" - route: host.springsource.org\n");
|
||||||
editor.assertProblems();
|
editor.assertProblems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void dashedContentAssistForServices() throws Exception {
|
@Test public void dashedContentAssistForServices() throws Exception {
|
||||||
Editor editor;
|
Editor editor;
|
||||||
CFServiceInstance service = mock(CFServiceInstance.class);
|
CFServiceInstance service = mock(CFServiceInstance.class);
|
||||||
@@ -1401,6 +1401,183 @@ public class ManifestYamlEditorTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void contentAssistInsideRouteDomain() throws Exception {
|
||||||
|
Editor editor;
|
||||||
|
|
||||||
|
ImmutableList<CFDomain> domains = ImmutableList.of(
|
||||||
|
mockDomain("cfapps.io"),
|
||||||
|
mockDomain("dsyer.com")
|
||||||
|
);
|
||||||
|
when(cloudfoundry.client.getDomains()).thenReturn(domains);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route:<*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: cfapps.io<*>"
|
||||||
|
, // ==============
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: dsyer.com<*>"
|
||||||
|
);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: <*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: cfapps.io<*>"
|
||||||
|
, // ==============
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: dsyer.com<*>"
|
||||||
|
);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: dsyer.<*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: dsyer.com<*>"
|
||||||
|
, // ==============
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: dsyer.cfapps.io<*>"
|
||||||
|
);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.<*>"
|
||||||
|
);
|
||||||
|
CompletionItem c = editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.cfapps.io<*>"
|
||||||
|
, // ==============
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.dsyer.com<*>"
|
||||||
|
).get(0);
|
||||||
|
assertEquals("cfapps.io", c.getLabel());
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: foo.bar.<*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: foo.bar.cfapps.io<*>"
|
||||||
|
, // ==============
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: foo.bar.dsyer.com<*>"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// no content assist inside of path or port section of route:
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: foo.bar.com/<*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(/*NONE*/);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: foo.bar.com:<*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(/*NONE*/);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: foo.bar.com:7777/blah<*>"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(/*NONE*/);
|
||||||
|
|
||||||
|
// Martin's most fancy example:
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.cf<*>pps.io/superpath\n" +
|
||||||
|
" memory: 1024M\n"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.cfapps.io<*>/superpath\n" +
|
||||||
|
" memory: 1024M\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
//Kris's variants of Martin's example:
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.ds<*>pps.io/superpath\n" +
|
||||||
|
" memory: 1024M\n"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.dsyer.com<*>/superpath\n" +
|
||||||
|
" memory: 1024M\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
editor = harness.newEditor(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.ds<*>pps.io:8888/superpath\n" +
|
||||||
|
" memory: 1024M\n"
|
||||||
|
);
|
||||||
|
editor.assertCompletions(
|
||||||
|
"applications:\n" +
|
||||||
|
"- name: test\n" +
|
||||||
|
" routes:\n" +
|
||||||
|
" - route: test.dsyer.com<*>:8888/superpath\n" +
|
||||||
|
" memory: 1024M\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private List<CompletionItem> assertCompletions(String textBefore, String... textAfter) throws Exception {
|
private List<CompletionItem> assertCompletions(String textBefore, String... textAfter) throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user