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:
nsingh
2017-08-02 12:16:33 -07:00
55 changed files with 1837 additions and 408 deletions

View File

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

View File

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

View File

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

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

@@ -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));
}
}

View File

@@ -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;
}
/**

View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

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

View File

@@ -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();
}

View File

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