Merge branch 'application-yml-completions'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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++);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user