Relaxing yaml proposals for '- '.

This commit is contained in:
Kris De Volder
2017-04-25 18:08:05 -07:00
parent 347ad2a653
commit b866649a3d
16 changed files with 422 additions and 117 deletions

View File

@@ -13,6 +13,7 @@ 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;
@@ -20,7 +21,7 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
* @author Martin Lippert
*/
public class ScopeNameCompletionProposal implements ICompletionProposal {
public static final ScopeNameCompletion[] COMPLETIONS = new ScopeNameCompletion[] {
new ScopeNameCompletion("\"prototype\"", "prototype", "prototype scope", null, CompletionItemKind.Value),
new ScopeNameCompletion("\"singleton\"", "singleton", "singleton scope (default)", null, CompletionItemKind.Value),
@@ -29,8 +30,8 @@ public class ScopeNameCompletionProposal implements ICompletionProposal {
new ScopeNameCompletion("\"globalSession\"", "globalSession", "globalSession scope", null, CompletionItemKind.Value),
new ScopeNameCompletion("\"application\"", "application", "application scope", null, CompletionItemKind.Value),
new ScopeNameCompletion("\"websocket\"", "websocket", "websocket scope", null, CompletionItemKind.Value)
};
};
private final IDocument doc;
private final int startOffset;
private final int endOffset;
@@ -45,11 +46,6 @@ public class ScopeNameCompletionProposal implements ICompletionProposal {
this.prefix = prefix;
}
@Override
public ICompletionProposal deemphasize() {
return null;
}
@Override
public String getLabel() {
return completion.getLabel();

View File

@@ -32,11 +32,6 @@ public class ValuePropertyKeyProposal implements ICompletionProposal {
this.documentation = documentation;
}
@Override
public ICompletionProposal deemphasize() {
return null;
}
@Override
public String getLabel() {
return this.label;

View File

@@ -77,8 +77,7 @@ public abstract class AbstractPropertyProposal extends ScoreableProposal {
}
public void deprecate() {
if (!isDeprecated()) {
deemphasize();
deemphasize();
deemphasize(DEEMP_DEPRECATION);
isDeprecated = true;
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.properties.quickfix;
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;
public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal {
@@ -29,10 +30,6 @@ public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal {
// }
// };
@Override
public ICompletionProposal deemphasize() {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getLabel() {
throw new UnsupportedOperationException("Not yet implemented");
@@ -53,6 +50,10 @@ public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal {
public Renderable getDocumentation() {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public ScoreableProposal deemphasize(double howmuch) {
throw new UnsupportedOperationException("Not yet implemented");
}
// private final QuickfixContext context;
// private final SpringPropertyProblem problem;

View File

@@ -66,6 +66,8 @@ import org.springframework.ide.vscode.commons.yaml.util.YamlUtil;
import com.google.common.collect.ImmutableList;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*;
/**
* Represents a context insied a "application.yml" file relative to which we can provide
* content assistance.
@@ -198,7 +200,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
completionFactory.beanProperty(doc.getDocument(),
contextPath.toPropString(), getType(),
query, p, score, edits, typeUtil)
.deemphasize() //deemphasize because it already exists
.deemphasize(DEEMP_EXISTS) //deemphasize because it already exists
);
}
}
@@ -399,7 +401,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
doc.getDocument(), edits, match, typeUtil
);
if (getContextRoot(doc).exists(YamlPath.fromProperty(match.data.getId()))) {
completion.deemphasize();
completion.deemphasize(DEEMP_EXISTS);
}
completions.add(completion);
}
@@ -492,7 +494,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
}
return null;
}
@Override
public Renderable getHoverInfo(YamlPathSegment lastSegment) {
// TODO Auto-generated method stub
@@ -506,6 +508,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
}
}
@Override
public Renderable getHoverInfo(YamlPathSegment s) {
//ApplicationYamlAssistContext implements getHoverInfo directly. so this is not needed.
return null;
@@ -524,7 +527,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
}
};
}
private static Deprecation getDeprecation(TypeUtil typeUtil, Type parentType, String propName) {
Map<String, TypedProperty> props = typeUtil.getPropertiesMap(parentType, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
if (props!=null) {
@@ -535,7 +538,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
}
return null;
}
private static Renderable getDescription(TypeUtil typeUtil, Type parentType, String propName) {
try {
List<IJavaElement> jes = getAllJavaElements(typeUtil, parentType, propName);
@@ -554,7 +557,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
}
return Renderables.NO_DESCRIPTION;
}
private static List<IJavaElement> getAllJavaElements(TypeUtil typeUtil, Type parentType, String propName) {
if (propName!=null) {
Type beanType = parentType;

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.yaml.quickfix;
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;
public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal {
@@ -29,10 +30,6 @@ public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal {
// }
// };
@Override
public ICompletionProposal deemphasize() {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getLabel() {
throw new UnsupportedOperationException("Not yet implemented");
@@ -53,6 +50,10 @@ public class ReplaceDeprecatedYamlQuickfix implements ICompletionProposal {
public Renderable getDocumentation() {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public ScoreableProposal deemphasize(double howmuch) {
throw new UnsupportedOperationException("Not yet implemented");
}
// private final QuickfixContext context;
// private final SpringPropertyProblem problem;

View File

@@ -11,6 +11,9 @@
package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.ArrayList;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.lsp4j.TextEdit;
import org.springframework.ide.vscode.commons.util.Assert;
@@ -41,6 +44,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public class DocumentEdits implements ProposalApplier {
private static final Pattern NON_WS_CHAR = Pattern.compile("\\S");
// Note: for small number of edits this implementation is okay.
// for large number of edits it is potentially slow because of the
// way it transforms edit coordinates (a growing chain of
@@ -415,4 +420,21 @@ public class DocumentEdits implements ProposalApplier {
}
}
/**
* Find first non-whitepace insertion edit and transform its contents.
* @param transformFun receives the insertion text of the target edit and the offset of the first non-whitespace character
* as arguments. It should return a replacement text.
*/
public void transformFirstNonWhitespaceEdit(BiFunction<Integer, String, String> transformFun) {
for (Edit edit : edits) {
if (edit instanceof Insertion) {
Insertion insert = (Insertion) edit;
Matcher matcher = NON_WS_CHAR.matcher(insert.text);
if (matcher.find()) {
insert.text = transformFun.apply(matcher.start(), insert.text);
}
}
}
}
}

View File

@@ -19,10 +19,6 @@ import org.springframework.ide.vscode.commons.util.Renderable;
*/
public interface ICompletionProposal {
/**
* Transforms a proposal to make it standout less somehow.
*/
ICompletionProposal deemphasize();
String getLabel();
CompletionItemKind getKind();
@@ -32,4 +28,10 @@ public interface ICompletionProposal {
Renderable getDocumentation();
default String getFilterText() { return getLabel(); }
/**
* Transforms a proposal to make it standout less somehow.
* @param howmuch A 'weight' for the deemphasis. Allowing to deempasize some proposals more than others.
*/
default ICompletionProposal deemphasize(double howmuch) { return this; }
}

View File

@@ -13,8 +13,17 @@ package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.Comparator;
import org.springframework.ide.vscode.commons.util.Assert;
public abstract class ScoreableProposal implements ICompletionProposal {
private static final double DEEMP_VALUE = 100000; // should be large enough to move deemphasized stuff to bottom of list.
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;
private static final double DEEMP_VALUE = 10_000; // should be large enough to move deemphasized stuff to bottom of list.
private double deemphasizedBy = 0.0;
@@ -27,7 +36,7 @@ public abstract class ScoreableProposal implements ICompletionProposal {
if (p1 instanceof ScoreableProposal && p2 instanceof ScoreableProposal) {
double s1 = ((ScoreableProposal)p1).getScore();
double s2 = ((ScoreableProposal)p2).getScore();
if (s1==s2) {
if (Math.abs(s1-s2)<1E-5) {
String name1 = ((ScoreableProposal)p1).getLabel();
String name2 = ((ScoreableProposal)p2).getLabel();
return name1.compareTo(name2);
@@ -43,8 +52,9 @@ public abstract class ScoreableProposal implements ICompletionProposal {
return getBaseScore() - deemphasizedBy;
}
@Override
public ScoreableProposal deemphasize() {
deemphasizedBy+= DEEMP_VALUE;
public ScoreableProposal deemphasize(double howmuch) {
Assert.isLegal(howmuch>0.0);
deemphasizedBy+= howmuch*DEEMP_VALUE;
return this;
}
public boolean isDeemphasized() {

View File

@@ -8,7 +8,6 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.maven;
import static org.junit.Assert.assertEquals;

View File

@@ -45,6 +45,7 @@ public abstract class AbstractYamlAssistContext implements YamlAssistContext {
private static PrefixFinder prefixfinder = new PrefixFinder() {
@Override
protected boolean isPrefixChar(char c) {
return !Character.isWhitespace(c);
}
@@ -94,7 +95,7 @@ public abstract class AbstractYamlAssistContext implements YamlAssistContext {
}
protected SNode getContextNode() throws Exception {
SNode root = (SNode)getContextRoot(getDocument());
SNode root = getContextRoot(getDocument());
return contextPath.traverse(root);
}
@@ -119,5 +120,4 @@ public abstract class AbstractYamlAssistContext implements YamlAssistContext {
return getHoverInfo();
}
}

View File

@@ -0,0 +1,74 @@
/*******************************************************************************
* 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.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;
/**
* Abstract base-class to implement a Completion that is derived from
* another completion with some transformations applied to it. (E.g. add
* extra indentation for relaxed indentation proposals).
*
* @author Kris De Volder
*/
public abstract class TransformedCompletion extends ScoreableProposal {
protected final ICompletionProposal original;
private DocumentEdits transformedEdit = null;
public TransformedCompletion(ICompletionProposal proposal) {
this.original = proposal;
}
protected abstract String tranformLabel(String originalLabel);
protected abstract DocumentEdits transformEdit(DocumentEdits textEdit);
@Override
public synchronized DocumentEdits getTextEdit() {
if (transformedEdit==null) {
transformedEdit = transformEdit(original.getTextEdit());
}
return transformedEdit;
}
@Override
public String getLabel() {
return tranformLabel(original.getLabel());
}
@Override
public CompletionItemKind getKind() {
return original.getKind();
}
@Override
public Renderable getDocumentation() {
return original.getDocumentation();
}
@Override
public String getDetail() {
return original.getDetail();
}
@Override
public double getBaseScore() {
if (original instanceof ScoreableProposal) {
return ((ScoreableProposal) original).getScore();
}
return 0;
}
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.completion;
import static org.assertj.core.api.Assertions.offset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -45,6 +47,8 @@ import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
import com.google.common.collect.ImmutableList;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*;
public class YTypeAssistContext extends AbstractYamlAssistContext {
final static Logger logger = LoggerFactory.getLogger(YTypeAssistContext.class);
@@ -53,6 +57,17 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
final private YType type;
final private YamlAssistContext parent;
/**
* Create a 'relaxed' {@link YTypeAssistContext} that pretends the type expectedin this context
* is something else than what the schema sugests.
*/
public YTypeAssistContext(YTypeAssistContext relaxationTarget, YType relaxedType) {
super(relaxationTarget.getDocument(), relaxationTarget.documentSelector, relaxationTarget.contextPath);
this.parent = relaxationTarget.parent;
this.typeUtil = relaxationTarget.typeUtil;
this.type = relaxedType;
}
public YTypeAssistContext(YTypeAssistContext parent, YamlPath contextPath, YType YType, YTypeUtil typeUtil) {
super(parent.getDocument(), parent.documentSelector, contextPath);
this.parent = parent;
@@ -62,9 +77,9 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
public YTypeAssistContext(TopLevelAssistContext parent, int documentSelector, YType type, YTypeUtil typeUtil) {
super(parent.getDocument(), documentSelector, YamlPath.EMPTY);
this.type = type;
this.typeUtil = typeUtil;
this.parent = parent;
this.typeUtil = typeUtil;
this.type = type;
}
@Override
@@ -114,7 +129,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
completionFactory().beanProperty(doc.getDocument(),
contextPath.toPropString(), getType(),
query, p, score, edits, typeUtil)
.deemphasize() //deemphasize because it already exists
.deemphasize(DEEMP_EXISTS) //deemphasize because it already exists
);
}
}
@@ -243,7 +258,6 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
return "TypeContext("+contextPath.toPropString()+"::"+type+")";
}
@Override
public Renderable getHoverInfo() {
if (parent!=null) {
@@ -293,4 +307,59 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
private YTypedProperty getProperty(String name) {
return typeUtil.getPropertiesMap(getType()).get(name);
}
@Override
public YamlAssistContext relax() {
try {
if (typeUtil.isSequencable(type)) {
YType itemType = typeUtil.getDomainType(type);
if (itemType!=null) {
return new YTypeAssistContext(this, itemType) {
@Override
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
Collection<ICompletionProposal> basicCompletions = super.getCompletions(doc, node, offset);
return addDashes(basicCompletions);
}
};
}
}
} catch (Exception e) {
Log.log(e);
}
return super.relax();
}
private Collection<ICompletionProposal> addDashes(Collection<ICompletionProposal> basicCompletions) {
if (!basicCompletions.isEmpty()) {
List<ICompletionProposal> dashedCompletions = new ArrayList<>(basicCompletions.size());
for (ICompletionProposal c : basicCompletions) {
dashedCompletions.add(
new TransformedCompletion(c) {
@Override
protected DocumentEdits transformEdit(DocumentEdits textEdit) {
textEdit.transformFirstNonWhitespaceEdit((Integer offset, String insertText) -> {
if (offset > 2) {
String prefix = insertText.substring(offset-2, offset);
if (" ".equals(prefix)) {
//special case don't add the "- " in front, but replace the inserted spaces instead.
return insertText.substring(0, offset-2)+"- "+insertText.substring(offset);
}
}
return insertText.substring(0, offset) + "- "+insertText.substring(offset);
});
return textEdit;
}
@Override
protected String tranformLabel(String originalLabel) {
return "- "+originalLabel;
}
}.deemphasize(0.5)
);
}
return dashedCompletions;
}
return basicCompletions;
}
}

View File

@@ -34,4 +34,13 @@ public interface YamlAssistContext extends YamlNavigable<YamlAssistContext> {
Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion);
YamlDocument getDocument();
/**
* Allows a context to implement a 'relaxation' transformation. A relaxed context
* should compute the same proposals as the original context but may also add
* additional proposals. E.g. a {@link YTypeAssistContext} uses this to
* relax contexts for sequence types to include proposals for the elements
* of the sequence.
*/
default YamlAssistContext relax() { return null; }
}

View File

@@ -14,8 +14,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CompletionItemKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
@@ -23,7 +23,6 @@ import org.springframework.ide.vscode.commons.languageserver.completion.IComplet
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.Assert;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
@@ -35,6 +34,8 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
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 contructor.
@@ -71,15 +72,16 @@ public class YamlCompletionEngine implements ICompletionEngine {
SRootNode root = doc.getStructure();
SNode current = root.find(offset);
SNode contextNode = getContextNode(doc, current, offset);
List<ICompletionProposal> all = new ArrayList<>(getPreciseCompletions(offset, doc, current, contextNode));
all.addAll(addIndentations(getRelaxedCompletions(offset, doc, contextNode, current)));
List<ICompletionProposal> all = new ArrayList<>(getBaseCompletions(offset, doc, current, contextNode, false));
all.addAll(getMoreIndentedCompletions(offset, doc, contextNode, current));
all.addAll(getDashedCompletions(offset, doc, contextNode, current));
return all;
}
return Collections.emptyList();
}
private Collection<? extends ICompletionProposal> addIndentations(
Collection<? extends ICompletionProposal> completions) {
private Collection<ICompletionProposal> addIndentations(
Collection<ICompletionProposal> completions) {
if (!completions.isEmpty()) {
List<ICompletionProposal> transformed = new ArrayList<>();
for (ICompletionProposal p : completions) {
@@ -91,63 +93,40 @@ public class YamlCompletionEngine implements ICompletionEngine {
}
public ICompletionProposal indented(ICompletionProposal proposal) {
ScoreableProposal transformed = new ScoreableProposal() {
DocumentEdits indentedEdit = null;
@Override
public synchronized DocumentEdits getTextEdit() {
if (indentedEdit==null) {
indentedEdit = proposal.getTextEdit();
indentedEdit.indentFirstEdit(YamlIndentUtil.INDENT_STR);
}
return indentedEdit;
ScoreableProposal transformed = new TransformedCompletion(proposal) {
@Override public String tranformLabel(String originalLabel) {
return "" + originalLabel;
}
@Override
public String getLabel() {
return ""+proposal.getLabel();
}
@Override
public CompletionItemKind getKind() {
return proposal.getKind();
}
@Override
public Renderable getDocumentation() {
return proposal.getDocumentation();
}
@Override
public String getDetail() {
return proposal.getDetail();
}
@Override
public double getBaseScore() {
if (proposal instanceof ScoreableProposal) {
return ((ScoreableProposal) proposal).getBaseScore();
}
return 0;
@Override public DocumentEdits transformEdit(DocumentEdits originalEdit) {
originalEdit.indentFirstEdit(YamlIndentUtil.INDENT_STR);
return originalEdit;
}
};
transformed.deemphasize();
transformed.deemphasize(DEEMP_INDENTED_PROPOSAL);
return transformed;
}
private Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode preciseContextNode, SNode currentNode) throws Exception {
SNode contextNode = getContextNode(doc, currentNode, offset, YamlIndentUtil.INDENT_BY);
if (preciseContextNode!=contextNode && isRelaxable(contextNode)) {
private Collection<? extends ICompletionProposal> getDashedCompletions(int offset, YamlDocument doc, SNode preciseContextNode, SNode currentNode) throws Exception {
SNode contextNode = getContextNode(doc, currentNode, offset, "- ");
if (preciseContextNode!=contextNode) {
return getBaseCompletions(offset, doc, currentNode, contextNode, true);
}
return Collections.emptyList();
}
private Collection<ICompletionProposal> getMoreIndentedCompletions(int offset, YamlDocument doc, SNode preciseContextNode, SNode currentNode) throws Exception {
SNode contextNode = getContextNode(doc, currentNode, offset, YamlIndentUtil.INDENT_STR);
if (preciseContextNode!=contextNode && isIndentRelaxable(contextNode)) {
YamlAssistContext context = getContext(doc, contextNode);
if (context!=null) {
return context.getCompletions(doc, currentNode, offset);
return addIndentations(context.getCompletions(doc, currentNode, offset));
}
}
return Collections.emptyList();
}
private boolean isRelaxable(SNode contextNode) throws Exception {
private boolean isIndentRelaxable(SNode contextNode) throws Exception {
return contextNode!=null && (
isBarrenKey(contextNode) ||
contextNode.getNodeType()==SNodeType.SEQ
@@ -163,8 +142,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
return false;
}
protected Collection<ICompletionProposal> getPreciseCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode)
throws Exception {
private Collection<? extends ICompletionProposal> getBaseCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, boolean onlyDashes) throws Exception {
if (contextNode!=null) {
YamlAssistContext context = getContext(doc, contextNode);
if (context==null && isDubiousKey(contextNode, offset)) {
@@ -173,7 +151,15 @@ public class YamlCompletionEngine implements ICompletionEngine {
context = getContext(doc, contextNode);
}
if (context!=null) {
return context.getCompletions(doc, current, offset);
Collection<ICompletionProposal> all = new ArrayList<>();
if (!onlyDashes) {
all.addAll(context.getCompletions(doc, current, offset));
}
YamlAssistContext relaxedContext = context.relax();
if (relaxedContext!=null) {
all.addAll(relaxedContext.getCompletions(doc, current, offset));
}
return all;
}
}
return Collections.emptyList();
@@ -204,8 +190,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
return null;
}
protected SNode getContextNode(YamlDocument doc, SNode node, int offset, int adjustIndent) throws Exception {
Assert.isLegal(adjustIndent>=0); //The code doesn't handle negative indents yet.
protected SNode getContextNode(YamlDocument doc, SNode node, int offset, String adjustIndentStr) throws Exception {
if (node==null) {
return null;
} else if (node.getNodeType()==SNodeType.KEY) {
@@ -218,22 +203,41 @@ public class YamlCompletionEngine implements ICompletionEngine {
return keyNode.getParent();
}
} else if (node.getNodeType()==SNodeType.RAW) {
//Treat raw node as a 'key node'. This is basically assuming that is misclasified
// by structure parser because the ':' was not yet typed into the document.
if (adjustIndentStr.startsWith("- ")) {
// We are trying to determine context node for a completion that starts witjh a '- '.
// Yaml indentation rules means we have to treat this differently because '-' doesn't
// have to be indented to be considered as nested under a key node!
int cursorIndent = doc.getColumn(offset);
int nodeIndent = node.getIndent();
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
while (node.getNodeType()!=SNodeType.DOC && (
nodeIndent==-1 ||
nodeIndent>currentIndent ||
nodeIndent==currentIndent && node.getNodeType()==SNodeType.SEQ
)) {
node = node.getParent();
nodeIndent = node.getIndent();
}
return node;
} else {
//Treat raw node as a 'key node'. This is basically assuming that is misclasified
// by structure parser because the ':' was not yet typed into the document.
//Complication: if line with cursor is empty or the cursor is inside the indentation
// area then the structure may not reflect correctly the context. This is because
// the correct context depends on text the user has not typed yet.(which will change the
// indentation level of the current line. So we must use the cursorIndentation
// rather than the structure-tree to determine the 'context' node.
int cursorIndent = YamlIndentUtil.add(doc.getColumn(offset), adjustIndent);
int nodeIndent = YamlIndentUtil.add(node.getIndent(), adjustIndent);
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
while (nodeIndent==-1 || (nodeIndent>=currentIndent && node.getNodeType()!=SNodeType.DOC)) {
node = node.getParent();
nodeIndent = node.getIndent();
//Complication: if line with cursor is empty or the cursor is inside the indentation
// area then the structure may not reflect correctly the context. This is because
// the correct context depends on text the user has not typed yet.(which will change the
// indentation level of the current line. So we must use the cursorIndentation
// rather than the structure-tree to determine the 'context' node.
int adjustIndent = adjustIndentStr==null? 0 : adjustIndentStr.length();
int cursorIndent = YamlIndentUtil.add(doc.getColumn(offset), adjustIndent);
int nodeIndent = YamlIndentUtil.add(node.getIndent(), adjustIndent);
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
while (nodeIndent==-1 || (nodeIndent>=currentIndent && node.getNodeType()!=SNodeType.DOC)) {
node = node.getParent();
nodeIndent = node.getIndent();
}
return node;
}
return node;
} else if (node.getNodeType()==SNodeType.SEQ) {
SSeqNode seqNode = (SSeqNode)node;
if (seqNode.isInValue(offset)) {
@@ -248,7 +252,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
}
protected SNode getContextNode(YamlDocument doc, SNode node, int offset) throws Exception {
return getContextNode(doc, node, offset, 0);
return getContextNode(doc, node, offset, "");
}
protected YamlPath getContextPath(YamlDocument doc, SNode node, int offset) throws Exception {

View File

@@ -2968,6 +2968,13 @@ public class ConcourseEditorTest {
"name",
"plan",
"public",
//Completions with '-'
"- aggregate",
"- do",
"- get",
"- put",
"- task",
"- try",
//Completions for nested context (i.e. task step)
"➔ attempts",
"➔ config",
@@ -2981,9 +2988,9 @@ public class ConcourseEditorTest {
"➔ params",
"➔ privileged",
"➔ tags",
"➔ task",
"➔ timeout"
);
"➔ timeout",
"➔ task"
);
}
@Test public void gotoSymbolInPipeline() throws Exception {
@@ -3245,6 +3252,120 @@ public class ConcourseEditorTest {
editor.assertProblems("get: ^versi^|resource does not exist");
}
@Test public void relaxedContentAssistContextForListItem_sameLine() throws Exception {
Editor editor;
editor = harness.newEditor(
"jobs:\n" +
"- name: build-docker-image\n" +
" plan: <*>\n"
);
editor.assertCompletionWithLabel("- put",
"jobs:\n" +
"- name: build-docker-image\n" +
" plan: \n" +
" - put: <*>\n"
);
editor = harness.newEditor(
"jobs:\n" +
"- name: build-docker-image\n" +
" plan: pu<*>\n"
);
editor.assertCompletionWithLabel("- put",
"jobs:\n" +
"- name: build-docker-image\n" +
" plan: \n" +
" - put: <*>\n"
);
}
@Test public void relaxedContentAssistContextForListItem_indented() throws Exception {
Editor editor;
editor = harness.newEditor(
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" - get: docker-git\n" +
" trigger: true\n" +
" <*>"
);
editor.assertCompletionWithLabel("- put",
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" - get: docker-git\n" +
" trigger: true\n" +
" - put: <*>"
);
}
@Test public void relaxedContentAssistContextForListItem_not_indented() throws Exception {
Editor editor;
//Note: this is a tricky case because when <*> lines up with 'plan' it will not be considered
// to be a child of 'plan' but a child of the 'job' instead.
// However, for hypothetical '- ' completions we'd have to treat as a child of plan instead!
editor = harness.newEditor(
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" - get: docker-git\n" +
" trigger: true\n" +
" <*>"
);
editor.assertCompletionWithLabel("- put",
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" - get: docker-git\n" +
" trigger: true\n" +
" - put: <*>"
);
editor = harness.newEditor(
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" <*>"
);
editor.assertCompletionWithLabel("- put",
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" - put: <*>"
);
editor = harness.newEditor(
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" pu<*>"
);
editor.assertCompletionWithLabel("- put",
"jobs:\n" +
"- name: build-docker-image\n" +
" serial: true\n" +
" plan:\n" +
" - put: <*>"
);
//TODO: cases were the CA query is not a empty string (on same line as key / on next line)
// assertContextualCompletions(conText, "<*>",
// "- put: <*>");
//
// assertContextualCompletions(conText, "pu<*>", "- put: <*>");
}
//////////////////////////////////////////////////////////////////////////////
private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {