Cut down on the noise in content assist

Use a 'tiered' model for properties
 - primary property
 - requred
 - other

Only suggest completions for a tier if the properties in the preceding tier
are already defined.
This commit is contained in:
Kris De Volder
2017-05-05 15:18:36 -07:00
parent dab8b8c520
commit 3e7944d47d
14 changed files with 447 additions and 355 deletions

View File

@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.completions;
import org.eclipse.lsp4j.CompletionItemKind;
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.completion.ScoreableProposal;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.IDocument;

View File

@@ -19,9 +19,10 @@ public abstract class ScoreableProposal implements ICompletionProposal {
public static final double DEEMP_EXISTS = 0.1;
public static final double DEEMP_DEPRECATION = 0.2;
public static final double DEEMP_DASH_PROPOSAL = 0.5;
public static final double DEEMP_INDENTED_PROPOSAL = 1.0;
public static final double DEEMP_DEDENTED_PROPOSAL = 1.5;
public static final double DEEMP_NEXT_CONTEXT = 0.0;
public static final double DEEMP_INDENTED_PROPOSAL = 0.4;
public static final double DEEMP_DASH_PROPOSAL = 0.6;
public static final double DEEMP_DEDENTED_PROPOSAL = 0.8;
private static final double DEEMP_VALUE = 10_000; // should be large enough to move deemphasized stuff to bottom of list.
@@ -53,7 +54,7 @@ public abstract class ScoreableProposal implements ICompletionProposal {
}
@Override
public ScoreableProposal deemphasize(double howmuch) {
Assert.isLegal(howmuch>0.0);
Assert.isLegal(howmuch>=0.0);
deemphasizedBy+= howmuch*DEEMP_VALUE;
return this;
}

View File

@@ -10,12 +10,15 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.completion;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DASH_PROPOSAL;
import java.util.ArrayList;
import java.util.Collection;
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;
@@ -105,51 +108,71 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
int queryOffset = offset - query.length();
SNode contextNode = getContextNode();
DynamicSchemaContext dynamicCtxt = getSchemaContext();
List<YTypedProperty> properties = typeUtil.getProperties(type);
if (CollectionUtil.hasElements(properties)) {
ArrayList<ICompletionProposal> proposals = new ArrayList<>(properties.size());
List<YTypedProperty> allProperties = typeUtil.getProperties(type);
if (CollectionUtil.hasElements(allProperties)) {
List<List<YTypedProperty>> tieredProperties = sortIntoTiers(allProperties);
Set<String> definedProps = dynamicCtxt.getDefinedProperties();
for (YTypedProperty p : properties) {
String name = p.getName();
double score = FuzzyMatcher.matchScore(query, name);
if (score!=0) {
YamlPath relativePath = YamlPath.fromSimpleProperty(name);
YamlPathEdits edits = new YamlPathEdits(doc);
if (!definedProps.contains(name)) {
//property not yet defined
YType YType = p.getType();
edits.delete(queryOffset, query);
if (queryOffset>0 && !Character.isWhitespace(doc.getChar(queryOffset-1))) {
//See https://www.pivotaltracker.com/story/show/137722057
edits.insert(queryOffset, " ");
for (List<YTypedProperty> thisTier : tieredProperties) {
List<YTypedProperty> undefinedProps = thisTier.stream()
.filter(p -> !definedProps.contains(p.getName()))
.collect(Collectors.toList());
if (!undefinedProps.isEmpty()) {
List<ICompletionProposal> proposals = new ArrayList<>();
for (YTypedProperty p : undefinedProps) {
String name = p.getName();
double score = FuzzyMatcher.matchScore(query, name);
if (score!=0) {
YamlPath relativePath = YamlPath.fromSimpleProperty(name);
YamlPathEdits edits = new YamlPathEdits(doc);
YType YType = p.getType();
edits.delete(queryOffset, query);
if (queryOffset>0 && !Character.isWhitespace(doc.getChar(queryOffset-1))) {
//See https://www.pivotaltracker.com/story/show/137722057
edits.insert(queryOffset, " ");
}
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(YType));
proposals.add(completionFactory().beanProperty(doc.getDocument(),
contextPath.toPropString(), getType(),
query, p, score, edits, typeUtil)
);
}
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(YType));
proposals.add(completionFactory().beanProperty(doc.getDocument(),
contextPath.toPropString(), getType(),
query, p, score, edits, typeUtil)
);
} else {
// This piece below deactivated becuase moving cursor like this doesn't work in vscode
// //property already defined
// // instead of filtering, navigate to the place where its defined.
// deleteQueryAndLine(doc, query, queryOffset, edits);
// //Cast to SChildBearingNode cannot fail because otherwise definedProps would be the empty set.
// edits.createPath((SChildBearingNode) contextNode, relativePath, "");
// proposals.add(
// completionFactory().beanProperty(doc.getDocument(),
// contextPath.toPropString(), getType(),
// query, p, score, edits, typeUtil)
// .deemphasize(DEEMP_EXISTS) //deemphasize because it already exists
// );
}
return proposals;
}
}
return proposals;
}
return Collections.emptyList();
}
/**
* Divides a given list of properties into tiers of decreasing significance. Property tiering
* is a mechanism to reduce 'noise' in content assist proposals. Only properties of the
* first tier that some still undefined properties will be used to generate proposals.
* <p>
* This allows, for example, to only suggest a 'name' property when starting to define
* a new named entity. This is what a sane user would probably want, even though
* in theory they would be free to define the properties in any order they want.
*/
protected List<List<YTypedProperty>> sortIntoTiers(List<YTypedProperty> properties) {
if (properties.isEmpty()) {
//Nothing to sort
return ImmutableList.of();
} else {
ImmutableList.Builder<YTypedProperty> primary = ImmutableList.builder();
ImmutableList.Builder<YTypedProperty> required = ImmutableList.builder();
ImmutableList.Builder<YTypedProperty> other = ImmutableList.builder();
for (YTypedProperty p : properties) {
if (p.isPrimary()) {
primary.add(p);
} else if (p.isRequired()) {
required.add(p);
} else {
other.add(p);
}
}
return ImmutableList.of(primary.build(), required.build(), other.build());
}
}
/**
* Computes the text that should be appended at the end of a completion
@@ -379,7 +402,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
protected String tranformLabel(String originalLabel) {
return "- "+originalLabel;
}
}.deemphasize(0.5)
}.deemphasize(DEEMP_DASH_PROPOSAL)
);
}
return dashedCompletions;

View File

@@ -10,9 +10,6 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.completion;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DEDENTED_PROPOSAL;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_INDENTED_PROPOSAL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -45,6 +42,8 @@ import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*;
/**
* Implements {@link ICompletionEngine} for .yml file, based on a YamlAssistContextProvider
* which has to to be injected into engine via its constructor.
@@ -82,15 +81,17 @@ public class YamlCompletionEngine implements ICompletionEngine {
if (!doc.isCommented(offset)) {
SRootNode root = doc.getStructure();
SNode current = root.find(offset);
List<SNode> contextNodes = getContextNodes(doc, current, offset);
int cursorIndent = doc.getColumn(offset);
int nodeIndent = current.getIndent();
int baseIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
List<SNode> contextNodes = getContextNodes(doc, current, offset, baseIndent);
if (current.getNodeType()==SNodeType.RAW) {
//relaxed indentation
List<ICompletionProposal> completions = new ArrayList<>();
int cursorIndent = doc.getColumn(offset);
int nodeIndent = current.getIndent();
int baseIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
double deempasizeBy = 0.0;
for (SNode contextNode : contextNodes) {
completions.addAll(getRelaxedCompletions(offset, doc, current, contextNode, baseIndent));
completions.addAll(getRelaxedCompletions(offset, doc, current, contextNode, baseIndent, deempasizeBy));
deempasizeBy += ScoreableProposal.DEEMP_NEXT_CONTEXT;
}
return completions;
} else {
@@ -104,29 +105,27 @@ public class YamlCompletionEngine implements ICompletionEngine {
return Collections.emptyList();
}
protected Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, int baseIndent) {
protected Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, int baseIndent, double deempasizeBy) {
try {
return fixIndentations(getBaseCompletions(offset, doc, current, contextNode),
current, contextNode, baseIndent);
current, contextNode, baseIndent, deempasizeBy);
} catch (Exception e) {
Log.log(e);
}
return ImmutableList.of();
}
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode, SNode contextNode, int baseIndent) {
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode,
SNode contextNode, int baseIndent, double deempasizeBy) {
if (!completions.isEmpty()) {
int dashyIndent = getTargetIndent(contextNode, currentNode, true);
int plainIndent = getTargetIndent(contextNode, currentNode, false);
List<ICompletionProposal> transformed = new ArrayList<>();
for (ICompletionProposal p : completions) {
ICompletionProposal p_fixed = null;
if (p.getLabel().startsWith("- ")) {
p_fixed = indentFix(p, dashyIndent - baseIndent, currentNode, contextNode);
} else {
p_fixed = indentFix(p, plainIndent - baseIndent, currentNode, contextNode);
}
int targetIndent = p.getLabel().startsWith("- ") ? dashyIndent : plainIndent;
ScoreableProposal p_fixed = indentFix((ScoreableProposal)p, targetIndent - baseIndent, currentNode, contextNode);
if (p_fixed!=null) {
p_fixed.deemphasize(deempasizeBy);
transformed.add(p_fixed);
}
}
@@ -135,11 +134,11 @@ public class YamlCompletionEngine implements ICompletionEngine {
return Collections.emptyList();
}
protected ICompletionProposal indentFix(ICompletionProposal p, int fixIndentBy, SNode currentNode, SNode contextNode) {
protected ScoreableProposal indentFix(ScoreableProposal p, int fixIndentBy, SNode currentNode, SNode contextNode) {
if (fixIndentBy==0) {
return p;
} else if (fixIndentBy>0) {
if (isExtraIndentRelaxable(contextNode)) {
if (isExtraIndentRelaxable(contextNode, fixIndentBy)) {
return indented(p, Strings.repeat(" ", fixIndentBy));
}
} else { // fixIndentBy < 0
@@ -182,7 +181,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
: contextNode.getIndent() + YamlIndentUtil.INDENT_BY;
}
public ICompletionProposal dedented(ICompletionProposal proposal, int numSpacesToRemove, IDocument doc) {
public ScoreableProposal dedented(ICompletionProposal proposal, int numSpacesToRemove, IDocument doc) {
Assert.isLegal(numSpacesToRemove>0);
int spacesEnd = proposal.getTextEdit().getFirstEditStart();
int spacesStart = spacesEnd-numSpacesToRemove;
@@ -214,23 +213,23 @@ public class YamlCompletionEngine implements ICompletionEngine {
return null;
}
public ICompletionProposal indented(ICompletionProposal proposal, String indentStr) {
public ScoreableProposal indented(ICompletionProposal proposal, String indentStr) {
int numArrows = (indentStr.length()+1)/2;
ScoreableProposal transformed = new TransformedCompletion(proposal) {
@Override public String tranformLabel(String originalLabel) {
return Unicodes.RIGHT_ARROW+" " + originalLabel;
return Strings.repeat(Unicodes.RIGHT_ARROW+" ", numArrows) + originalLabel;
}
@Override public DocumentEdits transformEdit(DocumentEdits originalEdit) {
originalEdit.indentFirstEdit(indentStr);
return originalEdit;
}
};
transformed.deemphasize(DEEMP_INDENTED_PROPOSAL*indentStr.length()/2);
transformed.deemphasize(numArrows * DEEMP_INDENTED_PROPOSAL);
return transformed;
}
private boolean isExtraIndentRelaxable(SNode contextNode) {
return contextNode!=null && (
private boolean isExtraIndentRelaxable(SNode contextNode, int fixIndentBy) {
return contextNode!=null && /* fixIndentBy<=2 && */ (
isBarrenKey(contextNode) ||
isBarrenSeq(contextNode)
);
@@ -308,8 +307,9 @@ public class YamlCompletionEngine implements ICompletionEngine {
* To allow for the ambiguity in indentation a list of context nodes is returned instead of a
* single node. (Note we may still return a singleton list for cases where relaxed indentation
* doesn't seem desirable).
* @param baseIndent
*/
protected List<SNode> getContextNodes(YamlDocument doc, SNode node, int offset) {
protected List<SNode> getContextNodes(YamlDocument doc, SNode node, int offset, int baseIndent) {
if (node==null) {
return null;
} else if (node.getNodeType()==SNodeType.KEY) {
@@ -334,8 +334,8 @@ public class YamlCompletionEngine implements ICompletionEngine {
//This node has flexibility around indentation. So this is where me need to build a list of candidates!
ImmutableList.Builder<SNode> contextNodes = ImmutableList.builder();
while (node!=null ) {
//Any node that represents a 'step' between contexts must be kept.
if (node.getSegment()!=null) {
//Any node that represents a 'step' between contexts and is not too deeply nested is kept.
if (node.getSegment()!=null && node.getIndent()<=baseIndent) {
contextNodes.add(node);
}
node = node.getParent();

View File

@@ -464,11 +464,8 @@ public class YTypeFactory {
}
/**
* If set to false (which is the default), then this type (when not yet inferred to a
* more specific version of itself) will be treated as if it can be anything (i.e atomic, map or sequence)
* <p>
* If set to true, then it is treated as strictly atomic type instead (i.e it isn't valid to
* use a map or sequence for its value).
* Treat this type as an atomic type (i.e. it can't be a map or sequence), when not yet inferred to a
* more specific version of itself).
*/
public AbstractType treatAsAtomic() {
this.isAtomic = true;
@@ -732,6 +729,7 @@ public class YTypeFactory {
private Renderable description = Renderables.NO_DESCRIPTION;
private boolean isRequired;
private boolean isDeprecated;
private boolean isPrimary;
private YTypedPropertyImpl(String name, YType type) {
this.name = name;
@@ -770,7 +768,7 @@ public class YTypeFactory {
@Override
public boolean isRequired() {
return isRequired;
return isRequired || isPrimary;
}
public void isDeprecated(boolean isDeprecated) {
@@ -781,6 +779,16 @@ public class YTypeFactory {
public boolean isDeprecated() {
return this.isDeprecated;
}
public YTypedPropertyImpl isPrimary(boolean b) {
this.isPrimary = b;
return this;
}
@Override
public boolean isPrimary() {
return isPrimary;
}
}
public YAtomicType yatomic(String name) {

View File

@@ -13,7 +13,6 @@ 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;

View File

@@ -19,6 +19,7 @@ public interface YTypedProperty {
String getName();
YType getType();
Renderable getDescription();
default boolean isRequired() { return false; }
default boolean isRequired() { return isPrimary() || false; }
default boolean isDeprecated() { return false; }
default boolean isPrimary() { return false; }
}

View File

@@ -18,6 +18,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -656,7 +657,6 @@ public class YamlStructureParser {
if (indent==-1) {
createRawNode(parent, line);
} else {
parent = dropTo(parent, indent);
parent = parseLine(parent, line, true);
}
}
@@ -668,15 +668,14 @@ public class YamlStructureParser {
parent = createDocNode(parent.getRoot(), line);
} else if (line.matches(SIMPLE_KEY_LINE)) {
int currentIndent = line.getIndent();
while (currentIndent==parent.getIndent() && parent.getNodeType()!=SNodeType.DOC) {
parent = parent.getParent();
}
parent = dropToLevel(parent, (node) -> node.getIndent()<currentIndent);
parent = createKeyNode(parent, line);
} else if (line.matches(SEQ_LINE)) {
int currentIndent = line.getIndent();
while (currentIndent==parent.getIndent() && parent.getNodeType()==SNodeType.SEQ) {
parent = parent.getParent();
}
parent = dropToLevel(parent, (node) -> {
int indent = node.getIndent();
return indent < currentIndent || node.getNodeType()!=SNodeType.SEQ && indent<=currentIndent;
});
parent = createSeqNode(parent, line);
parent = parseLine(parent, line.moveIndentMark(2), false); //parse from just after "- " for nested seq and key nodes
} else if (createRawNode) {
@@ -685,6 +684,13 @@ public class YamlStructureParser {
return parent;
}
private SChildBearingNode dropToLevel(SChildBearingNode parent, Predicate<SNode> level) {
while (parent.getNodeType()!=SNodeType.DOC && parent.getSegment()!=null && !level.test(parent)) {
parent = parent.getParent();
}
return parent;
}
private SChildBearingNode createDocNode(SRootNode parent, YamlLine line) {
int start = line.getStart();
int end = line.getEnd();
@@ -713,13 +719,6 @@ public class YamlStructureParser {
}
private SChildBearingNode dropTo(SChildBearingNode node, int indent) {
while (indent<node.getIndent()) {
node = node.getParent();
}
return node;
}
public class SSeqNode extends SChildBearingNode {
/**

View File

@@ -207,7 +207,6 @@ public class YamlStructureParserTest {
);
}
@Test public void testSequenceBasic() throws Exception {
MockYamlEditor editor;
@@ -594,6 +593,41 @@ public class YamlStructureParserTest {
path.traverse((SNode)root).toString());
}
@Test public void testDedentedRawNode() throws Exception {
MockYamlEditor editor = new MockYamlEditor(
"world:\n" +
" europe:\n" +
" france:\n" +
" cheese\n" +
" belgium:\n" +
" beer\n" + //De-dented raw node, doesn't obviously belong to any node, but we associate it with closest 'structural' node
" canada:\n" +
" montreal: poutine\n" +
" vancouver:\n" +
" salmon\n" +
"moon:\n" +
" moonbase-alfa:\n" +
" moonstone\n"
);
assertParseOneDoc(editor, ////////////////
"DOC(0): ",
" KEY(0): world:",
" KEY(2): europe:",
" KEY(4): france:",
" RAW(6): cheese",
" KEY(4): belgium:",
" RAW(2): beer",
" KEY(2): canada:",
" KEY(4): montreal: poutine",
" KEY(4): vancouver:",
" RAW(6): salmon",
" KEY(0): moon:",
" KEY(2): moonbase-alfa:",
" RAW(4): moonstone",
" RAW(-1):"
);
}
@Test public void testTreeEnd() throws Exception {
MockYamlEditor editor = new MockYamlEditor(
"world:\n" +

View File

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

View File

@@ -202,7 +202,7 @@ public class PipelineYmlSchema implements YamlSchema {
);
AbstractType t_resource = f.ybean("Resource");
addProp(t_resource, "name", t_resource_name_def).isRequired(true);
addProp(t_resource, "name", t_resource_name_def).isPrimary(true);
addProp(t_resource, "type", t_resource_type_name).isRequired(true);
addProp(t_resource, "source", resourceSource);
addProp(t_resource, "check_every", t_duration);
@@ -218,11 +218,11 @@ public class PipelineYmlSchema implements YamlSchema {
t_platform.parseWith(ValueParsers.NE_STRING); //no errors because in theory platform are just strings.
AbstractType t_input = f.ybean("TaskInput");
addProp(t_input, "name", t_ne_string).isRequired(true);
addProp(t_input, "name", t_ne_string).isPrimary(true);
addProp(t_input, "path", t_ne_string);
AbstractType t_output = f.ybean("TaskOutput");
addProp(t_output, "name", t_ne_string).isRequired(true);
addProp(t_output, "name", t_ne_string).isPrimary(true);
addProp(t_output, "path", t_ne_string);
AbstractType t_command = f.ybean("Command");
@@ -344,7 +344,7 @@ public class PipelineYmlSchema implements YamlSchema {
models.setStepType(step);
AbstractType job = f.ybean("Job");
addProp(job, "name", jobNameDef).isRequired(true);
addProp(job, "name", jobNameDef).isPrimary(true);
addProp(job, "plan", f.yseq(step)).isRequired(true);
addProp(job, "serial", t_boolean);
addProp(job, "build_logs_to_retain", t_pos_integer);
@@ -354,12 +354,12 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(job, "disable_manual_trigger", t_boolean);
AbstractType resourceType = f.ybean("ResourceType");
addProp(resourceType, "name", resourceTypeNameDef).isRequired(true);
addProp(resourceType, "name", resourceTypeNameDef).isPrimary(true);
addProp(resourceType, "type", t_resource_type_name).isRequired(true);
addProp(resourceType, "source", resourceSource);
AbstractType group = f.ybean("Group");
addProp(group, "name", t_ne_string).isRequired(true);
addProp(group, "name", t_ne_string).isPrimary(true);
addProp(group, "resources", f.yseq(t_resource_name));
addProp(group, "jobs", f.yseq(t_job_name));
@@ -385,7 +385,7 @@ public class PipelineYmlSchema implements YamlSchema {
// git :
{
AbstractType source = f.ybean("GitSource");
addProp(source, "uri", t_ne_string).isRequired(true);
addProp(source, "uri", t_ne_string).isPrimary(true);
addProp(source, "branch", t_ne_string); //It's more complicated than that! Its only required in 'put' step. So we'll check this as a contrain in put steps!
addProp(source, "private_key", t_ne_string);
addProp(source, "username", t_ne_string);
@@ -406,7 +406,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(get, "disable_git_lfs", t_boolean);
AbstractType put = f.ybean("GitPutParams");
addProp(put, "repository", t_ne_string).isRequired(true);
addProp(put, "repository", t_ne_string).isPrimary(true);
addProp(put, "rebase", t_boolean);
addProp(put, "tag", t_ne_string);
addProp(put, "only_tag", t_boolean);
@@ -419,7 +419,7 @@ public class PipelineYmlSchema implements YamlSchema {
//docker-image:
{
AbstractType source = f.ybean("DockerImageSource");
addProp(source, "repository", t_ne_string).isRequired(true);
addProp(source, "repository", t_ne_string).isPrimary(true);
addProp(source, "tag", t_ne_string);
addProp(source, "username", t_ne_string);
addProp(source, "password", t_ne_string);
@@ -473,7 +473,7 @@ public class PipelineYmlSchema implements YamlSchema {
);
AbstractType source = f.ybean("S3Source");
addProp(source, "bucket", t_ne_string).isRequired(true);
addProp(source, "bucket", t_ne_string).isPrimary(true);
addProp(source, "access_key_id", t_ne_string);
addProp(source, "secret_access_key", t_ne_string);
addProp(source, "region_name", t_s3_region);
@@ -492,7 +492,7 @@ public class PipelineYmlSchema implements YamlSchema {
//Note: S3GetParams intentionally has no properties since no params are expected according to the docs.
AbstractType put = f.ybean("S3PutParams");
addProp(put, "file", t_ne_string).isRequired(true);
addProp(put, "file", t_ne_string).isPrimary(true);
addProp(put, "acl", t_canned_acl);
addProp(put, "content_type", t_mime_type);
@@ -526,7 +526,7 @@ public class PipelineYmlSchema implements YamlSchema {
//semver
{
AbstractType git_source = f.ybean("GitSemverSource");
addProp(git_source, "uri", t_ne_string).isRequired(true);
addProp(git_source, "uri", t_ne_string).isPrimary(true);
addProp(git_source, "branch", t_ne_string).isRequired(true);
addProp(git_source, "file", t_ne_string).isRequired(true);
addProp(git_source, "private_key", t_ne_string);
@@ -535,7 +535,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(git_source, "git_user", t_ne_string);
AbstractType s3_source = f.ybean("S3SemverSource");
addProp(s3_source, "bucket", t_ne_string).isRequired(true);
addProp(s3_source, "bucket", t_ne_string).isPrimary(true);
addProp(s3_source, "key", t_ne_string).isRequired(true);
addProp(s3_source, "access_key_id", t_ne_string).isRequired(true);
addProp(s3_source, "secret_access_key", t_ne_string).isRequired(true);
@@ -544,7 +544,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(s3_source, "disable_ssl", t_boolean);
AbstractType swift_source = f.ybean("SwiftSemverSource");
addProp(swift_source, "openstack", t_any).isRequired(true);
addProp(swift_source, "openstack", t_any).isPrimary(true);
AbstractType[] driverSpecificSources = {
git_source, s3_source, swift_source

View File

@@ -10,14 +10,16 @@
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.junit.Before;
@@ -982,13 +984,25 @@ public class ConcourseEditorTest {
}
@Test public void gitResourceSourceCompletions() throws Exception {
assertContextualCompletions(
assertContextualCompletions(PLAIN_COMPLETION,
"resources:\n" +
"- name: the-repo\n" +
" type: git\n" +
" source:\n" +
" <*>\n" +
" blah: blah"
" <*>"
, //================
"<*>"
, // ==>
"uri: <*>"
);
assertContextualCompletions(PLAIN_COMPLETION,
"resources:\n" +
"- name: the-repo\n" +
" type: git\n" +
" source:\n" +
" uri:\n" +
" <*>"
, //================
"<*>"
, // ==>
@@ -1021,8 +1035,6 @@ public class ConcourseEditorTest {
,
"tag_filter: <*>"
,
"uri: <*>"
,
"username: <*>"
);
@@ -1156,6 +1168,22 @@ public class ConcourseEditorTest {
}
@Test public void gitResourcePutParamsCompletions() throws Exception {
assertContextualCompletions(PLAIN_COMPLETION,
"resources:\n" +
"- name: my-git\n" +
" type: git\n" +
"jobs:\n" +
"- name: do-stuff\n" +
" plan:\n" +
" - put: my-git\n" +
" params:\n" +
" <*>"
,
"<*>"
, // =>
"repository: <*>"
);
String context =
"resources:\n" +
"- name: my-git\n" +
@@ -1165,10 +1193,10 @@ public class ConcourseEditorTest {
" plan:\n" +
" - put: my-git\n" +
" params:\n" +
" <*>\n" +
" blah: blah";
" repository: blah\n" +
" <*>";
assertContextualCompletions(context,
assertContextualCompletions(PLAIN_COMPLETION, context,
"<*>"
, // ===>
"annotate: <*>"
@@ -1179,8 +1207,6 @@ public class ConcourseEditorTest {
,
"rebase: <*>"
,
"repository: <*>"
,
"tag: <*>"
,
"tag_prefix: <*>"
@@ -2011,41 +2037,57 @@ public class ConcourseEditorTest {
}
@Test public void semverGitResourceSourceContentAssist() throws Exception {
String conText =
assertContextualCompletions(PLAIN_COMPLETION,
"resources:\n" +
"- name: version\n" +
" type: semver\n" +
" source:\n" +
"<*>\n" +
" blah: blah";
assertContextualCompletions(conText,
" driver: git\n" +
" <*>"
, // ===========
"<*>"
, // ==>
"uri: <*>"
);
assertContextualCompletions(PLAIN_COMPLETION,
"resources:\n" +
"- name: version\n" +
" type: semver\n" +
" source:\n" +
" driver: git\n" +
" uri: something\n" +
"<*>"
, // =============
" <*>"
, // ==>
" branch: <*>"
,
" driver: git\n" +
" file: <*>"
,
);
assertContextualCompletions(PLAIN_COMPLETION,
"resources:\n" +
"- name: version\n" +
" type: semver\n" +
" source:\n" +
" driver: git\n" +
" uri: something\n" +
" branch: master\n" +
" file: somefile\n" +
"<*>"
, // =============
" <*>"
, // ==>
" git_user: <*>"
,
" driver: git\n" +
" initial_version: <*>"
,
" driver: git\n" +
" password: <*>"
,
" driver: git\n" +
" private_key: <*>"
,
" driver: git\n" +
" uri: <*>"
,
" driver: git\n" +
" username: <*>"
);
}
@Test public void semverGitResourceSourceReconcileAndHovers() throws Exception {
@@ -2593,6 +2635,19 @@ public class ConcourseEditorTest {
@Test public void contentAssistTaskFileToplevelProperties() throws Exception {
assertTaskCompletions(
"<*>"
, // ==>
"platform: <*>"
,
"run:\n" +
" <*>"
);
assertContextualTaskCompletions(
"run: {}\n" +
"platform: linux\n" +
"<*>"
,
"<*>"
, // ==>
"image: <*>"
@@ -2608,11 +2663,6 @@ public class ConcourseEditorTest {
,
"params:\n" +
" <*>"
,
"platform: <*>"
,
"run:\n" +
" <*>"
);
assertTaskCompletions(
@@ -2928,29 +2978,13 @@ public class ConcourseEditorTest {
//"source", exists
//"type", exists
//For the nested context:
"→ branch",
"→ commit_verification_key_ids",
"→ commit_verification_keys",
"→ disable_ci_skip",
"→ git_config",
"→ gpg_keyserver",
"→ ignore_paths",
"→ password",
"→ paths",
"→ private_key",
"→ skip_ssl_verification",
"→ tag_filter",
"→ uri",
"→ username",
// For the top-level context:
"← groups",
"← jobs",
"← resource_types",
// For the 'next job' context:
"← - check_every",
"← - name",
"← - source",
"← - type"
"← - name"
);
editor.assertCompletionWithLabel("check_every",
@@ -2961,33 +2995,55 @@ public class ConcourseEditorTest {
" check_every: <*>"
);
editor.assertCompletionWithLabel("branch",
editor.assertCompletionWithLabel("uri",
"resources:\n" +
"- name: foo\n" +
" type: git\n" +
" source:\n" +
" branch: <*>"
" uri: <*>"
);
editor = harness.newEditor(
"resources:\n" +
"- name: foo\n" +
" type: git\n" +
" source:\n" +
" uri: blah\n" +
" <*>"
);
editor.assertCompletionWithLabel("→ commit_verification_key_ids",
"resources:\n" +
"- name: foo\n" +
" type: git\n" +
" source:\n" +
" uri: blah\n" +
" commit_verification_key_ids:\n" +
" - <*>"
);
}
@Test public void relaxedIndentContextMoreSpaces2() throws Exception {
assertContextualCompletions(
assertContextualCompletions(INDENTED_COMPLETION,
"resources:\n" +
"- name: foo\n" +
" type: git\n" +
" source:\n" +
" <*>"
, // =========
"bra<*>"
"ur"
, //=>
" uri: <*>"
);
assertContextualCompletions(INDENTED_COMPLETION,
"resources:\n" +
"- name: foo\n" +
" type: git\n" +
" source:\n" +
" uri: blah\n" +
" <*>"
, // =========
"bra"
, //=>
" branch: <*>"
);
@@ -2997,6 +3053,7 @@ public class ConcourseEditorTest {
"- name: foo\n" +
" type: git\n" +
" source:\n" +
" uri: blah\n" +
" <*>"
, // =========
"comverids<*>"
@@ -3027,13 +3084,6 @@ public class ConcourseEditorTest {
//"name", exists
//"plan", exists
//"public", exists
//Completions with '-'
"- aggregate",
"- do",
"- get",
"- put",
"- task",
"- try",
//Completions for nested context (i.e. task step)
"→ attempts",
"→ config",
@@ -3048,18 +3098,18 @@ public class ConcourseEditorTest {
"→ privileged",
"→ tags",
"→ timeout",
//"→ task" exists
"← groups\n" +
"← resource_types\n" +
"← resources\n" +
"← - build_logs_to_retain\n" +
"← - disable_manual_trigger\n" +
"← - max_in_flight\n" +
"← - name\n" +
"- plan\n" +
"- public\n" +
"- serial\n" +
"← - serial_groups"
//Completions with '-'
"- aggregate",
"- do",
"- get",
"- put",
"- task",
"- try",
//Dedented completions
"groups",
"resource_types",
"resources",
"← - name"
);
}
@@ -3445,6 +3495,24 @@ public class ConcourseEditorTest {
);
}
@Test public void relaxedContentAssist_primary_properties() throws Exception{
//See https://www.pivotaltracker.com/story/show/144584163
Editor editor;
editor = harness.newEditor(
"resources:\n" +
"- name: docker-git\n" +
"<*>"
);
editor.assertCompletionLabels(
"groups",
"jobs",
"resource_types",
"→ type",
"- name"
);
}
@Test public void relaxedContentAssistLessSpaces() throws Exception {
Editor editor;
@@ -3543,7 +3611,15 @@ public class ConcourseEditorTest {
//////////////////////////////////////////////////////////////////////////////
private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {
Editor editor = harness.newEditor(conText);
assertContextualCompletions((c) -> true, conText, textBefore, textAfter);
}
private void assertContextualCompletions(Predicate<CompletionItem> isInteresting, String conText, String textBefore, String... textAfter) throws Exception {
assertContextualCompletions(LanguageId.CONCOURSE_PIPELINE, isInteresting, conText, textBefore, textAfter);
}
private void assertContextualCompletions(LanguageId language, Predicate<CompletionItem> isInteresting, String conText, String textBefore, String... textAfter) throws Exception {
Editor editor = harness.newEditor(language, conText);
editor.reconcile(); //this ensures the conText is parsed and its AST is cached (will be used for
//dynamic CA when the conText + textBefore is not parsable.
assertContains(CURSOR, conText);
@@ -3552,7 +3628,7 @@ public class ConcourseEditorTest {
.map((String t) -> conText.replace(CURSOR, t))
.collect(Collectors.toList()).toArray(new String[0]);
editor.setText(textBefore);
editor.assertCompletions(textAfter);
editor.assertCompletions(isInteresting, textAfter);
}
private void assertCompletions(String textBefore, String... textAfter) throws Exception {
@@ -3565,5 +3641,18 @@ public class ConcourseEditorTest {
editor.assertCompletions(textAfter);
}
private void assertContextualTaskCompletions(String conText, String textBefore, String... textAfter) throws Exception {
assertContextualCompletions(LanguageId.CONCOURSE_TASK, c -> true, conText, textBefore, textAfter);
}
public static final Predicate<CompletionItem> RELAXED_COMPLETION
= c -> c.getLabel().startsWith("- ")
|| c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ")
|| c.getLabel().startsWith(Unicodes.RIGHT_ARROW+" ")
;
public static final Predicate<CompletionItem> PLAIN_COMPLETION = c -> !RELAXED_COMPLETION.test(c);
public static final Predicate<CompletionItem> DEDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ");
public static final Predicate<CompletionItem> INDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.RIGHT_ARROW+" ");
}

View File

@@ -194,6 +194,7 @@ public class ManifestYmlSchema implements YamlSchema {
f.yprop("services", f.yseq(t_service)),
f.yprop("stack", t_stack),
f.yprop("timeout", t_pos_integer),
f.yprop(HEALTH_CHECK_TYPE_PROP, t_health_check_type),
f.yprop(HEALTH_CHECK_HTTP_ENDPOINT_PROP, t_ne_string)
};

View File

@@ -32,6 +32,7 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInsta
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import org.springframework.ide.vscode.commons.util.Unicodes;
import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -316,74 +317,101 @@ public class ManifestYamlEditorTest {
"- <*>"
);
editor.assertCompletions(
"applications:\n" +
"- name: <*>"
);
editor = harness.newEditor(
"applications:\n" +
"- name: foo\n" +
" <*>"
);
editor.assertCompletions((c) -> !(c.getLabel().startsWith("- ") || c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ")),
// ---------------
"applications:\n" +
"- buildpack: <*>",
"- name: foo\n" +
" buildpack: <*>",
// ---------------
"applications:\n" +
"- command: <*>",
"- name: foo\n" +
" command: <*>",
// ---------------
"applications:\n" +
"- disk_quota: <*>",
"- name: foo\n" +
" disk_quota: <*>",
// ---------------
"applications:\n" +
"- domain: <*>",
"- name: foo\n" +
" domain: <*>",
// ---------------
"applications:\n" +
"- domains:\n"+
"- name: foo\n" +
" domains:\n"+
" - <*>",
// ---------------
"applications:\n" +
"- env:\n"+
"- name: foo\n" +
" env:\n"+
" <*>",
// ---------------
"applications:\n" +
"- health-check-http-endpoint: <*>",
"- name: foo\n" +
" health-check-http-endpoint: <*>",
// ---------------
"applications:\n" +
"- health-check-type: <*>",
"- name: foo\n" +
" health-check-type: <*>",
// ---------------
"applications:\n" +
"- host: <*>",
"- name: foo\n" +
" host: <*>",
// ---------------
"applications:\n" +
"- hosts:\n"+
"- name: foo\n" +
" hosts:\n"+
" - <*>",
// ---------------
"applications:\n" +
"- instances: <*>",
"- name: foo\n" +
" instances: <*>",
// ---------------
"applications:\n" +
"- memory: <*>",
"- name: foo\n" +
" memory: <*>",
// ---------------
"applications:\n" +
"- name: <*>",
"- name: foo\n" +
" no-hostname: <*>",
// ---------------
"applications:\n" +
"- no-hostname: <*>",
"- name: foo\n" +
" no-route: <*>",
// ---------------
"applications:\n" +
"- no-route: <*>",
"- name: foo\n" +
" path: <*>",
// ---------------
"applications:\n" +
"- path: <*>",
"- name: foo\n" +
" random-route: <*>",
// ---------------
"applications:\n" +
"- random-route: <*>",
// ---------------
"applications:\n" +
"- routes:\n"+
"- name: foo\n" +
" routes:\n"+
" - route: <*>",
// ---------------
"applications:\n" +
"- services:\n"+
"- name: foo\n" +
" services:\n"+
" - <*>",
// ---------------
"applications:\n" +
"- stack: <*>",
"- name: foo\n" +
" stack: <*>",
// ---------------
"applications:\n" +
"- timeout: <*>"
"- name: foo\n" +
" timeout: <*>"
);
}
@@ -391,7 +419,8 @@ public class ManifestYamlEditorTest {
public void completionDetailsAndDocs() throws Exception {
Editor editor = harness.newEditor(
"applications:\n" +
"- build<*>"
"- name: foo\n" +
" build<*>"
);
editor.assertCompletionDetails("buildpack", "Buildpack", "If your application requires a custom buildpack");
}
@@ -837,72 +866,7 @@ public class ManifestYamlEditorTest {
"-<*>",
// ===>
"applications:\n" +
"- buildpack: <*>",
// ---------------
"applications:\n" +
"- command: <*>",
// ---------------
"applications:\n" +
"- disk_quota: <*>",
// ---------------
"applications:\n" +
"- domain: <*>",
// ---------------
"applications:\n" +
"- domains:\n"+
" - <*>",
// ---------------
"applications:\n" +
"- env:\n"+
" <*>",
// ---------------
"applications:\n" +
"- health-check-http-endpoint: <*>",
// ---------------
"applications:\n" +
"- health-check-type: <*>",
// ---------------
"applications:\n" +
"- host: <*>",
// ---------------
"applications:\n" +
"- hosts:\n"+
" - <*>",
// ---------------
"applications:\n" +
"- instances: <*>",
// ---------------
"applications:\n" +
"- memory: <*>",
// ---------------
"applications:\n" +
"- name: <*>",
// ---------------
"applications:\n" +
"- no-hostname: <*>",
// ---------------
"applications:\n" +
"- no-route: <*>",
// ---------------
"applications:\n" +
"- path: <*>",
// ---------------
"applications:\n" +
"- random-route: <*>",
// ---------------
"applications:\n" +
"- routes:\n"+
" - route: <*>",
// ---------------
"applications:\n" +
"- services:\n"+
" - <*>",
// ---------------
"applications:\n" +
"- stack: <*>",
// ---------------
"applications:\n" +
"- timeout: <*>"
"- name: <*>"
);
//Second example
@@ -912,93 +876,8 @@ public class ManifestYamlEditorTest {
"- name: test"
, // ==>
"applications:\n" +
"- buildpack: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- command: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- disk_quota: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- domain: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- domains:\n" +
" - <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- env:\n" +
" <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- health-check-http-endpoint: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- health-check-type: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- host: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- hosts:\n" +
" - <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- instances: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- memory: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- name: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- no-hostname: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- no-route: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- path: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- random-route: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- routes:\n" +
" - route: <*>\n" +
"- name: test"
,// ---------------------
"applications:\n" +
"- services:\n" +
" - <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- stack: <*>\n" +
"- name: test"
, // ---------------------
"applications:\n" +
"- timeout: <*>\n" +
"- name: test"
);
}