Merge branch 'master' into async_validations
Conflicts: headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java
This commit is contained in:
@@ -47,6 +47,8 @@ public class DocumentEdits implements ProposalApplier {
|
||||
|
||||
private static final Pattern NON_WS_CHAR = Pattern.compile("\\S");
|
||||
|
||||
private boolean isRelativeIndent = false;
|
||||
|
||||
// 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
|
||||
|
||||
@@ -117,8 +117,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
|
||||
private Mono<CompletionList> getCompletionsMono(TextDocumentPositionParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
if (doc!=null) {
|
||||
if (documents.get(params) != null) {
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
return Mono.fromCallable(() -> {
|
||||
if (resolver!=null) {
|
||||
//Assumes we don't have more than one completion request in flight from the client.
|
||||
|
||||
@@ -52,31 +52,33 @@ public class SimpleDefinitionFinder<T extends SimpleLanguageServer> implements D
|
||||
* currently pointed at in the current document using String.indexOf.
|
||||
*/
|
||||
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
|
||||
try {
|
||||
try {
|
||||
TextDocument doc = server.getTextDocumentService().get(params);
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
int start = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(start))) {
|
||||
start--;
|
||||
}
|
||||
start = start+1;
|
||||
int end = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(end))) {
|
||||
end++;
|
||||
}
|
||||
String word = doc.textBetween(start, end);
|
||||
Log.log("Looking for definition of '"+word+"'");
|
||||
String text = doc.get();
|
||||
int def = text.indexOf(word);
|
||||
if (def>=0) {
|
||||
return Flux.just(
|
||||
new Location(params.getTextDocument().getUri(),
|
||||
doc.toRange(def, word.length())
|
||||
if (doc != null) {
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
int start = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(start))) {
|
||||
start--;
|
||||
}
|
||||
start = start+1;
|
||||
int end = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(end))) {
|
||||
end++;
|
||||
}
|
||||
String word = doc.textBetween(start, end);
|
||||
Log.log("Looking for definition of '"+word+"'");
|
||||
String text = doc.get();
|
||||
int def = text.indexOf(word);
|
||||
if (def>=0) {
|
||||
return Flux.just(
|
||||
new Location(params.getTextDocument().getUri(),
|
||||
doc.toRange(def, word.length())
|
||||
)
|
||||
)
|
||||
)
|
||||
.doOnNext((Location loc) -> {
|
||||
Log.log("definition: "+loc);
|
||||
});
|
||||
.doOnNext((Location loc) -> {
|
||||
Log.log("definition: "+loc);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
|
||||
package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -18,9 +17,9 @@ import java.util.Map;
|
||||
* retrieve properties from the settings object.
|
||||
*/
|
||||
public class Settings {
|
||||
|
||||
|
||||
private Object settings;
|
||||
|
||||
|
||||
public Settings(Object settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
@@ -32,7 +31,7 @@ public class Settings {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Object getProperty(String... names) {
|
||||
return getProperty(settings, names, 0);
|
||||
}
|
||||
@@ -49,6 +48,8 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return settings.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,7 @@ public class SnippetBuilder {
|
||||
* be overridden by subclasses to support other formats.
|
||||
* <p>
|
||||
* The default implementation creates place holder strings that
|
||||
* match the undocumented format vscode currently supports.
|
||||
* <p>
|
||||
* Note: this format is explicitly different from what the LSP
|
||||
* specifies. So it is very likely we should change this implementation
|
||||
* in the near future.
|
||||
* match format specified by LSP 3.0.
|
||||
*/
|
||||
protected String createPlaceHolder(int id) {
|
||||
return "$"+id;
|
||||
@@ -50,4 +46,24 @@ public class SnippetBuilder {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void newline(int indent) {
|
||||
buf.append("\n");
|
||||
for (int i = 0; i < indent; i++) {
|
||||
buf.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureSpace() {
|
||||
if (buf.length()>0 && !Character.isWhitespace(buf.charAt(buf.length()-1))) {
|
||||
buf.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of placeholder that where inserted in the snippet.
|
||||
*/
|
||||
public int getPlaceholderCount() {
|
||||
return nextPlaceHolderId-1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ import com.google.common.collect.ImmutableSet;
|
||||
public class EnumValueParser implements ValueParser {
|
||||
|
||||
private String typeName;
|
||||
private Provider<Collection<String>> values;
|
||||
private final boolean longRunning;
|
||||
|
||||
private Provider<PartialCollection<String>> values;
|
||||
private final boolean longRunning;
|
||||
|
||||
|
||||
public EnumValueParser(String typeName, String... values) {
|
||||
this(typeName, ImmutableSet.copyOf(values));
|
||||
@@ -37,16 +39,28 @@ public class EnumValueParser implements ValueParser {
|
||||
this(typeName, false /* not long running by default */, provider(values));
|
||||
}
|
||||
|
||||
private static <T> Provider<PartialCollection<T>> provider(Collection<T> values) {
|
||||
return () -> PartialCollection.compute(() -> values);
|
||||
}
|
||||
|
||||
private static <T> Provider<PartialCollection<T>> provider(Callable<Collection<T>> values) {
|
||||
return () -> PartialCollection.compute(() -> values.call());
|
||||
}
|
||||
|
||||
public EnumValueParser(String typeName, boolean longRunning, Callable<Collection<String>> values) {
|
||||
this(typeName, longRunning, provider(values));
|
||||
}
|
||||
|
||||
public EnumValueParser(String typeName, boolean longRunning, Provider<Collection<String>> values) {
|
||||
public EnumValueParser(String typeName, boolean longRunning, Provider<PartialCollection<String>> values) {
|
||||
this.typeName = typeName;
|
||||
this.values = values;
|
||||
this.longRunning = longRunning;
|
||||
}
|
||||
|
||||
public EnumValueParser(String name, PartialCollection<String> values) {
|
||||
this(name, false /* not long running by default */, () -> values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(String str) throws Exception {
|
||||
// IMPORTANT: check the text FIRST before fetching values
|
||||
@@ -56,13 +70,13 @@ public class EnumValueParser implements ValueParser {
|
||||
throw errorOnBlank(createBlankTextErrorMessage());
|
||||
}
|
||||
|
||||
Collection<String> values = this.values.get();
|
||||
PartialCollection<String> values = this.values.get();
|
||||
|
||||
// If values is not known (null) then just assume the str is acceptable.
|
||||
if (values == null || values.contains(str)) {
|
||||
// If values is not fully known then just assume the str is acceptable.
|
||||
if (values == null || !values.isComplete() || values.getElements().contains(str)) {
|
||||
return str;
|
||||
} else {
|
||||
throw errorOnParse(createErrorMessage(str, values));
|
||||
throw errorOnParse(createErrorMessage(str, values.getElements()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,21 +95,6 @@ public class EnumValueParser implements ValueParser {
|
||||
protected Exception errorOnBlank(String message) {
|
||||
return new ValueParseException(message);
|
||||
}
|
||||
|
||||
private static <T> Provider<T> provider(T values) {
|
||||
return () -> values;
|
||||
}
|
||||
|
||||
private static <T> Provider<T> provider(Callable<T> values) {
|
||||
return () -> {
|
||||
try {
|
||||
return values.call();
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean longRunning() {
|
||||
return this.longRunning ;
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
@@ -76,4 +77,27 @@ public class ExternalCommand {
|
||||
// org.junit.Assert.assertEquals(0, process.getExitValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(command);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ExternalCommand other = (ExternalCommand) obj;
|
||||
if (!Arrays.equals(command, other.command))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*******************************************************************************
|
||||
* 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.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* A partial collection instance represents collection of
|
||||
* elements which may not be entirely known.
|
||||
* <p>
|
||||
* For unknown collection, an optional explanation, in the
|
||||
* form of a caught exception may be stored as well.
|
||||
*/
|
||||
public class PartialCollection<T> {
|
||||
|
||||
private static final PartialCollection<?> UNKNOWN = new PartialCollection<>(ImmutableSet.of(), false);
|
||||
private static final PartialCollection<?> EMPTY = new PartialCollection<>(ImmutableSet.of(), true);
|
||||
|
||||
final private ImmutableCollection<T> knownElements;
|
||||
final private boolean isComplete;
|
||||
final private Throwable explanation;
|
||||
|
||||
private PartialCollection(ImmutableCollection<T> knownElements, boolean isComplete, Throwable error) {
|
||||
this.knownElements = knownElements;
|
||||
this.isComplete = isComplete;
|
||||
this.explanation = error;
|
||||
}
|
||||
|
||||
private PartialCollection(ImmutableCollection<T> knownElements, boolean isComplete) {
|
||||
this.knownElements = knownElements;
|
||||
this.isComplete = isComplete;
|
||||
this.explanation = null;
|
||||
}
|
||||
|
||||
private PartialCollection(ImmutableCollection<T> knownElements, Throwable error) {
|
||||
this.knownElements = knownElements;
|
||||
this.isComplete = error==null;
|
||||
this.explanation = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PartialCollection} by executing some computation that returs a collectioon.
|
||||
* If the computation throws the resulting collection will be completely unknown, otherwise
|
||||
* it will be completely known.
|
||||
*/
|
||||
public static <T> PartialCollection<T> compute(Callable<Collection<T>> computer) {
|
||||
try {
|
||||
Collection<T> allValues = computer.call();
|
||||
if (allValues==null) {
|
||||
return PartialCollection.unknown();
|
||||
}
|
||||
return new PartialCollection<>(ImmutableSet.copyOf(allValues), true);
|
||||
} catch (Exception e) {
|
||||
return new PartialCollection<>(ImmutableSet.of(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PartialCollection} by executing some computation that returs a collectioon.
|
||||
* If the computation throws the resulting collection will be completely unknown, otherwise
|
||||
* it will be completely known.
|
||||
*/
|
||||
public static <T> PartialCollection<T> fromCallable(Callable<PartialCollection<T>> computer) {
|
||||
try {
|
||||
return computer.call();
|
||||
} catch (Exception e) {
|
||||
return new PartialCollection<>(ImmutableSet.of(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return All the known elements of this partial collection.
|
||||
*/
|
||||
public Collection<T> getElements() {
|
||||
return knownElements;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the totally unknown collection. I.e. a unknown collection with no known elements
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> PartialCollection<T> unknown() {
|
||||
return (PartialCollection<T>) UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like map on streams, but silently drops any null elements returned by the mapper.
|
||||
*/
|
||||
public <R> PartialCollection<R> map(Function<? super T, ? extends R> mapper) {
|
||||
ImmutableSet<R> mappedElements = getElements().stream().map((x) -> mapper.apply(x)).filter(x -> x!=null).collect(CollectorUtil.toImmutableSet());
|
||||
return new PartialCollection<R>(mappedElements, isComplete, explanation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a empty collection (i.e. the collection is know to be empty).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> PartialCollection<T> empty() {
|
||||
return (PartialCollection<T>) EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a copy of this collection that has the same known elements but also has unknown elements.
|
||||
*/
|
||||
public PartialCollection<T> addUncertainty() {
|
||||
if (!this.isComplete()) {
|
||||
return this; //No need to make a copy. Current collection is already only partially known.
|
||||
}
|
||||
return new PartialCollection<>(knownElements, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A completely unknown collection with a given exception explaining the reason.
|
||||
*/
|
||||
public static <T> PartialCollection<T> unknown(Exception e) {
|
||||
Assert.isLegal(e!=null);
|
||||
return new PartialCollection<>(ImmutableSet.of(), e);
|
||||
}
|
||||
|
||||
public PartialCollection<T> addAll(Collection<T> moreElements) {
|
||||
ImmutableSet.Builder<T> elements = ImmutableSet.builder();
|
||||
elements.addAll(getElements());
|
||||
elements.addAll(moreElements);
|
||||
return new PartialCollection<>(elements.build(), isComplete, explanation);
|
||||
}
|
||||
|
||||
public Throwable getExplanation() {
|
||||
return explanation;
|
||||
}
|
||||
|
||||
public PartialCollection<T> add(@SuppressWarnings("unchecked") T... values) {
|
||||
return addAll(Arrays.asList(values));
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.linetracker.DefaultLineTracker;
|
||||
import org.springframework.ide.vscode.commons.util.text.linetracker.ILineTracker;
|
||||
|
||||
@@ -88,11 +89,14 @@ public class TextDocument implements IDocument {
|
||||
|
||||
public synchronized void apply(DidChangeTextDocumentParams params) throws BadLocationException {
|
||||
int newVersion = params.getTextDocument().getVersion();
|
||||
Assert.isLegal(version<newVersion);
|
||||
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
|
||||
apply(change);
|
||||
if (version<newVersion) {
|
||||
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
|
||||
apply(change);
|
||||
}
|
||||
this.version = newVersion;
|
||||
} else {
|
||||
Log.warn("Change event with bad version ignored: "+params);
|
||||
}
|
||||
this.version = newVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.completion;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*;
|
||||
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DASH_PROPOSAL;
|
||||
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DEPRECATION;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -30,9 +31,9 @@ import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.PartialCollection;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParseException;
|
||||
import org.springframework.ide.vscode.commons.yaml.completion.DefaultCompletionFactory.ValueProposal;
|
||||
import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
||||
@@ -44,6 +45,8 @@ import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.Snippet;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
|
||||
@@ -97,6 +100,28 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
List<ICompletionProposal> completions = getValueCompletions(doc, node, offset, query);
|
||||
if (completions.isEmpty()) {
|
||||
completions = getKeyCompletions(doc, offset, query);
|
||||
TypeBasedSnippetProvider snippetProvider = typeUtil.getSnippetProvider();
|
||||
if (snippetProvider!=null) {
|
||||
Collection<Snippet> snippets = snippetProvider.getSnippets(type);
|
||||
YamlIndentUtil indenter = new YamlIndentUtil(doc);
|
||||
for (Snippet snippet : snippets) {
|
||||
String snippetName = snippet.getName();
|
||||
double score = FuzzyMatcher.matchScore(query, snippetName);
|
||||
if (score!=0.0 && snippet.isApplicable(getSchemaContext())) {
|
||||
DocumentEdits edits = new DocumentEdits(doc.getDocument());
|
||||
int start = offset - query.length();
|
||||
edits.delete(start, query);
|
||||
int referenceIndent = doc.getColumn(start);
|
||||
boolean needsSpace = start > 0 && !Character.isWhitespace(doc.getChar(start-1));
|
||||
if (needsSpace) {
|
||||
referenceIndent++;
|
||||
edits.insert(start, " ");
|
||||
}
|
||||
edits.insert(start, indenter.applyIndentation(snippet.getSnippet(), referenceIndent));
|
||||
completions.add(completionFactory().valueProposal(snippetName, query, snippetName, type, null, score, edits, typeUtil));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeUtil.isSequencable(type)) {
|
||||
completions = new ArrayList<>(completions);
|
||||
@@ -107,7 +132,6 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
|
||||
public List<ICompletionProposal> getKeyCompletions(YamlDocument doc, int offset, String query) throws Exception {
|
||||
int queryOffset = offset - query.length();
|
||||
SNode contextNode = getContextNode();
|
||||
DynamicSchemaContext dynamicCtxt = getSchemaContext();
|
||||
List<YTypedProperty> allProperties = typeUtil.getProperties(type);
|
||||
if (CollectionUtil.hasElements(allProperties)) {
|
||||
@@ -115,6 +139,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
Set<String> definedProps = dynamicCtxt.getDefinedProperties();
|
||||
List<ICompletionProposal> proposals = new ArrayList<>();
|
||||
boolean suggestDeprecated = typeUtil.suggestDeprecatedProperties();
|
||||
YamlIndentUtil indenter = new YamlIndentUtil(doc);
|
||||
for (List<YTypedProperty> thisTier : tieredProperties) {
|
||||
List<YTypedProperty> undefinedProps = thisTier.stream()
|
||||
.filter(p -> !definedProps.contains(p.getName()) && (suggestDeprecated || !p.isDeprecated()))
|
||||
@@ -124,15 +149,17 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
String name = p.getName();
|
||||
double score = FuzzyMatcher.matchScore(query, name);
|
||||
if (score!=0) {
|
||||
YamlPath relativePath = YamlPath.fromSimpleProperty(name);
|
||||
YamlPathEdits edits = new YamlPathEdits(doc);
|
||||
DocumentEdits edits = new DocumentEdits(doc.getDocument());
|
||||
YType YType = p.getType();
|
||||
edits.delete(queryOffset, query);
|
||||
int referenceIndent = doc.getColumn(queryOffset);
|
||||
if (queryOffset>0 && !Character.isWhitespace(doc.getChar(queryOffset-1))) {
|
||||
//See https://www.pivotaltracker.com/story/show/137722057
|
||||
edits.insert(queryOffset, " ");
|
||||
referenceIndent++;
|
||||
}
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(YType));
|
||||
String snippet = p.getName()+":" +appendTextFor(YType);
|
||||
edits.insert(queryOffset, indenter.applyIndentation(snippet, referenceIndent));
|
||||
ICompletionProposal completion = completionFactory().beanProperty(doc.getDocument(),
|
||||
contextPath.toPropString(), getType(),
|
||||
query, p, score, edits, typeUtil);
|
||||
@@ -197,16 +224,11 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
}
|
||||
|
||||
private List<ICompletionProposal> getValueCompletions(YamlDocument doc, SNode node, int offset, String query) {
|
||||
YValueHint[] values=null;
|
||||
try {
|
||||
values = typeUtil.getHintValues(type, getSchemaContext());
|
||||
} catch (Exception e) {
|
||||
if (!Boolean.getBoolean("lsp.yaml.completions.errors.disable")) {
|
||||
return ImmutableList.of(completionFactory().errorMessage(query, getMessage(e)));
|
||||
} else {
|
||||
Log.warn(query, e);
|
||||
}
|
||||
PartialCollection<YValueHint> _values = typeUtil.getHintValues(type, getSchemaContext());
|
||||
if (_values.getExplanation()!=null && _values.getElements().isEmpty() && !Boolean.getBoolean("lsp.yaml.completions.errors.disable")) {
|
||||
return ImmutableList.of(completionFactory().errorMessage(query, getMessage(_values.getExplanation())));
|
||||
}
|
||||
Collection<YValueHint> values = _values.getElements();
|
||||
if (values!=null) {
|
||||
ArrayList<ICompletionProposal> completions = new ArrayList<>();
|
||||
YamlIndentUtil indenter = new YamlIndentUtil(doc);
|
||||
@@ -243,7 +265,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private String getMessage(Exception _e) {
|
||||
private String getMessage(Throwable _e) {
|
||||
Throwable e = ExceptionUtil.getDeepestCause(_e);
|
||||
|
||||
// If value parse exception, do not append any additional information
|
||||
@@ -384,18 +406,20 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
@Override
|
||||
protected DocumentEdits transformEdit(DocumentEdits textEdit) {
|
||||
textEdit.transformFirstNonWhitespaceEdit((Integer offset, String insertText) -> {
|
||||
YamlIndentUtil indenter = new YamlIndentUtil("\n");
|
||||
if (needNewline(textEdit)) {
|
||||
return insertText.substring(0, offset)
|
||||
+ "\n" +Strings.repeat(" ", node.getIndent())+"- "
|
||||
+ insertText.substring(offset);
|
||||
+ indenter.applyIndentation(insertText.substring(offset), YamlIndentUtil.INDENT_BY);
|
||||
} else 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-2)
|
||||
+ "- "+ insertText.substring(offset);
|
||||
}
|
||||
}
|
||||
return insertText.substring(0, offset) + "- "+insertText.substring(offset);
|
||||
return insertText.substring(0, offset) + "- "+indenter.applyIndentation(insertText.substring(offset), YamlIndentUtil.INDENT_BY);
|
||||
});
|
||||
return textEdit;
|
||||
}
|
||||
@@ -404,11 +428,9 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
|
||||
//value proposals which are inserted right after a key will not automatically include a newline, as
|
||||
// its not required for them. So we should add it along with the dash.
|
||||
try {
|
||||
if (original instanceof ValueProposal) {
|
||||
Integer insertAt = textEdit.getFirstEditStart();
|
||||
if (insertAt!=null) {
|
||||
return !"".equals(doc.getLineTextBefore(insertAt).trim());
|
||||
}
|
||||
Integer insertAt = textEdit.getFirstEditStart();
|
||||
if (insertAt!=null) {
|
||||
return !"".equals(doc.getLineTextBefore(insertAt).trim());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
|
||||
@@ -52,7 +52,7 @@ import com.google.common.collect.ImmutableList;
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class YamlCompletionEngine implements ICompletionEngine {
|
||||
|
||||
|
||||
Pattern SPACES = Pattern.compile("[ ]+");
|
||||
|
||||
final static Logger logger = LoggerFactory.getLogger(YamlCompletionEngine.class);
|
||||
@@ -110,7 +110,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
|
||||
protected Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, int baseIndent, double deempasizeBy) {
|
||||
try {
|
||||
return fixIndentations(getBaseCompletions(offset, doc, current, contextNode),
|
||||
return fixIndentations(getBaseCompletions(offset, doc, current, contextNode),
|
||||
current, contextNode, baseIndent, deempasizeBy);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
@@ -118,7 +118,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode,
|
||||
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode,
|
||||
SNode contextNode, int baseIndent, double deempasizeBy) {
|
||||
if (!completions.isEmpty()) {
|
||||
int dashyIndent = getTargetIndent(contextNode, currentNode, true);
|
||||
@@ -144,7 +144,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
if (isExtraIndentRelaxable(contextNode, fixIndentBy)) {
|
||||
return indented(p, Strings.repeat(" ", fixIndentBy));
|
||||
}
|
||||
} else { // fixIndentBy < 0
|
||||
} else { // fixIndentBy < 0
|
||||
if (isLesserIndentRelaxable(currentNode, contextNode)) {
|
||||
return dedented(p, -fixIndentBy, contextNode.getDocument());
|
||||
}
|
||||
@@ -168,7 +168,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the indentation level needed to line up with other contextNode children.
|
||||
* Determine the indentation level needed to line up with other contextNode children.
|
||||
* If the contextNode has no children, then compute a proper default indentation where
|
||||
* a new child could be added.
|
||||
*/
|
||||
@@ -182,8 +182,8 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
if (child.isPresent()) {
|
||||
return child.get().getIndent();
|
||||
}
|
||||
return (dashy || contextNode.getNodeType()==SNodeType.DOC)
|
||||
? contextNode.getIndent()
|
||||
return (dashy || contextNode.getNodeType()==SNodeType.DOC)
|
||||
? contextNode.getIndent()
|
||||
: contextNode.getIndent() + YamlIndentUtil.INDENT_BY;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
};
|
||||
transformed.deemphasize(DEEMP_DEDENTED_PROPOSAL*numArrows);
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
// we can't dedent the proposal by the requested amount of space. So err on the safe
|
||||
// side and ignore the proposal. (Otherwise me might end up deleting non-space chars
|
||||
// in our attempt to de-dent.)
|
||||
@@ -226,7 +226,13 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
return Strings.repeat(Unicodes.RIGHT_ARROW+" ", numArrows) + originalLabel;
|
||||
}
|
||||
@Override public DocumentEdits transformEdit(DocumentEdits originalEdit) {
|
||||
originalEdit.indentFirstEdit(indentStr);
|
||||
// originalEdit.indentFirstEdit(indentStr);
|
||||
YamlIndentUtil indenter = new YamlIndentUtil("\n");
|
||||
originalEdit.transformFirstNonWhitespaceEdit((Integer offset, String insertText) -> {
|
||||
String prefix = insertText.substring(0, offset);
|
||||
String target = insertText.substring(offset);
|
||||
return prefix + indentStr + indenter.applyIndentation(target, indentStr);
|
||||
});
|
||||
return originalEdit;
|
||||
}
|
||||
};
|
||||
@@ -306,14 +312,14 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get context node candidates taking into account that we want to have a 'relaxed' interpretation
|
||||
* of the context node with respect to the current indentation where we ask for a completion.
|
||||
* To allow for the ambiguity in indentation a list of context nodes is returned instead of a
|
||||
* To allow for the ambiguity in indentation a list of context nodes is returned instead of a
|
||||
* single node. (Note we may still return a singleton list for cases where relaxed indentation
|
||||
* doesn't seem desirable).
|
||||
* @param baseIndent
|
||||
* @param baseIndent
|
||||
*/
|
||||
protected List<SNode> getContextNodes(YamlDocument doc, SNode node, int offset, int baseIndent) {
|
||||
if (node==null) {
|
||||
@@ -340,7 +346,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
//This node has flexibility around indentation. So this is where me need to build a list of candidates!
|
||||
ImmutableList.Builder<SNode> contextNodes = ImmutableList.builder();
|
||||
while (node!=null ) {
|
||||
//Any node that represents a 'step' between contexts and is not too deeply nested is kept.
|
||||
//Any node that represents a 'step' between contexts and is not too deeply nested is kept.
|
||||
if (node.getSegment()!=null && node.getIndent()<=baseIndent) {
|
||||
contextNodes.add(node);
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ package org.springframework.ide.vscode.commons.yaml.completion;
|
||||
public interface YamlCompletionEngineOptions {
|
||||
/**
|
||||
* Whether the completion engine includes 'less indented' proposals (i.e. proposals
|
||||
* that aren't valid at the current CA position, but are valid if we delete
|
||||
* some spaces in front of the cursor first.
|
||||
* that aren't valid at the current CA position, but are valid if we delete
|
||||
* some spaces in front of the cursor first.
|
||||
*/
|
||||
default boolean includeDeindentedProposals() {
|
||||
default boolean includeDeindentedProposals() {
|
||||
//Disabled by default for now because of bug introduced in VSCode 1.12:
|
||||
//https://github.com/Microsoft/vscode/issues/26096
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
YamlCompletionEngineOptions DEFAULT = new YamlCompletionEngineOptions() {};
|
||||
YamlCompletionEngineOptions TEST_DEFAULT = new YamlCompletionEngineOptions() {
|
||||
@Override public boolean includeDeindentedProposals() { return true; }
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -29,14 +28,16 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.EnumValueParser;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.PartialCollection;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
@@ -56,6 +57,7 @@ public class YTypeFactory {
|
||||
|
||||
private boolean enableTieredOptionalPropertyProposals = true;
|
||||
private boolean suggestDeprecatedProperties = true;
|
||||
private TypeBasedSnippetProvider snippetProvider = null;
|
||||
|
||||
private static class Deprecation {
|
||||
final String errorMsg;
|
||||
@@ -185,7 +187,7 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YValueHint[] getHintValues(YType type, DynamicSchemaContext dc) throws Exception {
|
||||
public PartialCollection<YValueHint> getHintValues(YType type, DynamicSchemaContext dc) {
|
||||
return ((AbstractType)type).getHintValues(dc);
|
||||
}
|
||||
|
||||
@@ -235,6 +237,11 @@ public class YTypeFactory {
|
||||
return ((AbstractType)type).getCustomContentAssistant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeBasedSnippetProvider getSnippetProvider() {
|
||||
return snippetProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tieredOptionalPropertyProposals() {
|
||||
return enableTieredOptionalPropertyProposals;
|
||||
@@ -257,7 +264,7 @@ public class YTypeFactory {
|
||||
private List<YTypedProperty> propertyList = new ArrayList<>();
|
||||
private List<YValueHint> hints = new ArrayList<>();
|
||||
private Map<String, YTypedProperty> cachedPropertyMap;
|
||||
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
|
||||
private SchemaContextAware<PartialCollection<YValueHint>> hintProvider;
|
||||
//TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware<Collection<YValueHint>>
|
||||
|
||||
private List<Constraint> constraints = new ArrayList<>(2);
|
||||
@@ -293,47 +300,19 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
public AbstractType setHintProvider(Callable<Collection<YValueHint>> hintProvider) {
|
||||
setHintProvider((DynamicSchemaContext dc) -> hintProvider);
|
||||
setHintProvider((DynamicSchemaContext dc) -> PartialCollection.compute(hintProvider));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AbstractType setHintProvider(SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider) {
|
||||
public AbstractType setHintProvider(SchemaContextAware<PartialCollection<YValueHint>> hintProvider) {
|
||||
//TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware<Collection<YValueHint>>
|
||||
this.hintProvider = hintProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public YValueHint[] getHintValues(DynamicSchemaContext dc) throws Exception {
|
||||
Collection<YValueHint> providerHints = null;
|
||||
try {
|
||||
providerHints=getProviderHints(dc);
|
||||
} catch (Exception e) {
|
||||
if (!hints.isEmpty()) {
|
||||
Log.log(e);
|
||||
//Recover from error returning just the static hints.
|
||||
return hints.toArray(new YValueHint[hints.size()]);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (providerHints == null || providerHints.isEmpty()) {
|
||||
return hints.toArray(new YValueHint[hints.size()]);
|
||||
} else {
|
||||
// Only merge if there are provider hints to merge
|
||||
Set<YValueHint> mergedHints = new LinkedHashSet<>();
|
||||
|
||||
// Add type hints first
|
||||
for (YValueHint val : hints) {
|
||||
mergedHints.add(val);
|
||||
}
|
||||
|
||||
// merge the provider hints
|
||||
for (YValueHint val : providerHints) {
|
||||
mergedHints.add(val);
|
||||
}
|
||||
return mergedHints.toArray(new YValueHint[mergedHints.size()]);
|
||||
}
|
||||
public PartialCollection<YValueHint> getHintValues(DynamicSchemaContext dc) {
|
||||
return getProviderHints(dc)
|
||||
.addAll(hints);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,14 +322,15 @@ public class YTypeFactory {
|
||||
hints = ImmutableList.copyOf(hints);
|
||||
}
|
||||
|
||||
private Collection<YValueHint> getProviderHints(DynamicSchemaContext dc) throws Exception {
|
||||
private PartialCollection<YValueHint> getProviderHints(DynamicSchemaContext dc) {
|
||||
if (hintProvider != null) {
|
||||
Callable<Collection<YValueHint>> withContext = hintProvider.withContext(dc);
|
||||
if (withContext != null) {
|
||||
return withContext.call();
|
||||
try {
|
||||
return hintProvider.withContext(dc);
|
||||
} catch (Exception e) {
|
||||
return PartialCollection.unknown(e);
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
return PartialCollection.empty();
|
||||
}
|
||||
|
||||
public List<Constraint> getConstraints() {
|
||||
@@ -417,7 +397,7 @@ public class YTypeFactory {
|
||||
parseWith((DynamicSchemaContext dc) -> parser);
|
||||
return this;
|
||||
}
|
||||
private SchemaContextAware<ValueParser> getParser() {
|
||||
public SchemaContextAware<ValueParser> getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
@@ -878,25 +858,41 @@ public class YTypeFactory {
|
||||
return ((YTypedPropertyImpl)prop).copy();
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<YValueHint>> values) {
|
||||
public YAtomicType yenumFromHints(String name, SchemaContextAware<BiFunction<String, Collection<String>, String>> errorMessageFormatter, SchemaContextAware<PartialCollection<YValueHint>> values) {
|
||||
YAtomicType t = yatomic(name);
|
||||
t.setHintProvider((dc) -> () -> values.withContext(dc));
|
||||
t.setHintProvider(values);
|
||||
t.parseWith((DynamicSchemaContext dc) -> {
|
||||
Collection<String> strings = YTypeFactory.values(values.withContext(dc));
|
||||
return new EnumValueParser(name, strings) {
|
||||
PartialCollection<YValueHint> hints = PartialCollection.fromCallable(() -> values.withContext(dc));
|
||||
return new EnumValueParser(name, hints.map(h -> h.getValue())) {
|
||||
@Override
|
||||
protected String createErrorMessage(String parseString, Collection<String> values) {
|
||||
return errorMessageFormatter.apply(parseString, values);
|
||||
try {
|
||||
return errorMessageFormatter.withContext(dc).apply(parseString, values);
|
||||
} catch (Exception e) {
|
||||
return super.createErrorMessage(parseString, values);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
return t;
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<Collection<String>> values) {
|
||||
public YAtomicType yenumFromDynamicValues(String name,
|
||||
SchemaContextAware<BiFunction<String, Collection<String>, String>> errorMessageFormatter,
|
||||
SchemaContextAware<PartialCollection<String>> values
|
||||
) {
|
||||
return yenumFromHints(name,
|
||||
//Error message formatter:
|
||||
(parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
|
||||
errorMessageFormatter,
|
||||
//Hints provider:
|
||||
(dc) -> hints(values.withContext(dc))
|
||||
);
|
||||
}
|
||||
|
||||
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<PartialCollection<String>> values) {
|
||||
return yenumFromHints(name,
|
||||
//Error message formatter:
|
||||
(dc) -> (parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
|
||||
//Hints provider:
|
||||
(dc) -> hints(values.withContext(dc))
|
||||
);
|
||||
@@ -906,30 +902,35 @@ public class YTypeFactory {
|
||||
return new EnumTypeBuilder(name, values);
|
||||
}
|
||||
|
||||
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
|
||||
public YAtomicType yenum(String name, SchemaContextAware<BiFunction<String, Collection<String>, String>> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
|
||||
YAtomicType t = yatomic(name);
|
||||
t.setHintProvider((dc) -> {
|
||||
Collection<String> strings = values.withContext(dc);
|
||||
return strings==null
|
||||
? null
|
||||
: () -> strings.stream()
|
||||
.map((s) -> new BasicYValueHint(s))
|
||||
.collect(Collectors.toSet());
|
||||
return PartialCollection.compute(() -> values.withContext(dc))
|
||||
.map(BasicYValueHint::new);
|
||||
});
|
||||
t.parseWith((DynamicSchemaContext dc) -> {
|
||||
EnumValueParser enumParser = new EnumValueParser(name, values.withContext(dc)) {
|
||||
@Override
|
||||
protected String createErrorMessage(String parseString, Collection<String> values) {
|
||||
return errorMessageFormatter.apply(parseString, values);
|
||||
try {
|
||||
return errorMessageFormatter.withContext(dc).apply(parseString, values);
|
||||
} catch (Exception e) {
|
||||
return super.createErrorMessage(parseString, values);
|
||||
}
|
||||
}
|
||||
};
|
||||
return enumParser;
|
||||
});
|
||||
return t;
|
||||
|
||||
}
|
||||
|
||||
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
|
||||
return yenum(name, (dc) -> errorMessageFormatter, values);
|
||||
}
|
||||
|
||||
public static Collection<String> values(Collection<YValueHint> hints) {
|
||||
return hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
|
||||
return hints == null ? null : hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public YAtomicType yenum(String name, String... values) {
|
||||
@@ -960,10 +961,16 @@ public class YTypeFactory {
|
||||
}
|
||||
|
||||
public static Collection<YValueHint> hints(Collection<String> values) {
|
||||
return values.stream()
|
||||
.map(YTypeFactory::hint)
|
||||
.collect(CollectorUtil.toMultiset());
|
||||
}
|
||||
|
||||
public static PartialCollection<YValueHint> hints(PartialCollection<String> values) {
|
||||
if (values!=null) {
|
||||
return values.stream().map(YTypeFactory::hint).collect(Collectors.toList());
|
||||
return values.map(YTypeFactory::hint);
|
||||
}
|
||||
return null;
|
||||
return PartialCollection.unknown();
|
||||
}
|
||||
|
||||
public YTypeFactory enableTieredProposals(boolean enable) {
|
||||
@@ -976,5 +983,10 @@ public class YTypeFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
public YTypeFactory setSnippetProvider(TypeBasedSnippetProvider snippetProvider) {
|
||||
this.snippetProvider = snippetProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.schema;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.PartialCollection;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.Snippet;
|
||||
import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider;
|
||||
|
||||
/**
|
||||
* An implementation of YTypeUtil provides implementations of various
|
||||
@@ -30,7 +34,7 @@ public interface YTypeUtil {
|
||||
boolean isSequencable(YType type);
|
||||
boolean isBean(YType type);
|
||||
YType getDomainType(YType type);
|
||||
YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc) throws Exception;
|
||||
PartialCollection<YValueHint> getHintValues(YType yType, DynamicSchemaContext dc);
|
||||
String niceTypeName(YType type);
|
||||
YType getKeyType(YType type);
|
||||
SchemaContextAware<ValueParser> getValueParser(YType type);
|
||||
@@ -47,9 +51,17 @@ public interface YTypeUtil {
|
||||
*/
|
||||
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
|
||||
List<Constraint> getConstraints(YType type);
|
||||
|
||||
|
||||
ISubCompletionEngine getCustomContentAssistant(YType type);
|
||||
|
||||
|
||||
/**
|
||||
* Config option for type-bases complection enging. Snippets can be
|
||||
* associated with schema types. These snippets will be suggested as
|
||||
* additional completions based on the type of value expected in
|
||||
* a context.
|
||||
*/
|
||||
TypeBasedSnippetProvider getSnippetProvider();
|
||||
|
||||
/**
|
||||
* Config option for type-based completion engine. This enables the
|
||||
* 'tiered' proposals feature (so that optional properties are not
|
||||
@@ -58,7 +70,7 @@ public interface YTypeUtil {
|
||||
boolean tieredOptionalPropertyProposals();
|
||||
/**
|
||||
* Config option for type-based completion engine. This enables/disables
|
||||
* whether engine should generate proposals for deprecated properties (true),
|
||||
* whether engine should generate proposals for deprecated properties (true),
|
||||
* or suppress them (false).
|
||||
*/
|
||||
boolean suggestDeprecatedProperties();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* 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.snippet;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
|
||||
public class Snippet {
|
||||
|
||||
private final String name;
|
||||
private final String snippet;
|
||||
private final Predicate<DynamicSchemaContext> applicability;
|
||||
|
||||
public Snippet(String name, String snippet, Predicate<DynamicSchemaContext> applicability) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.snippet = snippet;
|
||||
this.applicability = applicability;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getSnippet() {
|
||||
return snippet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Snippet [ name="+name+",\n" +snippet +"\n]";
|
||||
}
|
||||
public Predicate<DynamicSchemaContext> getApplicability() {
|
||||
return applicability;
|
||||
}
|
||||
public boolean isApplicable(DynamicSchemaContext dc) {
|
||||
return applicability==null || applicability.test(dc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* 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.snippet;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
|
||||
public interface TypeBasedSnippetProvider {
|
||||
|
||||
Collection<Snippet> getSnippets(YType contextType);
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.commons.yaml.util;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
@@ -42,7 +43,11 @@ public class YamlIndentUtil {
|
||||
}
|
||||
|
||||
public YamlIndentUtil(YamlDocument doc) {
|
||||
this(doc.getDocument().getDefaultLineDelimiter());
|
||||
this(doc.getDocument());
|
||||
}
|
||||
|
||||
public YamlIndentUtil(IDocument doc) {
|
||||
this(doc.getDefaultLineDelimiter());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +94,10 @@ public class YamlIndentUtil {
|
||||
return text.replaceAll("\\n", newlineWithIndent(indentBy));
|
||||
}
|
||||
|
||||
public String applyIndentation(String text, String indentStr) {
|
||||
return text.replaceAll("\\n", "\n"+indentStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase offset by indentation. Take care when 'indent' is -1 (unkownn) to
|
||||
* just return offset unmodified.
|
||||
|
||||
@@ -63,6 +63,7 @@ public class Editor {
|
||||
public static final Predicate<CompletionItem> PLAIN_COMPLETION = c -> !RELAXED_COMPLETION.test(c);
|
||||
public static final Predicate<CompletionItem> DEDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ");
|
||||
public static final Predicate<CompletionItem> INDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.RIGHT_ARROW+" ");
|
||||
public static final Predicate<CompletionItem> SNIPPET_COMPLETION = c -> c.getLabel().endsWith("Snippet");
|
||||
|
||||
static class EditorState {
|
||||
String documentContents;
|
||||
@@ -327,6 +328,10 @@ public class Editor {
|
||||
}
|
||||
|
||||
public List<CompletionItem> assertCompletionLabels(String... expectedLabels) throws Exception {
|
||||
return assertCompletionLabels(c -> true, expectedLabels);
|
||||
}
|
||||
|
||||
public List<CompletionItem> assertCompletionLabels(Predicate<CompletionItem> isInteresting, String... expectedLabels) throws Exception {
|
||||
StringBuilder expect = new StringBuilder();
|
||||
StringBuilder actual = new StringBuilder();
|
||||
for (String label : expectedLabels) {
|
||||
@@ -336,8 +341,10 @@ public class Editor {
|
||||
|
||||
List<CompletionItem> completions;
|
||||
for (CompletionItem completion : completions = getCompletions()) {
|
||||
actual.append(completion.getLabel());
|
||||
actual.append("\n");
|
||||
if (isInteresting.test(completion)) {
|
||||
actual.append(completion.getLabel());
|
||||
actual.append("\n");
|
||||
}
|
||||
}
|
||||
assertEquals(expect.toString(), actual.toString());
|
||||
return completions;
|
||||
@@ -417,19 +424,25 @@ public class Editor {
|
||||
String docText = doc.getText();
|
||||
if (edit!=null) {
|
||||
String replaceWith = edit.getNewText();
|
||||
//Apply indentfix, this is magic vscode seems to apply to edits returned by language server. So our harness has to
|
||||
// mimick that behavior. See https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
int referenceLine = edit.getRange().getStart().getLine();
|
||||
int cursorOffset = edit.getRange().getStart().getCharacter();
|
||||
String referenceIndent = doc.getLineIndentString(referenceLine);
|
||||
if (cursorOffset<referenceIndent.length()) {
|
||||
referenceIndent = referenceIndent.substring(0, cursorOffset);
|
||||
}
|
||||
replaceWith = replaceWith.replaceAll("\\n", "\n"+referenceIndent);
|
||||
int cursorReplaceOffset = 0;
|
||||
|
||||
int cursorReplaceOffset = replaceWith.indexOf(VS_CODE_CURSOR_MARKER);
|
||||
if (cursorReplaceOffset>=0) {
|
||||
replaceWith = replaceWith.substring(0, cursorReplaceOffset) + replaceWith.substring(cursorReplaceOffset+VS_CODE_CURSOR_MARKER.length());
|
||||
if (!Boolean.getBoolean("lsp.completions.indentation.enable")) {
|
||||
//Apply indentfix, this is magic vscode seems to apply to edits returned by language server. So our harness has to
|
||||
// mimick that behavior. See https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
int referenceLine = edit.getRange().getStart().getLine();
|
||||
int cursorOffset = edit.getRange().getStart().getCharacter();
|
||||
String referenceIndent = doc.getLineIndentString(referenceLine);
|
||||
if (cursorOffset<referenceIndent.length()) {
|
||||
referenceIndent = referenceIndent.substring(0, cursorOffset);
|
||||
}
|
||||
replaceWith = replaceWith.replaceAll("\\n", "\n"+referenceIndent);
|
||||
}
|
||||
|
||||
// Replace the cursor string
|
||||
cursorReplaceOffset = replaceWith.indexOf(VS_CODE_CURSOR_MARKER);
|
||||
if (cursorReplaceOffset >= 0) {
|
||||
replaceWith = replaceWith.substring(0, cursorReplaceOffset)
|
||||
+ replaceWith.substring(cursorReplaceOffset + VS_CODE_CURSOR_MARKER.length());
|
||||
} else {
|
||||
cursorReplaceOffset = replaceWith.length();
|
||||
}
|
||||
|
||||
@@ -92,16 +92,16 @@ import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class LanguageServerHarness {
|
||||
public class LanguageServerHarness<S extends SimpleLanguageServer> {
|
||||
|
||||
//Warning this 'harness' is incomplete. Growing it as needed.
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
private Callable<? extends SimpleLanguageServer> factory;
|
||||
private Callable<S> factory;
|
||||
private LanguageId defaultLanguageId;
|
||||
|
||||
private SimpleLanguageServer server;
|
||||
private S server;
|
||||
|
||||
private InitializeResult initResult;
|
||||
|
||||
@@ -110,12 +110,12 @@ public class LanguageServerHarness {
|
||||
private List<Editor> activeEditors = new ArrayList<>();
|
||||
|
||||
|
||||
public LanguageServerHarness(Callable<? extends SimpleLanguageServer> factory, LanguageId defaultLanguageId) {
|
||||
public LanguageServerHarness(Callable<S> factory, LanguageId defaultLanguageId) {
|
||||
this.factory = factory;
|
||||
this.defaultLanguageId = defaultLanguageId;
|
||||
}
|
||||
|
||||
public LanguageServerHarness(Callable<? extends SimpleLanguageServer> factory) throws Exception {
|
||||
public LanguageServerHarness(Callable<S> factory) throws Exception {
|
||||
this(factory, LanguageId.PLAINTEXT);
|
||||
}
|
||||
|
||||
@@ -184,9 +184,9 @@ public class LanguageServerHarness {
|
||||
workspaceCap.setExecuteCommand(exeCap);
|
||||
clientCap.setWorkspace(workspaceCap);
|
||||
initParams.setCapabilities(clientCap);
|
||||
initResult = server.initialize(initParams).get();
|
||||
if (server instanceof LanguageClientAware) {
|
||||
((LanguageClientAware) server).connect(new STS4LanguageClient() {
|
||||
initResult = getServer().initialize(initParams).get();
|
||||
if (getServer() instanceof LanguageClientAware) {
|
||||
((LanguageClientAware) getServer()).connect(new STS4LanguageClient() {
|
||||
@Override
|
||||
public void telemetryEvent(Object object) {
|
||||
// TODO Auto-generated method stub
|
||||
@@ -248,15 +248,15 @@ public class LanguageServerHarness {
|
||||
});
|
||||
|
||||
}
|
||||
server.initialized();
|
||||
getServer().initialized();
|
||||
return initResult;
|
||||
}
|
||||
|
||||
public TextDocumentInfo openDocument(TextDocumentInfo documentInfo) throws Exception {
|
||||
DidOpenTextDocumentParams didOpen = new DidOpenTextDocumentParams();
|
||||
didOpen.setTextDocument(documentInfo.getDocument());
|
||||
if (server!=null) {
|
||||
server.getTextDocumentService().didOpen(didOpen);
|
||||
if (getServer()!=null) {
|
||||
getServer().getTextDocumentService().didOpen(didOpen);
|
||||
}
|
||||
return documentInfo;
|
||||
}
|
||||
@@ -295,8 +295,8 @@ public class LanguageServerHarness {
|
||||
default:
|
||||
throw new IllegalStateException("Unkown SYNC mode: "+getDocumentSyncMode());
|
||||
}
|
||||
if (server!=null) {
|
||||
server.getTextDocumentService().didChange(didChange);
|
||||
if (getServer()!=null) {
|
||||
getServer().getTextDocumentService().didChange(didChange);
|
||||
}
|
||||
return documents.get(uri);
|
||||
}
|
||||
@@ -320,8 +320,8 @@ public class LanguageServerHarness {
|
||||
default:
|
||||
throw new IllegalStateException("Unkown SYNC mode: "+getDocumentSyncMode());
|
||||
}
|
||||
if (server!=null) {
|
||||
server.getTextDocumentService().didChange(didChange);
|
||||
if (getServer()!=null) {
|
||||
getServer().getTextDocumentService().didChange(didChange);
|
||||
}
|
||||
return documents.get(uri);
|
||||
}
|
||||
@@ -339,7 +339,7 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public PublishDiagnosticsParams getDiagnostics(TextDocumentInfo doc) throws Exception {
|
||||
this.server.waitForReconcile();
|
||||
this.getServer().waitForReconcile();
|
||||
return diagnostics.get(doc.getUri());
|
||||
}
|
||||
|
||||
@@ -376,8 +376,8 @@ public class LanguageServerHarness {
|
||||
TextDocumentPositionParams params = new TextDocumentPositionParams();
|
||||
params.setPosition(cursor);
|
||||
params.setTextDocument(doc.getId());
|
||||
server.waitForReconcile();
|
||||
Either<List<CompletionItem>, CompletionList> completions = server.getTextDocumentService().completion(params).get();
|
||||
getServer().waitForReconcile();
|
||||
Either<List<CompletionItem>, CompletionList> completions = getServer().getTextDocumentService().completion(params).get();
|
||||
if (completions.isLeft()) {
|
||||
List<CompletionItem> list = completions.getLeft();
|
||||
return new CompletionList(false, list);
|
||||
@@ -391,14 +391,14 @@ public class LanguageServerHarness {
|
||||
TextDocumentPositionParams params = new TextDocumentPositionParams();
|
||||
params.setPosition(cursor);
|
||||
params.setTextDocument(document.getId());
|
||||
return server.getTextDocumentService().hover(params ).get();
|
||||
return getServer().getTextDocumentService().hover(params ).get();
|
||||
}
|
||||
|
||||
|
||||
public CompletionItem resolveCompletionItem(CompletionItem maybeUnresolved) {
|
||||
if (server.hasLazyCompletionResolver()) {
|
||||
if (getServer().hasLazyCompletionResolver()) {
|
||||
try {
|
||||
return server.getTextDocumentService().resolveCompletionItem(maybeUnresolved).get();
|
||||
return getServer().getTextDocumentService().resolveCompletionItem(maybeUnresolved).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -477,14 +477,14 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public List<? extends Location> getDefinitions(TextDocumentPositionParams params) throws Exception {
|
||||
server.waitForReconcile(); //goto definitions relies on reconciler infos! Must wait or race condition breaking tests occasionally.
|
||||
return server.getTextDocumentService().definition(params).get();
|
||||
getServer().waitForReconcile(); //goto definitions relies on reconciler infos! Must wait or race condition breaking tests occasionally.
|
||||
return getServer().getTextDocumentService().definition(params).get();
|
||||
}
|
||||
|
||||
public List<CodeAction> getCodeActions(TextDocumentInfo doc, Diagnostic problem) throws Exception {
|
||||
CodeActionContext context = new CodeActionContext(ImmutableList.of(problem));
|
||||
List<? extends Command> actions =
|
||||
server.getTextDocumentService().codeAction(new CodeActionParams(doc.getId(), problem.getRange(), context)).get();
|
||||
getServer().getTextDocumentService().codeAction(new CodeActionParams(doc.getId(), problem.getRange(), context)).get();
|
||||
return actions.stream()
|
||||
.map((command) -> new CodeAction(this, command))
|
||||
.collect(Collectors.toList());
|
||||
@@ -498,7 +498,7 @@ public class LanguageServerHarness {
|
||||
//Note convert the params to a 'typeless' Object because that is more representative on how it will be
|
||||
// received when we get it in a real client/server setting (i.e. parsed from json).
|
||||
List untypedParams = mapper.convertValue(args, List.class);
|
||||
server.getWorkspaceService()
|
||||
getServer().getWorkspaceService()
|
||||
.executeCommand(new ExecuteCommandParams(command.getCommand(), untypedParams))
|
||||
.get();
|
||||
}
|
||||
@@ -544,9 +544,9 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public List<? extends SymbolInformation> getDocumentSymbols(TextDocumentInfo document) throws Exception {
|
||||
server.waitForReconcile(); //TODO: if the server works properly this shouldn't be needed it should do that internally itself somehow.
|
||||
getServer().waitForReconcile(); //TODO: if the server works properly this shouldn't be needed it should do that internally itself somehow.
|
||||
DocumentSymbolParams params = new DocumentSymbolParams(document.getId());
|
||||
return server.getTextDocumentService().documentSymbol(params).get();
|
||||
return getServer().getTextDocumentService().documentSymbol(params).get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,7 +554,7 @@ public class LanguageServerHarness {
|
||||
*/
|
||||
public SynchronizationPoint reconcilerThreadStart() {
|
||||
CompletableFuture<Void> blocker = new CompletableFuture<>();
|
||||
server.setTestListener(new LanguageServerTestListener() {
|
||||
getServer().setTestListener(new LanguageServerTestListener() {
|
||||
@Override
|
||||
public void reconcileStarted(String uri, int version) {
|
||||
try {
|
||||
@@ -585,4 +585,8 @@ public class LanguageServerHarness {
|
||||
return newEditor(IOUtil.toString(is));
|
||||
}
|
||||
}
|
||||
|
||||
public S getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user