Autoinsert single required proerty after '- '.
This commit is contained in:
@@ -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(' ');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ICompletionProposal> getValueCompletions(YamlDocument doc, SNode node, int offset, String query) {
|
||||
|
||||
@@ -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> T getSingle(Stream<T> stream) {
|
||||
ArrayList<T> 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
|
||||
*/
|
||||
|
||||
@@ -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<JobModel> 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) {
|
||||
|
||||
@@ -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" +
|
||||
" <*>"
|
||||
|
||||
@@ -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<CompletionItem> assertCompletions(String textBefore, String... textAfter) throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user