Relaxing yaml proposals for '- '.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -77,8 +77,7 @@ public abstract class AbstractPropertyProposal extends ScoreableProposal {
|
||||
}
|
||||
public void deprecate() {
|
||||
if (!isDeprecated()) {
|
||||
deemphasize();
|
||||
deemphasize();
|
||||
deemphasize(DEEMP_DEPRECATION);
|
||||
isDeprecated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
|
||||
package org.springframework.ide.vscode.commons.maven;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user