From d47fa48e435e2a0b8337bd3099f3a132606cfa19 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Wed, 26 Apr 2017 16:50:56 -0700 Subject: [PATCH] Autoinsert single required proerty after '- '. --- .../yaml/completion/AppendTextBuilder.java | 88 +++++++++++++++++++ .../yaml/completion/YTypeAssistContext.java | 24 ++--- .../ide/vscode/commons/yaml/util/Streams.java | 13 +++ .../ide/vscode/concourse/ConcourseModel.java | 17 ++-- .../vscode/concourse/ConcourseEditorTest.java | 6 +- .../manifest/yaml/ManifestYamlEditorTest.java | 32 +++++-- 6 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java new file mode 100644 index 000000000..59bd5a95a --- /dev/null +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * 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.completion; + +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.util.Streams; +import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil; + +/** + * Helper for building the auto-insertion text that is appended after a + * property completion, based on the {@link YType} that is expected for the + * property value. + */ +public class AppendTextBuilder { + + private YTypeUtil typeUtil; + + public AppendTextBuilder(YTypeUtil typeUtil) { + this.typeUtil = typeUtil; + } + + public String buildFor(YType type) { + //Note that caller is responsible for proper indentation + //to align with the parent. The strings created here only need to contain + //indentation spaces to indent *more* than the parent node. + + //Nevertheless, to support building sophisticated appendText recursively, + //we do need to keep track of the relative indentation level internally + //(or potentially do a lot of string copying) + + StringBuilder text = new StringBuilder(); + build(type, 0, text); + return text.toString(); + } + + private void build(YType type, int indent, StringBuilder text) { + if (type==null) { + //Assume its some kind of pojo bean + newline(text, indent+YamlIndentUtil.INDENT_BY); + } else if (typeUtil.isMap(type)) { + //ready to enter nested map key on next line + newline(text, indent+YamlIndentUtil.INDENT_BY); + } else if (typeUtil.isSequencable(type)) { + //ready to enter sequence element on next line + newline(text, indent); + text.append("- "); + singleRequiredProperty(typeUtil.getDomainType(type), indent+2, text); + //Yes using 2 here instead of YamlIndentUtil.INDENT_BY is deliberate. It's the same value (now), + // but the 2 used here is the width of the "- " which should determine nested indent level for things to + // line up properly. + } else if (typeUtil.isAtomic(type)) { + //ready to enter whatever on the same line + text.append(" "); + } else { + newline(text, indent+YamlIndentUtil.INDENT_BY); + } + } + + private void singleRequiredProperty(YType type, int indent, StringBuilder text) { + if (type!=null) { + YTypedProperty requireProp = Streams.getSingle(typeUtil.getProperties(type).stream() + .filter(p -> p.isRequired())); + if (requireProp!=null) { + text.append(requireProp.getName()); + text.append(':'); + build(requireProp.getType(), indent+YamlIndentUtil.INDENT_BY, text); + } + } + } + + private void newline(StringBuilder text, int indent) { + text.append("\n"); + for (int i = 0; i < indent; i++) { + text.append(' '); + } + } + +} 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 42ee34887..e3518afc2 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 @@ -16,6 +16,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode; +import org.springframework.ide.vscode.commons.yaml.util.Streams; import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil; import org.springframework.ide.vscode.commons.yaml.completion.DefaultCompletionFactory.ValueProposal; @@ -140,30 +142,14 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { return Collections.emptyList(); } + + /** * Computes the text that should be appended at the end of a completion * proposal depending on what type of value is expected. */ protected String appendTextFor(YType type) { - //Note that proper indentation after each \n" is added automatically - //to align with the parent. The strings created here only need to contain - //indentation spaces to indent *more* than the parent node. - if (type==null) { - //Assume its some kind of pojo bean - return "\n"+YamlIndentUtil.INDENT_STR; - } else if (typeUtil.isMap(type)) { - //ready to enter nested map key on next line - return "\n"+YamlIndentUtil.INDENT_STR; - } if (typeUtil.isSequencable(type)) { - //ready to enter sequence element on next line - return "\n- "; - } else if (typeUtil.isAtomic(type)) { - //ready to enter whatever on the same line - return " "; - } else { - //Assume its some kind of pojo bean - return "\n"+YamlIndentUtil.INDENT_STR; - } + return new AppendTextBuilder(typeUtil).buildFor(type); } private List getValueCompletions(YamlDocument doc, SNode node, int offset, String query) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java index 571f2acad..f02993420 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java @@ -11,10 +11,23 @@ package org.springframework.ide.vscode.commons.yaml.util; +import java.util.ArrayList; +import java.util.stream.Collectors; import java.util.stream.Stream; public class Streams { + /** + * Returns the element in the stream, if the stream has exactly one element. + * Otherwise returns null + */ + public static T getSingle(Stream stream) { + ArrayList els = stream + .limit(2) //Don't need more than 2 to know there is more than 1 + .collect(Collectors.toCollection(() -> new ArrayList<>(2))); + return els.size()==1 ? els.get(0) : null; + } + /** * Like java.util.Stream.of but returns Stream.empty if the element is null */ diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index d4cbb1ccf..aff26de38 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -102,16 +102,13 @@ public class ConcourseModel { * Get the job with given name. If there is no such job, or if there is more than one, this will return null. */ private JobModel getJob(IDocument doc, String jobName) { - List jobs = getFromAst(doc, ast -> - JOBS_PATH.traverseAmbiguously(ast) - .filter(node -> jobName.equals(NodeUtil.getScalarProperty(node, "name"))) - .map(JobModel::new) - ) - .limit(2) //We only need 2 elements at most to determine if there is more than one - .collect(Collectors.toList()); - return jobs.size()==1 - ? jobs.get(0) - : null; + return Streams.getSingle( + getFromAst(doc, ast -> + JOBS_PATH.traverseAmbiguously(ast) + .filter(node -> jobName.equals(NodeUtil.getScalarProperty(node, "name"))) + .map(JobModel::new) + ) + ); } public StepModel newStep(Node _node) { diff --git a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index c805e4372..ee0967cec 100644 --- a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -452,7 +452,7 @@ public class ConcourseEditorTest { editor = harness.newEditor(CURSOR); editor.assertCompletions( "groups:\n" + - "- <*>" + "- name: <*>" , // -------------- "jobs:\n" + "- <*>" @@ -2552,10 +2552,10 @@ public class ConcourseEditorTest { " <*>" , "inputs:\n" + - "- <*>" + "- name: <*>" , "outputs:\n" + - "- <*>" + "- name: <*>" , "params:\n" + " <*>" 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 72faa9476..7f2e44941 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 @@ -233,6 +233,12 @@ public class ManifestYamlEditorTest { editor = harness.newEditor("appl<*>"); editor.assertCompletions( "applications:\n"+ + "- name: <*>" + ); + + editor = harness.newEditor("serv<*>"); + editor.assertCompletions( + "services:\n"+ "- <*>" ); } @@ -243,7 +249,7 @@ public class ManifestYamlEditorTest { editor = harness.newEditor("<*>"); editor.assertCompletions( "applications:\n"+ - "- <*>", + "- name: <*>", // --------------- "buildpack: <*>", // --------------- @@ -285,7 +291,7 @@ public class ManifestYamlEditorTest { "random-route: <*>", // --------------- "routes:\n"+ - "- <*>", + "- route: <*>", // --------------- "services:\n"+ "- <*>", @@ -366,7 +372,7 @@ public class ManifestYamlEditorTest { // --------------- "applications:\n" + "- routes:\n"+ - " - <*>", + " - route: <*>", // --------------- "applications:\n" + "- services:\n"+ @@ -678,7 +684,7 @@ public class ManifestYamlEditorTest { // --------------- "applications:\n" + "- routes:\n"+ - " - <*>", + " - route: <*>", // --------------- "applications:\n" + "- services:\n"+ @@ -770,7 +776,7 @@ public class ManifestYamlEditorTest { , // --------------------- "applications:\n" + "- routes:\n" + - " - <*>\n" + + " - route: <*>\n" + "- name: test" ,// --------------------- "applications:\n" + @@ -1243,6 +1249,22 @@ public class ManifestYamlEditorTest { ); } + @Test public void autoInsertRouteProperty() throws Exception { + Editor editor; + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " ro<*>" + ); + editor.assertCompletions(c -> c.getLabel().contains("routes"), + "applications:\n" + + "- name: foo\n" + + " routes:\n"+ + " - route: <*>" + ); + } + ////////////////////////////////////////////////////////////////////////////// private List assertCompletions(String textBefore, String... textAfter) throws Exception {