From 5ccb3e5f229a6aa8d67cc489f8e12f8bcd5bc9cc Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 28 Apr 2017 18:20:34 -0700 Subject: [PATCH] Content assist for 'domains' inside of route-strings --- .../completion/DocumentEdits.java | 3 +- .../ide/vscode/commons/util/FuzzyMatcher.java | 11 +- .../completion/AbstractYamlAssistContext.java | 14 ++ .../completion/DefaultCompletionFactory.java | 2 - .../completion/TransformedCompletion.java | 4 +- .../yaml/completion/YTypeAssistContext.java | 8 + .../yaml/schema/ISubCompletionEngine.java | 38 ++++ .../commons/yaml/schema/YTypeFactory.java | 15 ++ .../vscode/commons/yaml/schema/YTypeUtil.java | 3 + .../.settings/org.eclipse.jdt.ui.prefs | 59 ++++++ .../manifest/yaml/ManifestYmlSchema.java | 14 +- .../manifest/yaml/RouteContentAssistant.java | 133 ++++++++++++ .../manifest/yaml/ManifestYamlEditorTest.java | 195 +++++++++++++++++- 13 files changed, 473 insertions(+), 26 deletions(-) create mode 100644 headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ISubCompletionEngine.java create mode 100644 headless-services/manifest-yaml-language-server/.settings/org.eclipse.jdt.ui.prefs create mode 100644 headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteContentAssistant.java diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java index 54e19550c..98fdbdf79 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.commons.languageserver.completion; import java.util.ArrayList; +import java.util.List; import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -295,7 +296,7 @@ public class DocumentEdits implements ProposalApplier { } } - private ArrayList edits = new ArrayList(); + private List edits = new ArrayList(); private IDocument doc; public DocumentEdits(IDocument doc) { diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FuzzyMatcher.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FuzzyMatcher.java index 5fb77b6e8..a13f36166 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FuzzyMatcher.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FuzzyMatcher.java @@ -58,18 +58,17 @@ public class FuzzyMatcher { //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 } - 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) { //gaps == 0 means a prefix match, ignore 'skips' at end of String and just sort // alphabetic (see STS-4049) - double badness = 0.1; // all scored equally, assumes using a 'stable' sorter. - return -badness; //higher is better + return 0.5+pattern.length(); //all scored equally, assumes using a 'stable' sorter. } else { - double badness = 1+gaps + skips/10000.0; // higher is worse - return -badness; //higher is better + double badness = 1+gaps + skips/1000.0; // higher is worse + return 1.0/badness + pattern.length(); //higher is better } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AbstractYamlAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AbstractYamlAssistContext.java index a0e05e9b7..eeff8901c 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AbstractYamlAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AbstractYamlAssistContext.java @@ -56,6 +56,20 @@ public abstract class AbstractYamlAssistContext implements YamlAssistContext { 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) { //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. diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java index aa494b63e..94832dd65 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java @@ -84,7 +84,6 @@ public class DefaultCompletionFactory implements CompletionFactory { public class ValueProposal extends ScoreableProposal { private String value; - //private String query; private String label; private YType type; 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) { this.value = value; - //this.query = query; this.label = label; this.type = type; this.docs = docs; diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java index 601066b4b..44d39b248 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java @@ -32,7 +32,9 @@ public abstract class TransformedCompletion extends ScoreableProposal { this.original = proposal; } - protected abstract String tranformLabel(String originalLabel); + protected String tranformLabel(String originalLabel) { + return originalLabel; + } protected abstract DocumentEdits transformEdit(DocumentEdits textEdit); @Override diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index ef064892e..4a28c7ef9 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -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.YamlPathSegmentType; 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.YType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; @@ -84,6 +85,13 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { @Override public Collection 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); List valueCompletions = getValueCompletions(doc, node, offset, query); if (!valueCompletions.isEmpty()) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ISubCompletionEngine.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ISubCompletionEngine.java new file mode 100644 index 000000000..e9c59fb79 --- /dev/null +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ISubCompletionEngine.java @@ -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 getCompletions(CompletionFactory f, DocumentRegion region, int offset); + + +} diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index a33d7da92..c1a4beafa 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -224,6 +224,11 @@ public class YTypeFactory { public List getConstraints(YType type) { return ((AbstractType)type).getConstraints(); } + + @Override + public ISubCompletionEngine getCustomContentAssistant(YType type) { + return ((AbstractType)type).getCustomContentAssistant(); + } }; ///////////////////////////////////////////////////////////////////////////////////// @@ -239,11 +244,21 @@ public class YTypeFactory { private Map cachedPropertyMap; private SchemaContextAware>> hintProvider; private List constraints = new ArrayList<>(2); + private ISubCompletionEngine customContentAssistant = null; public boolean isSequenceable() { return false; } + public ISubCompletionEngine getCustomContentAssistant() { + return customContentAssistant; + } + + public AbstractType setCustomContentAssistant(ISubCompletionEngine customContentAssistant) { + this.customContentAssistant = customContentAssistant; + return this; + } + public YType inferMoreSpecificType(DynamicSchemaContext dc) { return this; } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java index a78845a29..0f3bb1b4e 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.schema; import java.util.List; 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.yaml.schema.constraints.Constraint; @@ -47,4 +48,6 @@ public interface YTypeUtil { */ YType inferMoreSpecificType(YType type, DynamicSchemaContext dc); List getConstraints(YType type); + + ISubCompletionEngine getCustomContentAssistant(YType type); } diff --git a/headless-services/manifest-yaml-language-server/.settings/org.eclipse.jdt.ui.prefs b/headless-services/manifest-yaml-language-server/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..68f26e829 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/.settings/org.eclipse.jdt.ui.prefs @@ -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 diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java index 50fd4d213..5e7dd6c02 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -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_TYPE_PROP = "health-check-type"; - + private final AbstractType TOPLEVEL_TYPE; private final YTypeUtil TYPE_UTIL; + public final AbstractType t_route_string; + private static final Set TOPLEVEL_EXCLUDED = ImmutableSet.of( "name", "host", "hosts" ); @@ -65,7 +67,7 @@ public class ManifestYmlSchema implements YamlSchema { if (markerNode != null) { String healthCheckType = getEffectiveHealthCheckType(ast, dc.getPath(), node); 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)); } } @@ -142,14 +144,12 @@ public class ManifestYmlSchema implements YamlSchema { YType t_string = f.yatomic("String"); YType t_strings = f.yseq(t_string); - // "routes" has nested required property "route": - // routes: - // - route: someroute.io + t_route_string = f.yatomic("RouteUri") + .parseWith(new RouteValueParser(YTypeFactory.valuesFromHintProvider(domainsProvider))) + .setCustomContentAssistant(new RouteContentAssistant(domainsProvider, this)); YBeanType route = f.ybean("Route"); - YAtomicType t_route_string = f.yatomic("route"); 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"); t_memory.addHints("256M", "512M", "1024M"); diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteContentAssistant.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteContentAssistant.java new file mode 100644 index 000000000..5422d892d --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteContentAssistant.java @@ -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> domainsProvider; + + private ManifestYmlSchema schema; + + public RouteContentAssistant(Callable> domainsProvider, ManifestYmlSchema manifestYmlSchema) { + this.domainsProvider = domainsProvider; + this.schema = manifestYmlSchema; + } + + @Override + public List 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 domains = domainsProvider.call(); + List 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; + } + +} diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java index 066935510..f57dc7488 100644 --- a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -463,7 +463,7 @@ public class ManifestYamlEditorTest { ); editor.assertProblems(/*NONE*/); } - + @Test public void reconcileHealthHttpEndPointIgnoredWarning() throws Exception { Editor editor; Diagnostic problem; @@ -483,7 +483,7 @@ public class ManifestYamlEditorTest { " health-check-http-endpoint: /health" ); editor.assertProblems(/*NONE*/); - + editor = harness.newEditor( "applications:\n" + "- name: my-app\n" + @@ -499,7 +499,7 @@ public class ManifestYamlEditorTest { " health-check-http-endpoint: /health" ); editor.assertProblems(/*NONE*/); - + editor = harness.newEditor( "health-check-type: http\n" + "applications:\n" + @@ -508,12 +508,12 @@ public class ManifestYamlEditorTest { " 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 = harness.newEditor( "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 = harness.newEditor( "health-check-type: process\n" + "health-check-http-endpoint: /health" @@ -662,19 +662,19 @@ public class ManifestYamlEditorTest { assertEquals("an-org : a-space [test.io]", c.getDocumentation()); } - + @Test public void domainReconcile() throws Exception { List domains = ImmutableList.of(mockDomain("one.com"), mockDomain("two.com")); when(cloudfoundry.client.getDomains()).thenReturn(domains); Editor editor; Diagnostic p; - + editor = harness.newEditor( "domain: bad.com" ); p = editor.assertProblems("bad.com|unknown 'Domain'. Valid values are: [one.com, two.com]").get(0); assertEquals(DiagnosticSeverity.Warning, p.getSeverity()); - + editor= harness.newEditor( "domains:\n" + "- one.com\n" + @@ -1282,7 +1282,7 @@ public class ManifestYamlEditorTest { " - route: host.springsource.org\n"); editor.assertProblems(); } - + @Test public void dashedContentAssistForServices() throws Exception { Editor editor; CFServiceInstance service = mock(CFServiceInstance.class); @@ -1401,6 +1401,183 @@ public class ManifestYamlEditorTest { ); } + @Test public void contentAssistInsideRouteDomain() throws Exception { + Editor editor; + + ImmutableList 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 assertCompletions(String textBefore, String... textAfter) throws Exception {