PT 155476005 - Support for jumpy completions
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Pivotal, Inc.
|
||||
* Copyright (c) 2015, 2018 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
|
||||
@@ -142,7 +142,7 @@ public class DocumentEdits implements ProposalApplier {
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(DocumentState doc) throws BadLocationException {
|
||||
void apply(IDocumentState doc) throws BadLocationException {
|
||||
doc.insert(grabCursor, offset, text);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ public class DocumentEdits implements ProposalApplier {
|
||||
}
|
||||
public abstract int getStart();
|
||||
public abstract int getEnd();
|
||||
abstract void apply(DocumentState doc) throws BadLocationException;
|
||||
abstract void apply(IDocumentState doc) throws BadLocationException;
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
@@ -186,13 +186,17 @@ public class DocumentEdits implements ProposalApplier {
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(DocumentState doc) throws BadLocationException {
|
||||
void apply(IDocumentState doc) throws BadLocationException {
|
||||
doc.delete(grabCursor, start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "del("+start+"->"+end+")";
|
||||
try {
|
||||
return "del("+start+"->"+end+", ["+ doc.textBetween(start, end)+"])";
|
||||
} catch (BadLocationException e) {
|
||||
return "del("+start+"->"+end+")";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,112 +211,17 @@ public class DocumentEdits implements ProposalApplier {
|
||||
|
||||
}
|
||||
|
||||
private interface OffsetTransformer {
|
||||
public interface OffsetTransformer {
|
||||
int transform(int offset, Direction dir);
|
||||
}
|
||||
|
||||
private static final OffsetTransformer NULL_TRANSFORM = new OffsetTransformer() {
|
||||
static final OffsetTransformer NULL_TRANSFORM = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int offset, Direction dir) {
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DocumentState provides methods to modify a document, its methods accept
|
||||
* offsets expressed relative to the original document contents and keeps track
|
||||
* of a OffsetTransformer that maps them to offsets in the current document.
|
||||
*/
|
||||
private static class DocumentState {
|
||||
private IDocument doc; //may be null, in which case no actual modifications are performed
|
||||
private OffsetTransformer org2new = NULL_TRANSFORM;
|
||||
private int selection = -1; //-1 Means no edits where applied that change selection so
|
||||
// the current selection is unknown
|
||||
|
||||
public DocumentState(IDocument doc) {
|
||||
this.doc = doc;
|
||||
}
|
||||
|
||||
public void insert(boolean grabCursor, int start, final String text) throws BadLocationException {
|
||||
final int tStart = org2new.transform(start, Direction.AFTER);
|
||||
if (!text.isEmpty()) {
|
||||
if (doc!=null) {
|
||||
doc.replace(tStart, 0, text);
|
||||
}
|
||||
final OffsetTransformer parent = org2new;
|
||||
org2new = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int org, Direction dir) {
|
||||
int tOffset = parent.transform(org, dir);
|
||||
if (tOffset<tStart) {
|
||||
return tOffset;
|
||||
} else if (tOffset>tStart) {
|
||||
return tOffset + text.length();
|
||||
} else /* tOffset==tStart*/ {
|
||||
if (dir==Direction.BEFORE) {
|
||||
return tOffset;
|
||||
} else {
|
||||
return tOffset + text.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
if (grabCursor) {
|
||||
selection = tStart+text.length();
|
||||
} else if (selection > tStart) {
|
||||
selection += text.length();
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(boolean grabCursor, final int start, final int end) throws BadLocationException {
|
||||
final int tStart = org2new.transform(start, Direction.AFTER);
|
||||
if (end>start) { // skip work for 'delete nothing' op
|
||||
final int tEnd = org2new.transform(end, Direction.AFTER);
|
||||
if (tEnd>tStart) { // skip work for 'delete nothing' op
|
||||
if (doc!=null) {
|
||||
doc.replace(tStart, tEnd-tStart, "");
|
||||
}
|
||||
|
||||
final OffsetTransformer parent = org2new;
|
||||
org2new = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int org, Direction dir) {
|
||||
int tOffset = parent.transform(org, dir);
|
||||
if (tOffset<=tStart) {
|
||||
return tOffset;
|
||||
} else if (tOffset>=tEnd) {
|
||||
return tOffset - tEnd + tStart;
|
||||
} else {
|
||||
return start;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if (grabCursor) {
|
||||
selection = tStart;
|
||||
} else if (selection>tStart) {
|
||||
int len = end - start;
|
||||
if (len > 0) {
|
||||
selection = Math.max(tStart, selection-len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (doc==null) {
|
||||
return super.toString();
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("DocumentState(\n");
|
||||
buf.append(doc.get()+"\n");
|
||||
buf.append(")\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Edit> edits = new ArrayList<Edit>();
|
||||
private IDocument doc;
|
||||
|
||||
@@ -372,7 +281,11 @@ public class DocumentEdits implements ProposalApplier {
|
||||
|
||||
@Override
|
||||
public void apply(IDocument _doc) throws Exception {
|
||||
DocumentState doc = new DocumentState(_doc);
|
||||
IDocumentState doc = new DocumentState(_doc);
|
||||
apply(doc);
|
||||
}
|
||||
|
||||
public void apply(IDocumentState doc) throws Exception {
|
||||
for (Edit edit : edits) {
|
||||
edit.apply(doc);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 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.languageserver.completion;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.Direction;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.OffsetTransformer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* DocumentState provides methods to modify a document, its methods accept
|
||||
* offsets expressed relative to the original document contents and keeps track
|
||||
* of a OffsetTransformer that maps them to offsets in the current document.
|
||||
*/
|
||||
public class DocumentState implements IDocumentState {
|
||||
IDocument doc; //may be null, in which case no actual modifications are performed
|
||||
OffsetTransformer org2new = DocumentEdits.NULL_TRANSFORM;
|
||||
int selection = -1; //-1 Means no edits where applied that change selection so
|
||||
// the current selection is unknown
|
||||
|
||||
public DocumentState(IDocument doc) {
|
||||
this.doc = doc;
|
||||
}
|
||||
|
||||
public OffsetTransformer getOrg2New() {
|
||||
return this.org2new;
|
||||
}
|
||||
|
||||
public int getCursor() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.ide.vscode.commons.languageserver.completion.IDocumentState#insert(boolean, int, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void insert(boolean grabCursor, int start, final String text) throws BadLocationException {
|
||||
final int tStart = org2new.transform(start, Direction.AFTER);
|
||||
if (!text.isEmpty()) {
|
||||
if (doc!=null) {
|
||||
doc.replace(tStart, 0, text);
|
||||
}
|
||||
final OffsetTransformer parent = org2new;
|
||||
org2new = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int org, Direction dir) {
|
||||
int tOffset = parent.transform(org, dir);
|
||||
if (tOffset<tStart) {
|
||||
return tOffset;
|
||||
} else if (tOffset>tStart) {
|
||||
return tOffset + text.length();
|
||||
} else /* tOffset==tStart*/ {
|
||||
if (dir==Direction.BEFORE) {
|
||||
return tOffset;
|
||||
} else {
|
||||
return tOffset + text.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
if (grabCursor) {
|
||||
selection = tStart+text.length();
|
||||
} else if (selection > tStart) {
|
||||
selection += text.length();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.ide.vscode.commons.languageserver.completion.IDocumentState#delete(boolean, int, int)
|
||||
*/
|
||||
@Override
|
||||
public void delete(boolean grabCursor, final int start, final int end) throws BadLocationException {
|
||||
final int tStart = org2new.transform(start, Direction.AFTER);
|
||||
if (end>start) { // skip work for 'delete nothing' op
|
||||
final int tEnd = org2new.transform(end, Direction.AFTER);
|
||||
if (tEnd>tStart) { // skip work for 'delete nothing' op
|
||||
if (doc!=null) {
|
||||
doc.replace(tStart, tEnd-tStart, "");
|
||||
}
|
||||
|
||||
final OffsetTransformer parent = org2new;
|
||||
org2new = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int org, Direction dir) {
|
||||
int tOffset = parent.transform(org, dir);
|
||||
if (tOffset<=tStart) {
|
||||
return tOffset;
|
||||
} else if (tOffset>=tEnd) {
|
||||
return tOffset - tEnd + tStart;
|
||||
} else {
|
||||
return start;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if (grabCursor) {
|
||||
selection = tStart;
|
||||
} else if (selection>tStart) {
|
||||
int len = end - start;
|
||||
if (len > 0) {
|
||||
selection = Math.max(tStart, selection-len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (doc==null) {
|
||||
return super.toString();
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("DocumentState(\n");
|
||||
buf.append(doc.get()+"\n");
|
||||
buf.append(")\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public IDocument getDocument() {
|
||||
return this.doc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 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.languageserver.completion;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
|
||||
public interface IDocumentState {
|
||||
|
||||
void insert(boolean grabCursor, int start, String text) throws BadLocationException;
|
||||
|
||||
void delete(boolean grabCursor, int start, int end) throws BadLocationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 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.languageserver.completion;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.Direction;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.OffsetTransformer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class LspCompletionInterpreter implements IDocumentState {
|
||||
|
||||
private DocumentState docState;
|
||||
|
||||
private DocumentRegion mainLine;
|
||||
private DocumentRegion mainEditRegion;
|
||||
private DocumentRegion beforeMainEditRegion;
|
||||
private DocumentRegion afterMainEditRegion;
|
||||
|
||||
private boolean needsCursorMove = false;
|
||||
|
||||
private TextDocument originalDoc;
|
||||
|
||||
private SimpleLanguageServer server;
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LspCompletionInterpreter.class);
|
||||
|
||||
public LspCompletionInterpreter(TextDocument originalDoc, int cursor, SimpleLanguageServer server) {
|
||||
this.originalDoc = originalDoc;
|
||||
this.server = server;
|
||||
this.docState = new DocumentState(originalDoc.copy());
|
||||
this.mainLine = new DocumentRegion(originalDoc, originalDoc.getLineInformationOfOffset(cursor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(boolean grabCursor, int start, String text) throws BadLocationException {
|
||||
trackEditRegions(start, start);
|
||||
docState.insert(grabCursor, start, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(boolean grabCursor, int start, int end) throws BadLocationException {
|
||||
trackEditRegions(start, end);
|
||||
docState.delete(grabCursor, start, end);
|
||||
}
|
||||
|
||||
protected void trackEditRegions(int start, int end) {
|
||||
DocumentRegion editRegion = new DocumentRegion(originalDoc, start, end);
|
||||
this.needsCursorMove = true;
|
||||
if (isMainEdit(start, end)) {
|
||||
mainEditRegion = editRegion.merge(mainEditRegion);
|
||||
this.needsCursorMove = false;
|
||||
} else if (allBefore(start, end)) {
|
||||
beforeMainEditRegion = editRegion.merge(beforeMainEditRegion);
|
||||
} else if (allAfter(start, end)){
|
||||
afterMainEditRegion = editRegion.merge(afterMainEditRegion);
|
||||
} else {
|
||||
if (start < mainLine.getStart()) {
|
||||
DocumentRegion beforePiece = new DocumentRegion(originalDoc, start, mainLine.getStart());
|
||||
beforeMainEditRegion = beforePiece.merge(beforeMainEditRegion);
|
||||
editRegion = new DocumentRegion(originalDoc, mainLine.getStart(), editRegion.getEnd());
|
||||
}
|
||||
if (end > mainLine.getEnd()) {
|
||||
DocumentRegion afterPiece = new DocumentRegion(originalDoc, mainLine.getEnd(), end);
|
||||
afterMainEditRegion = afterPiece.merge(afterMainEditRegion);
|
||||
editRegion = new DocumentRegion(originalDoc, start, mainLine.getEnd());
|
||||
}
|
||||
mainEditRegion = editRegion.merge(mainEditRegion);
|
||||
}
|
||||
}
|
||||
|
||||
public void resolveEdits(CompletionItem item) throws BadLocationException {
|
||||
if (mainEditRegion != null) {
|
||||
TextEdit mainEdit = new TextEdit();
|
||||
mainEdit.setRange(originalDoc.toRange(mainEditRegion));
|
||||
OffsetTransformer org2New = docState.getOrg2New();
|
||||
String newText = docState.getDocument().textBetween(
|
||||
org2New.transform(mainEditRegion.getStart(), Direction.BEFORE),
|
||||
org2New.transform(mainEditRegion.getEnd(), Direction.AFTER)
|
||||
);
|
||||
item.setTextEdit(mainEdit);
|
||||
item.setInsertTextFormat(InsertTextFormat.Snippet);
|
||||
|
||||
if (Boolean.getBoolean("lsp.completions.indentation.enable")) {
|
||||
mainEdit.setNewText(newText);
|
||||
} else {
|
||||
mainEdit.setNewText(vscodeIndentFix(originalDoc, originalDoc.toPosition(mainEditRegion.getStart()), newText));
|
||||
}
|
||||
} else {
|
||||
item.setInsertText("");
|
||||
}
|
||||
|
||||
ImmutableList.Builder<TextEdit> additionalEdits = ImmutableList.builder();
|
||||
|
||||
resolveAdditionalEdit(beforeMainEditRegion, additionalEdits);
|
||||
resolveAdditionalEdit(afterMainEditRegion, additionalEdits);
|
||||
|
||||
item.setAdditionalTextEdits(additionalEdits.build());
|
||||
|
||||
if (needsCursorMove) {
|
||||
Position position = docState.getDocument().toPosition(docState.getCursor());
|
||||
item.setCommand(new Command("Move Cursor", server.MOVE_CURSOR_COMMAND_ID, ImmutableList.of(originalDoc.getUri(), position)));
|
||||
}
|
||||
}
|
||||
|
||||
protected void resolveAdditionalEdit(DocumentRegion editRegion, ImmutableList.Builder<TextEdit> builder)
|
||||
throws BadLocationException {
|
||||
if (editRegion != null) {
|
||||
TextEdit edit = new TextEdit();
|
||||
edit.setRange(originalDoc.toRange(editRegion));
|
||||
OffsetTransformer org2New = docState.getOrg2New();
|
||||
String newText = docState.getDocument().textBetween(
|
||||
org2New.transform(editRegion.getStart(), Direction.BEFORE),
|
||||
org2New.transform(editRegion.getEnd(), Direction.AFTER));
|
||||
edit.setNewText(newText);
|
||||
builder.add(edit);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMainEdit(int start, int end) {
|
||||
return mainLine.containsOffset(start) && mainLine.containsOffset(end);
|
||||
}
|
||||
|
||||
private boolean allBefore(int start, int end) {
|
||||
return end <= mainLine.getStart();
|
||||
}
|
||||
|
||||
private boolean allAfter(int start, int end) {
|
||||
return start >= mainLine.getEnd();
|
||||
}
|
||||
|
||||
private static String vscodeIndentFix(TextDocument doc, Position 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.
|
||||
//See here: https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
IndentUtil indenter = new IndentUtil(doc);
|
||||
try {
|
||||
String refIndent = indenter.getReferenceIndent(doc.toOffset(start), doc);
|
||||
if (!refIndent.isEmpty()) {
|
||||
return StringUtil.stripIndentation(refIndent, newText);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
}
|
||||
@@ -20,26 +20,19 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.MarkupContent;
|
||||
import org.eclipse.lsp4j.MarkupKind;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
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.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -153,7 +146,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
items.add(adaptItem(doc, c, sortkeys));
|
||||
items.add(adaptItem(doc, offset, c, sortkeys));
|
||||
} catch (Exception e) {
|
||||
logger.error("error computing completion", e);
|
||||
}
|
||||
@@ -166,14 +159,14 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
return Mono.just(SimpleTextDocumentService.NO_COMPLETIONS);
|
||||
}
|
||||
|
||||
private CompletionItem adaptItem(TextDocument doc, ICompletionProposal completion, SortKeys sortkeys) throws Exception {
|
||||
private CompletionItem adaptItem(TextDocument doc, int cursor, ICompletionProposal completion, SortKeys sortkeys) throws Exception {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(completion.getLabel());
|
||||
item.setKind(completion.getKind());
|
||||
item.setSortText(sortkeys.next());
|
||||
item.setFilterText(completion.getFilterText());
|
||||
item.setDetail(completion.getDetail());
|
||||
resolveEdits(doc, completion, item); //Warning. Its not allowed by LSP spec to resolveEdits
|
||||
resolveEdits(doc, cursor, completion, item); //Warning. Its not allowed by LSP spec to resolveEdits
|
||||
//lazy as we used to do in the past.
|
||||
if (resolver!=null) {
|
||||
item.setData(resolver.resolveLater(completion, doc));
|
||||
@@ -190,20 +183,28 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
item.setDocumentation(content);
|
||||
}
|
||||
|
||||
private static void resolveEdits(TextDocument doc, ICompletionProposal completion, CompletionItem item) {
|
||||
Optional<TextEdit> mainEdit = adaptEdits(doc, completion.getTextEdit());
|
||||
if (mainEdit.isPresent()) {
|
||||
item.setTextEdit(mainEdit.get());
|
||||
item.setInsertTextFormat(InsertTextFormat.Snippet);
|
||||
} else {
|
||||
item.setInsertText("");
|
||||
}
|
||||
/**
|
||||
* Fill in the text edit related fields in completion Item: textEdit, additionalTextEdits, command
|
||||
*
|
||||
*/
|
||||
private void resolveEdits(TextDocument doc, int cursor, ICompletionProposal completion, CompletionItem item) {
|
||||
|
||||
completion.getAdditionalEdit().ifPresent(edit -> {
|
||||
adaptEdits(doc, edit).ifPresent(extraEdit -> {
|
||||
item.setAdditionalTextEdits(ImmutableList.of(extraEdit));
|
||||
});
|
||||
});
|
||||
try {
|
||||
|
||||
LspCompletionInterpreter lspCompletionInterpreter = new LspCompletionInterpreter(doc, cursor, server);
|
||||
Optional<DocumentEdits> additionalEdit = completion.getAdditionalEdit();
|
||||
if (additionalEdit.isPresent()) {
|
||||
additionalEdit.get().apply(lspCompletionInterpreter);
|
||||
}
|
||||
|
||||
DocumentEdits mainEdit = completion.getTextEdit();
|
||||
mainEdit.apply(lspCompletionInterpreter);
|
||||
|
||||
lspCompletionInterpreter.resolveEdits(item);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.get().error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String toMarkdown(Renderable r) {
|
||||
@@ -213,46 +214,33 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Optional<TextEdit> adaptEdits(TextDocument doc, DocumentEdits edits) {
|
||||
try {
|
||||
TextReplace replaceEdit = edits.asReplacement(doc);
|
||||
if (replaceEdit==null) {
|
||||
//The original edit does nothing.
|
||||
return Optional.empty();
|
||||
} else {
|
||||
TextDocument newDoc = doc.copy();
|
||||
edits.apply(newDoc);
|
||||
TextEdit vscodeEdit = new TextEdit();
|
||||
vscodeEdit.setRange(doc.toRange(replaceEdit.start, replaceEdit.end-replaceEdit.start));
|
||||
if (Boolean.getBoolean("lsp.completions.indentation.enable")) {
|
||||
vscodeEdit.setNewText(replaceEdit.newText);
|
||||
} else {
|
||||
vscodeEdit.setNewText(vscodeIndentFix(doc, vscodeEdit.getRange().getStart(), replaceEdit.newText));
|
||||
}
|
||||
//TODO: cursor offset within newText? for now we assume its always at the end.
|
||||
return Optional.of(vscodeEdit);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.get().error("{}", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static String vscodeIndentFix(TextDocument doc, Position 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.
|
||||
//See here: https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
IndentUtil indenter = new IndentUtil(doc);
|
||||
try {
|
||||
String refIndent = indenter.getReferenceIndent(doc.toOffset(start), doc);
|
||||
if (!refIndent.isEmpty()) {
|
||||
return StringUtil.stripIndentation(refIndent, newText);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
LOG.get().error("{}", e);
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
// private static Optional<TextEdit> adaptEdits(TextDocument doc, DocumentEdits edits) {
|
||||
// try {
|
||||
// TextReplace replaceEdit = edits.asReplacement(doc);
|
||||
// if (replaceEdit==null) {
|
||||
// //The original edit does nothing.
|
||||
// return Optional.empty();
|
||||
// } else {
|
||||
// TextDocument newDoc = doc.copy();
|
||||
// edits.apply(newDoc);
|
||||
// TextEdit vscodeEdit = new TextEdit();
|
||||
// vscodeEdit.setRange(doc.toRange(replaceEdit.start, replaceEdit.end-replaceEdit.start));
|
||||
// if (Boolean.getBoolean("lsp.completions.indentation.enable")) {
|
||||
// vscodeEdit.setNewText(replaceEdit.newText);
|
||||
// } else {
|
||||
// vscodeEdit.setNewText(vscodeIndentFix(doc, vscodeEdit.getRange().getStart(), replaceEdit.newText));
|
||||
// }
|
||||
// //TODO: cursor offset within newText? for now we assume its always at the end.
|
||||
// return Optional.of(vscodeEdit);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// LOG.get().error("{}", e);
|
||||
// return Optional.empty();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public CompletionItem resolveCompletion(CompletionItem unresolved) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.MessageType;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.Registration;
|
||||
import org.eclipse.lsp4j.RegistrationParams;
|
||||
@@ -64,6 +65,7 @@ import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathLis
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixResolveParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
@@ -77,6 +79,8 @@ import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -102,8 +106,11 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
|
||||
|
||||
private static final Scheduler RECONCILER_SCHEDULER = Schedulers.newSingle("Reconciler");
|
||||
|
||||
|
||||
public final String EXTENSION_ID;
|
||||
private final String CODE_ACTION_COMMAND_ID;
|
||||
public final String MOVE_CURSOR_COMMAND_ID;
|
||||
|
||||
protected final LazyCompletionResolver completionResolver = createCompletionResolver();
|
||||
|
||||
private SimpleTextDocumentService tds;
|
||||
@@ -140,6 +147,10 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
|
||||
|
||||
private Map<String, ExecuteCommandHandler> commands = new HashMap<>();
|
||||
|
||||
private Gson gson = new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
private AsyncRunner async = new AsyncRunner();
|
||||
private ClasspathListenerManager classpathListenerManager;
|
||||
|
||||
@@ -177,6 +188,7 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
|
||||
public SimpleLanguageServer(String extensionId) {
|
||||
this.EXTENSION_ID = extensionId;
|
||||
this.CODE_ACTION_COMMAND_ID = "sts."+EXTENSION_ID+".codeAction";
|
||||
this.MOVE_CURSOR_COMMAND_ID = "sts."+EXTENSION_ID+".moveCursor";
|
||||
}
|
||||
|
||||
protected CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
|
||||
@@ -198,6 +210,12 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
|
||||
return applyEdit.flatMap(r -> r.isApplied() ? moveCursor : Mono.just(new ApplyWorkspaceEditResponse(true)));
|
||||
})
|
||||
.toFuture();
|
||||
} else if (MOVE_CURSOR_COMMAND_ID.equals(params.getCommand())) {
|
||||
Assert.isLegal(params.getArguments().size()==2);
|
||||
|
||||
String uri = ((JsonPrimitive)params.getArguments().get(0)).getAsString();
|
||||
Position position = gson.fromJson((JsonObject)params.getArguments().get(1), Position.class);
|
||||
return client.moveCursor(new CursorMovement(uri, position));
|
||||
}
|
||||
Log.warn("Unknown command ignored: "+params.getCommand());
|
||||
return CompletableFuture.completedFuture(false);
|
||||
@@ -372,10 +390,15 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
|
||||
codeLensOptions.setResolveProvider(hasCodeLensResolveProvider());
|
||||
c.setCodeLensProvider(codeLensOptions );
|
||||
}
|
||||
if (hasExecuteCommandSupport && hasQuickFixes()) {
|
||||
c.setExecuteCommandProvider(new ExecuteCommandOptions(ImmutableList.of(
|
||||
CODE_ACTION_COMMAND_ID
|
||||
)));
|
||||
if (hasExecuteCommandSupport ) {
|
||||
ImmutableList.Builder<String> builder = ImmutableList.builder();
|
||||
if (hasQuickFixes()) {
|
||||
builder.add(CODE_ACTION_COMMAND_ID);
|
||||
}
|
||||
builder.add(MOVE_CURSOR_COMMAND_ID);
|
||||
|
||||
c.setExecuteCommandProvider(new ExecuteCommandOptions(builder.build()));
|
||||
|
||||
}
|
||||
if (hasWorkspaceSymbolHandler()) {
|
||||
c.setWorkspaceSymbolProvider(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2018 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
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util.text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -139,6 +140,16 @@ public class DocumentRegion implements CharSequence, IRegion {
|
||||
public boolean containsOffset(int absoluteOffset) {
|
||||
return absoluteOffset>=start && absoluteOffset <= end;
|
||||
}
|
||||
|
||||
public Optional<DocumentRegion> intersection(DocumentRegion other) {
|
||||
int newStart = Math.max(this.start, other.start);
|
||||
int newEnd = Math.min(this.end, other.end);
|
||||
if (newEnd >= newStart) {
|
||||
return Optional.of(new DocumentRegion(doc, newStart, newEnd));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
@@ -339,4 +350,17 @@ public class DocumentRegion implements CharSequence, IRegion {
|
||||
return getStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes smallest document region that encompasses this region and the other region.
|
||||
*/
|
||||
public DocumentRegion merge(DocumentRegion otherEdit) {
|
||||
if (otherEdit == null) {
|
||||
return this;
|
||||
} else {
|
||||
int newStart = Math.min(otherEdit.getStart(), this.getStart());
|
||||
int newEnd = Math.max(otherEdit.getEnd(), this.getEnd());
|
||||
return new DocumentRegion(doc, newStart, newEnd);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2018 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
|
||||
@@ -45,5 +45,19 @@ public class DocumentUtil {
|
||||
throw new IllegalStateException("Bug!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the 'known minimum' of two document offsets. Correctly handle
|
||||
* when either one or both are '-1' (unknown).
|
||||
*/
|
||||
public static int min(int a, int b) {
|
||||
if (a==-1) {
|
||||
return b;
|
||||
} else if (b==-1) {
|
||||
return a;
|
||||
} else {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2016-2018 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
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
package org.springframework.ide.vscode.commons.util.text;
|
||||
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
|
||||
@@ -32,5 +33,6 @@ public interface IDocument {
|
||||
LanguageId getLanguageId();
|
||||
int getVersion();
|
||||
Range toRange(IRegion asRegion) throws BadLocationException;
|
||||
Position toPosition(int offset) throws BadLocationException;
|
||||
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ public class TextDocument implements IDocument {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Position toPosition(int offset) throws BadLocationException {
|
||||
int line = lineNumber(offset);
|
||||
int startOfLine = startOfLine(line);
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.Unicodes;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
@@ -87,7 +88,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
SNode current = root.find(offset);
|
||||
int cursorIndent = doc.getColumn(offset);
|
||||
int nodeIndent = current.getIndent();
|
||||
int baseIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
||||
int baseIndent = DocumentUtil.min(cursorIndent, nodeIndent);
|
||||
List<SNode> contextNodes = getContextNodes(doc, current, offset, baseIndent);
|
||||
if (current.getNodeType()==SNodeType.RAW) {
|
||||
//relaxed indentation
|
||||
@@ -460,7 +461,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
// rather than the structur-tree to determine the 'context' node.
|
||||
int cursorIndent = doc.getColumn(offset);
|
||||
int nodeIndent = node.getIndent();
|
||||
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
||||
int currentIndent = DocumentUtil.min(cursorIndent, nodeIndent);
|
||||
while (node.getIndent()==-1 || (node.getIndent()>=currentIndent && node.getNodeType()!=SNodeType.DOC)) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
@@ -53,20 +53,6 @@ public class YamlIndentUtil {
|
||||
this(doc.getDefaultLineDelimiter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the 'known minimum' of two indentation levels. Correctly handle
|
||||
* when either one or both indent levels are '-1' (unknown).
|
||||
*/
|
||||
public static int minIndent(int a, int b) {
|
||||
if (a==-1) {
|
||||
return b;
|
||||
} else if (b==-1) {
|
||||
return a;
|
||||
} else {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addIndent(int indent, StringBuilder buf) {
|
||||
for (int i = 0; i < indent; i++) {
|
||||
buf.append(' ');
|
||||
|
||||
@@ -14,6 +14,7 @@ package org.springframework.ide.vscode.languageserver.testharness;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness.getDocString;
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains;
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertDoesNotContain;
|
||||
|
||||
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
@@ -48,16 +50,16 @@ import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.junit.Assert;
|
||||
import org.springframework.ide.vscode.commons.languageserver.HighlightParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentState;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.Unicodes;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness.*;
|
||||
|
||||
public class Editor {
|
||||
|
||||
public static final Predicate<CompletionItem> RELAXED_COMPLETION
|
||||
@@ -445,7 +447,9 @@ public class Editor {
|
||||
public void apply(CompletionItem completion) throws Exception {
|
||||
completion = harness.resolveCompletionItem(completion);
|
||||
TextEdit edit = completion.getTextEdit();
|
||||
String docText = doc.getText();
|
||||
TextDocument textDoc = new TextDocument(getUri(), languageId);
|
||||
textDoc.setText(doc.getText());
|
||||
DocumentState docState = new DocumentState(textDoc);
|
||||
if (edit!=null) {
|
||||
String replaceWith = edit.getNewText();
|
||||
int cursorReplaceOffset = 0;
|
||||
@@ -474,9 +478,32 @@ public class Editor {
|
||||
Range rng = edit.getRange();
|
||||
int start = doc.toOffset(rng.getStart());
|
||||
int end = doc.toOffset(rng.getEnd());
|
||||
replaceText(start, end, replaceWith);
|
||||
selectionStart = selectionEnd = start+cursorReplaceOffset;
|
||||
docState.delete(true, start, end);
|
||||
docState.insert(true, start, replaceWith.substring(0, cursorReplaceOffset));
|
||||
docState.insert(false, start, replaceWith.substring(cursorReplaceOffset));
|
||||
|
||||
List<TextEdit> additionalTextEdits = completion.getAdditionalTextEdits();
|
||||
if (additionalTextEdits != null) {
|
||||
for (TextEdit textEdit : additionalTextEdits) {
|
||||
int textEditStart = doc.toOffset(textEdit.getRange().getStart());
|
||||
docState.delete(false, textEditStart, doc.toOffset(textEdit.getRange().getEnd()));
|
||||
docState.insert(false, textEditStart, textEdit.getNewText());
|
||||
}
|
||||
}
|
||||
|
||||
setRawText(docState.getDocument().get());
|
||||
|
||||
int cursor = docState.getCursor();
|
||||
if (cursor >= 0) {
|
||||
this.selectionStart = this.selectionEnd = cursor;
|
||||
}
|
||||
|
||||
Command command = completion.getCommand();
|
||||
if (command != null) {
|
||||
harness.perform(command);
|
||||
}
|
||||
} else {
|
||||
String docText = doc.getText();
|
||||
String insertText = getInsertText(completion);
|
||||
String newText = docText.substring(0, selectionStart) + insertText + docText.substring(selectionStart);
|
||||
|
||||
@@ -486,6 +513,7 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getInsertText(CompletionItem completion) {
|
||||
String s = completion.getInsertText();
|
||||
if (s==null) {
|
||||
|
||||
@@ -1401,23 +1401,6 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
|
||||
" bar: <*>"
|
||||
);
|
||||
|
||||
assertCompletion(
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
"other:\n" +
|
||||
"foo.nested.nested.b<*>"
|
||||
,
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar: <*>\n"+
|
||||
"other:\n"
|
||||
);
|
||||
|
||||
assertCompletion(
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
@@ -1433,22 +1416,6 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
|
||||
" bar: <*>\n"
|
||||
);
|
||||
|
||||
assertCompletion(
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
"other:\n" +
|
||||
"foo.nested.bar.b<*>"
|
||||
,
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
" bar: <*>\n" +
|
||||
"other:\n"
|
||||
);
|
||||
|
||||
assertCompletion(
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
@@ -1465,6 +1432,62 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
|
||||
|
||||
}
|
||||
|
||||
@Test public void testJumpyInsertion() throws Exception {
|
||||
String[] names = {"foo", "nested", "bar"};
|
||||
int levels = 4;
|
||||
generateNestedProperties(levels, names, "");
|
||||
|
||||
assertCompletion(
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
"other:\n" +
|
||||
"foo.nested.bar.b<*>"
|
||||
,
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
" bar: <*>\n" +
|
||||
"other:"
|
||||
);
|
||||
|
||||
assertCompletion(
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
"other:\n" +
|
||||
"foo.nested.nested.b<*>"
|
||||
,
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar: <*>\n"+
|
||||
"other:"
|
||||
);
|
||||
|
||||
assertCompletion(
|
||||
"foo.nested.nested.b<*>\n" +
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
"other:"
|
||||
,
|
||||
"foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar:\n" +
|
||||
" foo:\n" +
|
||||
" nested:\n" +
|
||||
" bar: <*>\n"+
|
||||
"other:"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void testBooleanValueCompletion() throws Exception {
|
||||
defaultTestData();
|
||||
assertCompletions(
|
||||
|
||||
Reference in New Issue
Block a user