Merge branch 'application-yml-completions'

This commit is contained in:
Kris De Volder
2016-11-07 15:18:54 -08:00
37 changed files with 1536 additions and 261 deletions

View File

@@ -7,9 +7,13 @@ import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexProvider {
private JavaProjectFinder javaProjectFinder = JavaProjectFinder.DEFAULT;
private JavaProjectFinder javaProjectFinder;
private SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault());
public DefaultSpringPropertyIndexProvider(JavaProjectFinder javaProjectFinder) {
this.javaProjectFinder = javaProjectFinder;
}
@Override
public FuzzyMap<PropertyInfo> getIndex(IDocument doc) {
IJavaProject jp = javaProjectFinder.find(doc);

View File

@@ -20,6 +20,11 @@ import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.boot.configurationmetadata.ValueProvider;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProviders;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
@@ -134,32 +139,32 @@ public class PropertyInfo {
return description;
}
// public HintProvider getHints(TypeUtil typeUtil, boolean dimensionAware) {
// Type type = TypeParser.parse(this.type);
// if (TypeUtil.isMap(type)) {
// return HintProviders.forMap(keyHints(typeUtil), valueHints(typeUtil), TypeUtil.getDomainType(type), dimensionAware);
// } else if (TypeUtil.isSequencable(type)) {
// if (dimensionAware) {
// if (TypeUtil.isSequencable(type)) {
// return HintProviders.forDomainAt(valueHints(typeUtil), TypeUtil.getDimensionality(type));
// } else {
// return HintProviders.forHere(valueHints(typeUtil));
// }
// } else {
// return HintProviders.forAllValueContexts(valueHints(typeUtil));
// }
// } else {
// return HintProviders.forHere(valueHints(typeUtil));
// }
// }
//
// private HintProvider keyHints(TypeUtil typeUtil) {
// return HintProviders.basic(typeUtil.getJavaProject(), keyHints, keyProvider);
// }
//
// private HintProvider valueHints(TypeUtil typeUtil) {
// return HintProviders.basic(typeUtil.getJavaProject(), valueHints, valueProvider);
// }
public HintProvider getHints(TypeUtil typeUtil, boolean dimensionAware) {
Type type = TypeParser.parse(this.type);
if (TypeUtil.isMap(type)) {
return HintProviders.forMap(keyHints(typeUtil), valueHints(typeUtil), TypeUtil.getDomainType(type), dimensionAware);
} else if (TypeUtil.isSequencable(type)) {
if (dimensionAware) {
if (TypeUtil.isSequencable(type)) {
return HintProviders.forDomainAt(valueHints(typeUtil), TypeUtil.getDimensionality(type));
} else {
return HintProviders.forHere(valueHints(typeUtil));
}
} else {
return HintProviders.forAllValueContexts(valueHints(typeUtil));
}
} else {
return HintProviders.forHere(valueHints(typeUtil));
}
}
private HintProvider keyHints(TypeUtil typeUtil) {
return HintProviders.basic(typeUtil.getJavaProject(), keyHints, keyProvider);
}
private HintProvider valueHints(TypeUtil typeUtil) {
return HintProviders.basic(typeUtil.getJavaProject(), valueHints, valueProvider);
}
public List<PropertySource> getSources() {
if (sources!=null) {

View File

@@ -17,7 +17,7 @@ import java.util.Map;
import java.util.function.Function;
import org.springframework.boot.configurationmetadata.ValueProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.CollectionUtil;

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 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.application.properties.metadata.completions;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import io.typefox.lsapi.CompletionItemKind;
public abstract class AbstractPropertyProposal extends ScoreableProposal {
protected final IDocument fDoc;
private final DocumentEdits proposalApplier;
private boolean isDeprecated = false;
public AbstractPropertyProposal(IDocument doc, DocumentEdits applier) {
this.proposalApplier = applier;
this.fDoc = doc;
}
@Override
public String getLabel() {
return getBaseDisplayString();
}
// public IRegion getSelection(IDocument document) {
// try {
// return proposalApplier.getSelection(document);
// } catch (Exception e) {
// Log.log(e);
// return null;
// }
// }
// public String getDisplayString() {
// StyledString styledText = getStyledDisplayString();
// return styledText.getString();
// }
// public Image getImage() {
// return null;
// }
// public IContextInformation getContextInformation() {
// return null;
// }
// @Override
// public StyledString getStyledDisplayString() {
// StyledString result = new StyledString();
// result = result.append(super.getStyledDisplayString());
// YType type = getType();
// if (type!=null) {
// String typeStr = niceTypeName(type);
// result.append(" : "+typeStr, StyledString.DECORATIONS_STYLER);
// }
// return result;
// }
protected boolean isDeprecated() {
return isDeprecated;
}
public void deprecate() {
if (!isDeprecated()) {
deemphasize();
deemphasize();
isDeprecated = true;
}
}
protected abstract YType getType();
protected abstract String getHighlightPattern();
protected abstract String getBaseDisplayString();
protected abstract String niceTypeName(YType type);
@Override
public CompletionItemKind getKind() {
return CompletionItemKind.Field;
}
@Override
public String toString() {
return getBaseDisplayString();
}
@Override
public final DocumentEdits getTextEdit() {
return this.proposalApplier;
}
// @Override
// public void apply(IDocument document) {
// try {
// proposalApplier.apply(document);
// } catch (Exception e) {
// EditorSupportActivator.log(e);
// }
// }
}

View File

@@ -0,0 +1,163 @@
/*******************************************************************************
* Copyright (c) 2014-2016 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.application.properties.metadata.completions;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.hints.ValueHintHoverInfo;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
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.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import io.typefox.lsapi.CompletionItemKind;
public class PropertyCompletionFactory {
public ICompletionProposal valueProposal(String value, String query, Type type, double score, DocumentEdits edits, ValueHintHoverInfo info) {
return new ScoreableProposal() {
@Override
public DocumentEdits getTextEdit() {
return edits;
}
@Override
public String getLabel() {
return value;
}
@Override
public CompletionItemKind getKind() {
return CompletionItemKind.Value;
}
@Override
public double getBaseScore() {
return score;
}
};
}
public ScoreableProposal property(IDocument doc, DocumentEdits applier, Match<PropertyInfo> prop, TypeUtil typeUtil) {
return new PropertyProposal(doc, applier, prop, typeUtil);
}
public ScoreableProposal beanProperty(IDocument doc, final String contextProperty, final Type contextType, final String pattern, final TypedProperty property, final double score, DocumentEdits applier, final TypeUtil typeUtil) {
AbstractPropertyProposal proposal = new AbstractPropertyProposal(doc, applier) {
// private HoverInfo hoverInfo;
// @Override
// public HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor) {
// if (hoverInfo==null) {
// String prefix = contextProperty==null?"":contextProperty+".";
// hoverInfo = new JavaTypeNavigationHoverInfo(prefix+property.getName(), property.getName(), contextType, property.getType(), typeUtil);
// }
// return hoverInfo;
// }
@Override
protected String getBaseDisplayString() {
return property.getName();
}
@Override
protected String getHighlightPattern() {
return pattern;
}
@Override
protected Type getType() {
return property.getType();
}
@Override
public double getBaseScore() {
return score;
}
@Override
protected String niceTypeName(YType type) {
return typeUtil.niceTypeName((Type) type);
}
};
if (property.isDeprecated()) {
proposal.deprecate();
}
return proposal;
}
private JavaProjectFinder documentContextFinder;
public PropertyCompletionFactory(JavaProjectFinder documentContextFinder) {
this.documentContextFinder = documentContextFinder;
}
private class PropertyProposal extends AbstractPropertyProposal {
private Match<PropertyInfo> match;
private Type type;
private TypeUtil typeUtil;
public PropertyProposal(IDocument doc, DocumentEdits applier, Match<PropertyInfo> match,
TypeUtil typeUtil) {
super(doc, applier);
this.typeUtil = typeUtil;
this.match = match;
if (match.data.isDeprecated()) {
deprecate();
}
}
// @Override
// public HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor) {
// return new SpringPropertyHoverInfo(documentContextFinder.getJavaProject(fDoc), match.data);
// }
@Override
protected String getBaseDisplayString() {
return match.data.getId();
}
@Override
public double getBaseScore() {
return match.score;
}
@Override
protected Type getType() {
if (type==null) {
type = TypeParser.parse(match.data.getType());
}
return type;
}
@Override
protected String getHighlightPattern() {
return match.getPattern();
}
@Override
protected String niceTypeName(YType type) {
return typeUtil.niceTypeName(((Type)type));
}
}
}

View File

@@ -0,0 +1,42 @@
package org.springframework.ide.vscode.application.properties.metadata.completions;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
/**
* Config object that determines some aspects of how the freedom of 'relaxed name binding'
* are taken into account when generating content-assist completions.
*
* @author Kris De Volder
*/
public class RelaxedNameConfig {
public static final RelaxedNameConfig ALIASSED = new RelaxedNameConfig(EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
public static final RelaxedNameConfig COMPLETION_DEFAULTS = new RelaxedNameConfig(EnumCaseMode.LOWER_CASE, BeanPropertyNameMode.HYPHENATED);
private EnumCaseMode enumMode = EnumCaseMode.LOWER_CASE;
private BeanPropertyNameMode beanMode = BeanPropertyNameMode.HYPHENATED;
public RelaxedNameConfig(EnumCaseMode enumMode, BeanPropertyNameMode beanMode) {
this.enumMode = enumMode;
this.beanMode = beanMode;
}
public EnumCaseMode getEnumMode() {
return enumMode;
}
public void setEnumMode(EnumCaseMode preferredEnumCompletions) {
this.enumMode = preferredEnumCompletions;
}
public BeanPropertyNameMode getBeanMode() {
return beanMode;
}
public void setBeanMode(BeanPropertyNameMode preferredBeanCompletions) {
this.beanMode = preferredBeanCompletions;
}
@Override
public String toString() {
return "RelaxedNameConfig("+enumMode+", "+beanMode+")";
}
}

View File

@@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (c) 2016 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.application.properties.metadata.hints;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
/**
* A single hint provider that combines the two kinds of 'hint' metadata that spring boot
* may contain (namely 'values' data and 'valueProvider' data).
* <p>
* This basic hint provider is not context-aware and returns the same value hints regardless of the
* yaml context.
* <p>
* This hint provider doesn't provide 'property' hints because property hints typically require
* context information (i.e the type of the parent context).
* <p>
* To make this provider 'context aware' it can be wrapped in an adapter created by calling
* one of the static methods in the {@link HintProviders} class.
*
* @author Kris De Volder
*/
public class BasicHintProvider implements HintProvider {
private IJavaProject javaProject;
private ImmutableList<ValueHint> valueHints;
private ValueProviderStrategy valueProvider;
public BasicHintProvider(IJavaProject javaProject,
ImmutableList<ValueHint> valueHints,
ValueProviderStrategy valueProvider) {
this.javaProject = javaProject;
this.valueHints = valueHints;
this.valueProvider = valueProvider;
}
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
//since this provider is not context sensitive it just returns itself (So hints provides in 'sub-contexts'
// are exaclty the same as hints in the parent context.
return this;
}
@Override
public List<StsValueHint> getValueHints(String query) {
Builder<StsValueHint> builder = ImmutableList.builder();
if (CollectionUtil.hasElements(valueHints)) {
for (ValueHint hint : valueHints) {
builder.add(StsValueHint.create(hint));
}
}
if (valueProvider!=null) {
Collection<StsValueHint> provided = valueProvider.getValuesNow(javaProject, query);
if (CollectionUtil.hasElements(provided)) {
builder.addAll(provided);
}
}
return builder.build();
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2016 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.application.properties.metadata.hints;
import java.util.List;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.yaml.path.YamlNavigable;
/**
* @author Kris De Volder
*/
public interface HintProvider extends YamlNavigable<HintProvider> {
List<StsValueHint> getValueHints(String query);
List<TypedProperty> getPropertyHints(String query);
}

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* Copyright (c) 2016 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.application.properties.metadata.hints;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import com.google.common.collect.ImmutableList;
/**
* Methods for creating hints providers that provide hint in specific kind of context.
*
* @author Kris De Volder
*/
public class HintProviders {
/**
* HintProvider that never returns any hints. This should be used
* instead of null pointer.
*/
public static final HintProvider NULL = new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
return NULL;
}
@Override
public List<StsValueHint> getValueHints(String query) {
return ImmutableList.of();
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
/**
* Creates a non-context-aware hint provider. Typically a hint provider is created by composing the result of
* this with one or more of the other methods in this class to wrap the basic provider so it becomes context-aware.
*/
public static HintProvider basic(IJavaProject jp, final ImmutableList<ValueHint> valueHints, final ValueProviderStrategy valueProvider) {
if (!CollectionUtil.hasElements(valueHints) && valueProvider==null) {
return NULL;
}
return new BasicHintProvider(jp, valueHints, valueProvider);
}
/**
* Create a hint provider that will return the given hints in the context following
* a traversal that goes down into a 'domain of' context a given number of times.
*/
public static HintProvider forDomainAt(final HintProvider valueHints, final int dim) {
if (isNull(valueHints)) {
return NULL;
}
if (dim==0) {
return forHere(valueHints);
}
return new HintProvider() {
public HintProvider traverse(YamlPathSegment s) throws Exception {
switch (s.getType()) {
case VAL_AT_INDEX:
case VAL_AT_KEY:
return forDomainAt(valueHints, dim-1);
default:
return NULL;
}
}
public List<StsValueHint> getValueHints(String query) {
return ImmutableList.of();
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
}
/**
* Only returns the given hints in this context but not one of its 'sub contexts'.
*/
public static HintProvider forHere(final HintProvider valueHints) {
if (isNull(valueHints)) {
return NULL;
}
return new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
return NULL;
}
@Override
public List<StsValueHint> getValueHints(String query) {
return valueHints.getValueHints(query);
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
}
/**
* REturns the given hints in this context and any of its subcontexts that expect values.
*/
public static HintProvider forAllValueContexts(final HintProvider valueProvider) {
if (isNull(valueProvider)) {
return NULL;
}
return new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
switch (s.getType()) {
case VAL_AT_INDEX:
case VAL_AT_KEY:
return this;
default:
return NULL;
}
}
@Override
public List<StsValueHint> getValueHints(String query) {
return valueProvider.getValueHints(query);
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
}
public static boolean isNull(HintProvider p) {
//If everyone is nice and doesn't ever use null pointers then the p==null check is
// not needed. But just in case.
return p == NULL || p==null;
}
public static HintProvider forMap(HintProvider _keyProvider, HintProvider _valueProvider, final Type valueType, final boolean dimensionAware) {
final HintProvider keyProvider = notNull(_keyProvider);
final HintProvider valueProvider = notNull(_valueProvider);
if (isNull(keyProvider) && isNull(valueProvider)) {
return NULL;
}
return new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
switch (s.getType()) {
case VAL_AT_INDEX:
case VAL_AT_KEY:
if (dimensionAware) {
return forHere(valueProvider);
} else {
return forAllValueContexts(valueProvider);
}
default:
return NULL;
}
}
@Override
public List<StsValueHint> getValueHints(String query) {
if (dimensionAware) {
//pickier, completions only suggested in the domain of map, but not for map itself.
return ImmutableList.of();
} else {
return valueProvider.getValueHints(query);
}
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
List<StsValueHint> keyHints = keyProvider.getValueHints(query);
if (CollectionUtil.hasElements(keyHints)) {
List<TypedProperty> props = new ArrayList<>(keyHints.size());
for (StsValueHint keyHint : keyHints) {
Object key = keyHint.getValue();
if (key instanceof String) {
props.add(new TypedProperty((String)key, valueType, null));
}
}
return props;
}
return ImmutableList.of();
}
};
}
/**
* Protection against bad code passing us null pointers.
*/
private static HintProvider notNull(HintProvider p) {
if (p==null) {
return NULL;
}
return p;
}
}

View File

@@ -1,4 +1,4 @@
package org.springframework.ide.vscode.application.properties.metadata.types;
package org.springframework.ide.vscode.application.properties.metadata.hints;
import static org.springframework.ide.vscode.application.properties.metadata.util.DeprecationUtil.*;
@@ -6,6 +6,7 @@ import javax.inject.Provider;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.util.DeprecationUtil;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IJavaProject;

View File

@@ -0,0 +1,9 @@
package org.springframework.ide.vscode.application.properties.metadata.hints;
public class ValueHintHoverInfo {
public ValueHintHoverInfo(StsValueHint hint) {
// TODO Auto-generated constructor stub
}
}

View File

@@ -25,6 +25,7 @@ import javax.inject.Provider;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.util.DeprecationUtil;
import org.springframework.ide.vscode.commons.java.Flags;
import org.springframework.ide.vscode.commons.java.IField;

View File

@@ -16,6 +16,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.BadLocationExc
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
import org.springframework.ide.vscode.commons.languageserver.util.IRegion;
import org.springframework.ide.vscode.commons.languageserver.util.Region;
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;
import org.springframework.ide.vscode.commons.util.Assert;
import io.typefox.lsapi.TextEdit;
@@ -322,12 +323,12 @@ public class DocumentEdits implements ProposalApplier {
return null;
}
public TextReplace asReplacement(IDocument doc) throws BadLocationException {
public TextReplace asReplacement(TextDocument doc) throws BadLocationException {
if (!edits.isEmpty()) {
int start = edits.stream().mapToInt(Edit::getStart).min().getAsInt();
int end = edits.stream().mapToInt(Edit::getEnd).max().getAsInt();
DocumentState state = new DocumentState(doc);
DocumentState state = new DocumentState(doc.copy());
for (Edit edit : edits) {
edit.apply(state);
}

View File

@@ -0,0 +1,105 @@
package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.Comparator;
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.
private double deemphasizedBy = 0.0;
/**
* A sorter suitable for sorting ScoreableProposals based on their score.
*/
public static final Comparator<ICompletionProposal> COMPARATOR = new Comparator<ICompletionProposal>() {
public int compare(ICompletionProposal p1, ICompletionProposal p2) {
if (p1 instanceof ScoreableProposal && p2 instanceof ScoreableProposal) {
double s1 = ((ScoreableProposal)p1).getScore();
double s2 = ((ScoreableProposal)p2).getScore();
if (s1==s2) {
String name1 = ((ScoreableProposal)p1).getLabel();
String name2 = ((ScoreableProposal)p2).getLabel();
return name1.compareTo(name2);
} else {
return Double.compare(s2, s1);
}
}
return 0;
}
};
public abstract double getBaseScore();
public final double getScore() {
return getBaseScore() - deemphasizedBy;
}
public ScoreableProposal deemphasize() {
deemphasizedBy+= DEEMP_VALUE;
return this;
}
public boolean isDeemphasized() {
return deemphasizedBy > 0;
}
// @Override
// public boolean isAutoInsertable() {
// return !isDeemphasized();
// }
// public StyledString getStyledDisplayString() {
// StyledString result = new StyledString();
// highlightPattern(getHighlightPattern(), getBaseDisplayString(), result);
// return result;
// }
// private void highlightPattern(String pattern, String data, StyledString result) {
// Styler highlightStyle = CompletionFactory.HIGHLIGHT;
// Styler plainStyle = isDeemphasized()?CompletionFactory.DEEMPHASIZE:CompletionFactory.NULL_STYLER;
// if (isDeprecated()) {
// highlightStyle = CompletionFactory.compose(highlightStyle, CompletionFactory.DEPRECATE);
// plainStyle = CompletionFactory.compose(plainStyle, CompletionFactory.DEPRECATE);
// }
// if (StringUtils.hasText(pattern)) {
// int dataPos = 0; int dataLen = data.length();
// int patternPos = 0; int patternLen = pattern.length();
//
// while (dataPos<dataLen && patternPos<patternLen) {
// int pChar = pattern.charAt(patternPos++);
// int highlightPos = data.indexOf(pChar, dataPos);
// if (dataPos<highlightPos) {
// result.append(data.substring(dataPos, highlightPos), plainStyle);
// }
// result.append(data.charAt(highlightPos), highlightStyle);
// dataPos = highlightPos+1;
// }
// if (dataPos<dataLen) {
// result.append(data.substring(dataPos), plainStyle);
// }
// } else { //no pattern to highlight
// result.append(data, plainStyle);
// }
// }
// protected abstract boolean isDeprecated();
// protected abstract String getHighlightPattern();
// protected abstract String getBaseDisplayString();
// @Override
// public String getAdditionalProposalInfo() {
// HoverInfo hoverInfo = getAdditionalProposalInfo(new NullProgressMonitor());
// if (hoverInfo!=null) {
// return hoverInfo.getHtml();
// }
// return null;
// }
// @Override
// public abstract HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor);
// @Override
// public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
// return null;
// }
//
// @Override
// public int getPrefixCompletionStart(IDocument document, int completionOffset) {
// return completionOffset;
// }
}

View File

@@ -0,0 +1,16 @@
package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.concurrent.CompletableFuture;
import io.typefox.lsapi.CompletionItem;
import io.typefox.lsapi.CompletionList;
import io.typefox.lsapi.TextDocumentPositionParams;
/**
* Interface that needs to be implemented by a 'completion engine' which can be easily
* wired-up to provide completions for a Vscode language server.
*/
public interface VscodeCompletionEngine {
CompletableFuture<CompletionList> getCompletions(TextDocumentPositionParams params);
CompletableFuture<CompletionItem> resolveCompletion(CompletionItem unresolved);
}

View File

@@ -0,0 +1,124 @@
package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.SortKeys;
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;
import org.springframework.ide.vscode.commons.util.Futures;
import org.springframework.ide.vscode.commons.util.StringUtil;
import io.typefox.lsapi.CompletionItem;
import io.typefox.lsapi.CompletionList;
import io.typefox.lsapi.TextDocumentPositionParams;
import io.typefox.lsapi.impl.CompletionItemImpl;
import io.typefox.lsapi.impl.CompletionListImpl;
import io.typefox.lsapi.impl.PositionImpl;
import io.typefox.lsapi.impl.TextEditImpl;
/**
* Adapts a {@link ICompletionEngine}, wrapping it, to implement {@link VscodeCompletionEngine}
*/
public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
final private int MAX_COMPLETIONS = 10;
final static Logger logger = LoggerFactory.getLogger(VscodeCompletionEngineAdapter.class);
public static final String VS_CODE_CURSOR_MARKER = "{{}}";
private SimpleLanguageServer server;
private ICompletionEngine engine;
public VscodeCompletionEngineAdapter(SimpleLanguageServer server, ICompletionEngine engine) {
this.server = server;
this.engine = engine;
}
@Override
public CompletableFuture<CompletionList> getCompletions(TextDocumentPositionParams params) {
//TODO: This returns a CompletableFuture which suggests we should try to do expensive work asyncly.
// We are currently just doing all this in a blocking way and wrapping the already computed list into
// a trivial pre-resolved future.
try {
SimpleTextDocumentService documents = server.getTextDocumentService();
TextDocument doc = documents.get(params);
if (doc!=null) {
int offset = doc.toOffset(params.getPosition());
List<ICompletionProposal> completions = new ArrayList<>(engine.getCompletions(doc, offset));
Collections.sort(completions, ScoreableProposal.COMPARATOR);
CompletionListImpl list = new CompletionListImpl();
list.setIncomplete(false);
List<CompletionItemImpl> items = new ArrayList<>(completions.size());
SortKeys sortkeys = new SortKeys();
int count = 0;
for (ICompletionProposal c : completions) {
count++;
if (count>MAX_COMPLETIONS) {
list.setIncomplete(true);
break;
}
try {
items.add(adaptItem(doc, c, sortkeys));
} catch (Exception e) {
logger.error("error computing completion", e);
}
}
list.setItems(items);
return Futures.of(list);
}
} catch (Exception e) {
logger.error("error computing completions", e);
}
return SimpleTextDocumentService.NO_COMPLETIONS;
}
private CompletionItemImpl adaptItem(TextDocument doc, ICompletionProposal completion, SortKeys sortkeys) throws Exception {
CompletionItemImpl item = new CompletionItemImpl();
item.setLabel(completion.getLabel());
item.setKind(completion.getKind());
item.setSortText(sortkeys.next());
item.setFilterText(completion.getLabel());
adaptEdits(item, doc, completion.getTextEdit());
return item;
}
private void adaptEdits(CompletionItemImpl item, TextDocument doc, DocumentEdits edits) throws Exception {
TextReplace replaceEdit = edits.asReplacement(doc);
if (replaceEdit==null) {
//The original edit does nothing.
item.setInsertText("");
} else {
TextDocument newDoc = doc.copy();
edits.apply(newDoc);
TextEditImpl vscodeEdit = new TextEditImpl();
vscodeEdit.setRange(newDoc.toRange(replaceEdit.start, replaceEdit.end-replaceEdit.start));
vscodeEdit.setNewText(vscodeIndentFix(vscodeEdit.getRange().getStart(), replaceEdit.newText));
//TODO: cursor offset within newText? for now we assume its always at the end.
item.setTextEdit(vscodeEdit);
}
}
private String vscodeIndentFix(PositionImpl start, String newText) {
//Vscode applies some magic indent to a multi-line edit text. We do everything ourself so we have adjust for the magic
// and do some kind of 'inverse magic' here.
int vscodeMagicIndent = start.getCharacter();
return StringUtil.stripIndentation(vscodeMagicIndent, newText);
}
@Override
public CompletableFuture<CompletionItem> resolveCompletion(CompletionItem unresolved) {
//TODO: item is pre-resoved so we don't do anything, but we really should somehow defer some work, such as
// for example computing docs and edits to resolve time.
//The tricky part is that we have to probably remember infos about the unresolved elements somehow so we can resolve later.
return Futures.of(unresolved);
}
}

View File

@@ -0,0 +1,8 @@
package org.springframework.ide.vscode.commons.languageserver.hover;
/**
* Placeholder. Still need to figure out what exactly we should do with this in vscode.
*/
public interface HoverInfo {
}

View File

@@ -133,6 +133,7 @@ public class SimpleTextDocumentService implements TextDocumentService {
@Override
public void didClose(DidCloseTextDocumentParams params) {
System.out.println("closing: "+params.getTextDocument().getUri());
String url = params.getTextDocument().getUri();
if (url!=null) {
documents.remove(url);

View File

@@ -0,0 +1,28 @@
package org.springframework.ide.vscode.commons.languageserver.util;
import java.util.Iterator;
/**
* Utility to generate 'sort keys' in ascending order.
* <p>
* VSCode uses a String in each completion to determine the order of completions.
* We use a 'score' based on how well a key matches what was typed.
* <p>
* To go from a 'score' to a sort-key we presort our proposals and then assign
* a sort key for vscode.
*/
public class SortKeys implements Iterator<String> {
private int counter;
@Override
public boolean hasNext() {
return true;
}
@Override
public String next() {
return String.format("%05d", counter++);
}
}

View File

@@ -131,4 +131,48 @@ public class StringUtil {
return f.format(d);
}
private static final Pattern NEWLINE = Pattern.compile("(\\n|\\r)+");
/**
* Removes a given number of spaces from all lines of text in a String,
* except for the first line.
* <p>
* Note: this method only deals with spaces its not suitable for strings
* which use tabs for indentation.
*/
public static String stripIndentation(int indent, String indentedText) {
StringBuilder out = new StringBuilder();
boolean first = true;
Matcher matcher = NEWLINE.matcher(indentedText);
int pos = 0;
while (matcher.find()) {
int newline = matcher.start();
int newline_end = matcher.end();
String line = indentedText.substring(pos, newline);
if (first) {
first = false;
} else {
line = stripIndentationFromLine(indent, line);
}
out.append(line);
out.append(indentedText.substring(newline, newline_end));
pos = newline_end;
}
String line = indentedText.substring(pos);
if (!first) {
line = stripIndentationFromLine(indent, line);
}
out.append(line);
return out.toString();
}
public static String stripIndentationFromLine(int indent, String line) {
int start = 0;
while (start<line.length() && start < indent && line.charAt(start)==' ') {
start++;
}
return line.substring(start);
}
}

View File

@@ -1,9 +1,8 @@
package org.springframework.ide.vscode.commons.yaml.completion;
import java.util.Comparator;
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.languageserver.util.IDocument;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
@@ -13,110 +12,6 @@ import io.typefox.lsapi.CompletionItemKind;
public class DefaultCompletionFactory implements CompletionFactory {
/**
* A sorter suitable for sorting proposals created by this factory
*/
public static final Comparator<ICompletionProposal> COMPARATOR = new Comparator<ICompletionProposal>() {
public int compare(ICompletionProposal p1, ICompletionProposal p2) {
if (p1 instanceof ScoreableProposal && p2 instanceof ScoreableProposal) {
double s1 = ((ScoreableProposal)p1).getScore();
double s2 = ((ScoreableProposal)p2).getScore();
if (s1==s2) {
String name1 = ((ScoreableProposal)p1).getLabel();
String name2 = ((ScoreableProposal)p2).getLabel();
return name1.compareTo(name2);
} else {
return Double.compare(s2, s1);
}
}
return 0;
}
};
public static abstract class ScoreableProposal implements ICompletionProposal {
private static final double DEEMP_VALUE = 100000; // should be large enough to move deemphasized stuff to bottom of list.
private double deemphasizedBy = 0.0;
public abstract double getBaseScore();
public final double getScore() {
return getBaseScore() - deemphasizedBy;
}
public ScoreableProposal deemphasize() {
deemphasizedBy+= DEEMP_VALUE;
return this;
}
public boolean isDeemphasized() {
return deemphasizedBy > 0;
}
// @Override
// public boolean isAutoInsertable() {
// return !isDeemphasized();
// }
// public StyledString getStyledDisplayString() {
// StyledString result = new StyledString();
// highlightPattern(getHighlightPattern(), getBaseDisplayString(), result);
// return result;
// }
// private void highlightPattern(String pattern, String data, StyledString result) {
// Styler highlightStyle = CompletionFactory.HIGHLIGHT;
// Styler plainStyle = isDeemphasized()?CompletionFactory.DEEMPHASIZE:CompletionFactory.NULL_STYLER;
// if (isDeprecated()) {
// highlightStyle = CompletionFactory.compose(highlightStyle, CompletionFactory.DEPRECATE);
// plainStyle = CompletionFactory.compose(plainStyle, CompletionFactory.DEPRECATE);
// }
// if (StringUtils.hasText(pattern)) {
// int dataPos = 0; int dataLen = data.length();
// int patternPos = 0; int patternLen = pattern.length();
//
// while (dataPos<dataLen && patternPos<patternLen) {
// int pChar = pattern.charAt(patternPos++);
// int highlightPos = data.indexOf(pChar, dataPos);
// if (dataPos<highlightPos) {
// result.append(data.substring(dataPos, highlightPos), plainStyle);
// }
// result.append(data.charAt(highlightPos), highlightStyle);
// dataPos = highlightPos+1;
// }
// if (dataPos<dataLen) {
// result.append(data.substring(dataPos), plainStyle);
// }
// } else { //no pattern to highlight
// result.append(data, plainStyle);
// }
// }
// protected abstract boolean isDeprecated();
// protected abstract String getHighlightPattern();
// protected abstract String getBaseDisplayString();
// @Override
// public String getAdditionalProposalInfo() {
// HoverInfo hoverInfo = getAdditionalProposalInfo(new NullProgressMonitor());
// if (hoverInfo!=null) {
// return hoverInfo.getHtml();
// }
// return null;
// }
// @Override
// public abstract HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor);
// @Override
// public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
// return null;
// }
//
// @Override
// public int getPrefixCompletionStart(IDocument document, int completionOffset) {
// return completionOffset;
// }
}
public static class BeanPropertyProposal extends ScoreableProposal {
private IDocument doc;

View File

@@ -105,8 +105,9 @@ public class YamlPathEdits extends DocumentEdits {
buf.append(":");
if (i<path.size()-1) {
indent += YamlIndentUtil.INDENT_BY;
} else {
buf.append(indentUtil.applyIndentation(appendText, indent));
}
buf.append(indentUtil.applyIndentation(appendText, indent));
}
return buf.toString();
}

View File

@@ -7,7 +7,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.ide.vscode.commons.languageserver.util.IRegion;
@@ -691,7 +690,7 @@ public class YamlStructureParser {
String indentedText = StringUtil.trimEnd(doc.textBetween(start, end));
int indent = getIndent();
if (indent>0) {
return stripIndentation(indent, indentedText);
return StringUtil.stripIndentation(indent, indentedText);
}
return indentedText;
}
@@ -702,39 +701,4 @@ public class YamlStructureParser {
return keyAliases.getKeyAliases(key);
}
public static String stripIndentation(int indent, String indentedText) {
StringBuilder out = new StringBuilder();
Pattern NEWLINE = Pattern.compile("(\\n|\\r)+");
boolean first = true;
Matcher matcher = NEWLINE.matcher(indentedText);
int pos = 0;
while (matcher.find()) {
int newline = matcher.start();
int newline_end = matcher.end();
String line = indentedText.substring(pos, newline);
if (first) {
first = false;
} else {
line = stripIndentationFromLine(indent, line);
}
out.append(line);
out.append(indentedText.substring(newline, newline_end));
pos = newline_end;
}
String line = indentedText.substring(pos);
if (!first) {
line = stripIndentationFromLine(indent, line);
}
out.append(line);
return out.toString();
}
private static String stripIndentationFromLine(int indent, String line) {
int start = 0;
while (start<line.length() && start < indent && line.charAt(start)==' ') {
start++;
}
return line.substring(start);
}
}

View File

@@ -258,8 +258,9 @@ public class LanguageServerHarness {
public void assertCompletion(String textBefore, String expectTextAfter) throws Exception {
Editor editor = newEditor(textBefore);
assertNotNull(editor.getCompletions());
assertFalse(editor.getCompletions().isEmpty());
List<CompletionItem> completions = editor.getCompletions();
assertNotNull(completions);
assertFalse(completions.isEmpty());
CompletionItem completion = editor.getFirstCompletion();
editor.apply(completion);
assertEquals(expectTextAfter, editor.getText());

View File

@@ -14,6 +14,7 @@ import org.springframework.ide.vscode.application.properties.metadata.SpringProp
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
@@ -32,6 +33,8 @@ public abstract class AbstractPropsEditorTest {
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
protected PropertyIndexHarness md;
protected final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject();
private LanguageServerHarness harness;
private IJavaProject testProject;
private TypeUtil typeUtil;
@@ -47,6 +50,10 @@ public abstract class AbstractPropsEditorTest {
return harness.newEditor(contents);
}
private IJavaProject getTestProject() {
return testProject;
}
@Before
public void setup() throws Exception {
md = new PropertyIndexHarness();