Merge branch 'application-yml-completions'

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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